Add clickable reference badges and improve UI layout
- Add clickable badges for backlinks and references that trigger query string searches - Improve toggle checkbox layout with better styling - Add description block styling with scrollable container - Update results styling with bordered cards and shadows - Add favicon support across pages - Enhance .env loading with logging for debugging 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
d8d2c5e34c
commit
14d37f23e4
@ -19,9 +19,14 @@ from typing import Optional
|
|||||||
# Load .env file if it exists
|
# Load .env file if it exists
|
||||||
try:
|
try:
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
import logging
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
_env_path = Path(__file__).parent / ".env"
|
_env_path = Path(__file__).parent / ".env"
|
||||||
if _env_path.exists():
|
if _env_path.exists():
|
||||||
load_dotenv(_env_path)
|
_logger.info(f"Loading .env from: {_env_path}")
|
||||||
|
result = load_dotenv(_env_path, override=True)
|
||||||
|
_logger.info(f"load_dotenv result: {result}")
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass # python-dotenv not installed
|
pass # python-dotenv not installed
|
||||||
|
|
||||||
|
|||||||
@ -748,7 +748,9 @@ def create_app(config: AppConfig = CONFIG) -> Flask:
|
|||||||
"secondary": bool(highlight_map.get("transcript_secondary_full")),
|
"secondary": bool(highlight_map.get("transcript_secondary_full")),
|
||||||
},
|
},
|
||||||
"internal_references_count": source.get("internal_references_count", 0),
|
"internal_references_count": source.get("internal_references_count", 0),
|
||||||
|
"internal_references": source.get("internal_references", []),
|
||||||
"referenced_by_count": source.get("referenced_by_count", 0),
|
"referenced_by_count": source.get("referenced_by_count", 0),
|
||||||
|
"referenced_by": source.get("referenced_by", []),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -802,48 +804,50 @@ def create_app(config: AppConfig = CONFIG) -> Flask:
|
|||||||
start = request.args.get("start", type=str)
|
start = request.args.get("start", type=str)
|
||||||
end = request.args.get("end", type=str)
|
end = request.args.get("end", type=str)
|
||||||
|
|
||||||
filters: List[Dict] = []
|
def parse_flag(name: str, default: bool = True) -> bool:
|
||||||
channel_filter = build_channel_filter(channels)
|
value = request.args.get(name)
|
||||||
if channel_filter:
|
if value is None:
|
||||||
filters.append(channel_filter)
|
return default
|
||||||
year_filter = build_year_filter(year)
|
lowered = value.lower()
|
||||||
if year_filter:
|
return lowered not in {"0", "false", "no"}
|
||||||
filters.append(year_filter)
|
|
||||||
|
use_exact = parse_flag("exact", True)
|
||||||
|
use_fuzzy = parse_flag("fuzzy", True)
|
||||||
|
use_phrase = parse_flag("phrase", True)
|
||||||
|
if use_query_string:
|
||||||
|
use_exact = use_fuzzy = use_phrase = False
|
||||||
|
|
||||||
|
search_payload = build_query_payload(
|
||||||
|
term,
|
||||||
|
channels=channels,
|
||||||
|
year=year,
|
||||||
|
sort="relevant",
|
||||||
|
use_exact=use_exact,
|
||||||
|
use_fuzzy=use_fuzzy,
|
||||||
|
use_phrase=use_phrase,
|
||||||
|
use_query_string=use_query_string,
|
||||||
|
)
|
||||||
|
query = search_payload.get("query", {"match_all": {}})
|
||||||
|
|
||||||
if start or end:
|
if start or end:
|
||||||
range_filter: Dict[str, Dict[str, Dict[str, str]]] = {"range": {"date": {}}}
|
range_filter: Dict[str, Dict[str, Dict[str, str]]] = {"range": {"date": {}}}
|
||||||
if start:
|
if start:
|
||||||
range_filter["range"]["date"]["gte"] = start
|
range_filter["range"]["date"]["gte"] = start
|
||||||
if end:
|
if end:
|
||||||
range_filter["range"]["date"]["lte"] = end
|
range_filter["range"]["date"]["lte"] = end
|
||||||
filters.append(range_filter)
|
if "bool" in query:
|
||||||
|
bool_clause = query.setdefault("bool", {})
|
||||||
base_fields = ["title^3", "description^2", "transcript_full", "transcript_secondary_full"]
|
existing_filter = bool_clause.get("filter")
|
||||||
if use_query_string:
|
if existing_filter is None:
|
||||||
qs_query = term or "*"
|
bool_clause["filter"] = [range_filter]
|
||||||
must_clause: List[Dict[str, Any]] = [
|
elif isinstance(existing_filter, list):
|
||||||
{
|
bool_clause["filter"].append(range_filter)
|
||||||
"query_string": {
|
|
||||||
"query": qs_query,
|
|
||||||
"default_operator": "AND",
|
|
||||||
"fields": base_fields,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
else:
|
else:
|
||||||
must_clause = [
|
bool_clause["filter"] = [existing_filter, range_filter]
|
||||||
{
|
elif query.get("match_all") is not None:
|
||||||
"multi_match": {
|
query = {"bool": {"filter": [range_filter]}}
|
||||||
"query": term,
|
else:
|
||||||
"fields": base_fields,
|
query = {"bool": {"must": [query], "filter": [range_filter]}}
|
||||||
"type": "best_fields",
|
|
||||||
"operator": "and",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
query: Dict[str, Any] = {"bool": {"must": must_clause}}
|
|
||||||
if filters:
|
|
||||||
query["bool"]["filter"] = filters
|
|
||||||
|
|
||||||
histogram: Dict[str, Any] = {
|
histogram: Dict[str, Any] = {
|
||||||
"field": "date",
|
"field": "date",
|
||||||
|
|||||||
138
static/app.js
138
static/app.js
@ -212,6 +212,36 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ensureQueryStringMode() {
|
||||||
|
if (!queryToggle) return;
|
||||||
|
if (!queryToggle.checked) {
|
||||||
|
rememberToggleState();
|
||||||
|
queryToggle.checked = true;
|
||||||
|
applyQueryMode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeQueryValue(value) {
|
||||||
|
return value.replace(/(["\\])/g, "\\$1");
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildFieldClause(field, ids) {
|
||||||
|
if (!Array.isArray(ids)) return null;
|
||||||
|
const seen = new Set();
|
||||||
|
const collected = [];
|
||||||
|
ids.forEach((raw) => {
|
||||||
|
if (!raw && raw !== 0) return;
|
||||||
|
const value = String(raw).trim();
|
||||||
|
if (!value) return;
|
||||||
|
if (seen.has(value)) return;
|
||||||
|
seen.add(value);
|
||||||
|
collected.push(value);
|
||||||
|
});
|
||||||
|
if (!collected.length) return null;
|
||||||
|
const escaped = collected.map((id) => `"${escapeQueryValue(id)}"`);
|
||||||
|
return `${field}:(${escaped.join(" OR ")})`;
|
||||||
|
}
|
||||||
|
|
||||||
if (channelOptions) {
|
if (channelOptions) {
|
||||||
channelOptions.addEventListener("change", (event) => {
|
channelOptions.addEventListener("change", (event) => {
|
||||||
const target = event.target;
|
const target = event.target;
|
||||||
@ -920,7 +950,7 @@ function renderFrequencyChart(buckets, channelTotals) {
|
|||||||
freqChart.appendChild(legend);
|
freqChart.appendChild(legend);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateFrequencyChart(term, channels, year, queryMode) {
|
async function updateFrequencyChart(term, channels, year, queryMode, toggles = {}) {
|
||||||
if (!freqChart || typeof d3 === "undefined") {
|
if (!freqChart || typeof d3 === "undefined") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -944,6 +974,10 @@ async function updateFrequencyChart(term, channels, year, queryMode) {
|
|||||||
if (queryMode) {
|
if (queryMode) {
|
||||||
params.set("query_string", "1");
|
params.set("query_string", "1");
|
||||||
}
|
}
|
||||||
|
const { exact = true, fuzzy = true, phrase = true } = toggles || {};
|
||||||
|
params.set("exact", exact ? "1" : "0");
|
||||||
|
params.set("fuzzy", fuzzy ? "1" : "0");
|
||||||
|
params.set("phrase", phrase ? "1" : "0");
|
||||||
|
|
||||||
clearFrequency("Loading timeline…");
|
clearFrequency("Loading timeline…");
|
||||||
try {
|
try {
|
||||||
@ -962,11 +996,16 @@ async function updateFrequencyChart(term, channels, year, queryMode) {
|
|||||||
freqSummary.textContent = `Matches: ${total.toLocaleString()} • Interval: ${payload.interval || "month"}`;
|
freqSummary.textContent = `Matches: ${total.toLocaleString()} • Interval: ${payload.interval || "month"}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const buckets = payload.buckets || [];
|
||||||
if (total === 0) {
|
if (total === 0) {
|
||||||
freqChart.innerHTML = "";
|
freqChart.innerHTML = "";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
renderFrequencyChart(payload.buckets || [], payload.channels || []);
|
if (!buckets.length) {
|
||||||
|
clearFrequency("Timeline unavailable for this query (missing video dates).");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
renderFrequencyChart(buckets, payload.channels || []);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
clearFrequency("Timeline unavailable.");
|
clearFrequency("Timeline unavailable.");
|
||||||
@ -988,46 +1027,97 @@ async function updateFrequencyChart(term, channels, year, queryMode) {
|
|||||||
item.descriptionHtml || escapeHtml(item.description || "");
|
item.descriptionHtml || escapeHtml(item.description || "");
|
||||||
|
|
||||||
const header = document.createElement("div");
|
const header = document.createElement("div");
|
||||||
const badges = [];
|
const badgeDefs = [];
|
||||||
if (item.highlightSource && item.highlightSource.primary) badges.push('primary transcript');
|
if (item.highlightSource && item.highlightSource.primary) {
|
||||||
if (item.highlightSource && item.highlightSource.secondary) badges.push('secondary transcript');
|
badgeDefs.push({ label: "primary transcript" });
|
||||||
|
}
|
||||||
|
if (item.highlightSource && item.highlightSource.secondary) {
|
||||||
|
badgeDefs.push({ label: "secondary transcript" });
|
||||||
|
}
|
||||||
|
|
||||||
// Add reference count badges
|
// Add reference count badges
|
||||||
const refByCount = item.referenced_by_count || 0;
|
const refByCount = item.referenced_by_count || 0;
|
||||||
const refToCount = item.internal_references_count || 0;
|
const refToCount = item.internal_references_count || 0;
|
||||||
|
const refByIds = Array.isArray(item.referenced_by) ? item.referenced_by : [];
|
||||||
|
const refToIds = Array.isArray(item.internal_references) ? item.internal_references : [];
|
||||||
|
|
||||||
// Debug: log reference counts for first item
|
if (refByCount > 0) {
|
||||||
if (badges.length === 0 || badges.length <= 2) {
|
let query = null;
|
||||||
console.log('Reference counts:', {
|
if (item.video_id) {
|
||||||
title: item.title,
|
query = buildFieldClause("internal_references", [item.video_id]);
|
||||||
referenced_by_count: refByCount,
|
}
|
||||||
internal_references_count: refToCount,
|
if (!query) {
|
||||||
hasReferencedBy: 'referenced_by_count' in item,
|
query = buildFieldClause("video_id", refByIds);
|
||||||
hasInternalRefs: 'internal_references_count' in item
|
}
|
||||||
|
badgeDefs.push({
|
||||||
|
label: `${refByCount} backlink${refByCount !== 1 ? "s" : ""}`,
|
||||||
|
query,
|
||||||
|
title: query
|
||||||
|
? "Show videos that reference this one"
|
||||||
|
: "Reference list unavailable in this result",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (refToCount > 0) {
|
||||||
|
const query = buildFieldClause("video_id", refToIds);
|
||||||
|
badgeDefs.push({
|
||||||
|
label: `${refToCount} reference${refToCount !== 1 ? "s" : ""}`,
|
||||||
|
query,
|
||||||
|
title: query
|
||||||
|
? "Show videos referenced by this one"
|
||||||
|
: "Reference list unavailable in this result",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (refByCount > 0) badges.push(`${refByCount} backlink${refByCount !== 1 ? 's' : ''}`);
|
|
||||||
if (refToCount > 0) badges.push(`${refToCount} reference${refToCount !== 1 ? 's' : ''}`);
|
|
||||||
|
|
||||||
const badgeHtml = badges.length
|
|
||||||
? `<div class="badge-row">${badges
|
|
||||||
.map((b) => `<span class="badge">${escapeHtml(b)}</span>` )
|
|
||||||
.join('')}</div>`
|
|
||||||
: '';
|
|
||||||
header.innerHTML = `
|
header.innerHTML = `
|
||||||
<strong>${titleHtml}</strong>
|
<strong>${titleHtml}</strong>
|
||||||
<div class="muted">${escapeHtml(item.channel_name || "")} • ${fmtDate(
|
<div class="muted">${escapeHtml(item.channel_name || "")} • ${fmtDate(
|
||||||
item.date
|
item.date
|
||||||
)}</div>
|
)}</div>
|
||||||
<div class="muted"><a href="${item.url}" target="_blank" rel="noopener">Open on YouTube</a></div>
|
<div class="muted"><a href="${item.url}" target="_blank" rel="noopener">Open on YouTube</a></div>
|
||||||
${badgeHtml}
|
|
||||||
`;
|
`;
|
||||||
|
if (badgeDefs.length) {
|
||||||
|
const badgeRow = document.createElement("div");
|
||||||
|
badgeRow.className = "badge-row";
|
||||||
|
badgeDefs.forEach((badge) => {
|
||||||
|
if (!badge || !badge.label) return;
|
||||||
|
const badgeEl = document.createElement("span");
|
||||||
|
badgeEl.className = "badge";
|
||||||
|
badgeEl.textContent = badge.label;
|
||||||
|
if (badge.title) {
|
||||||
|
badgeEl.title = badge.title;
|
||||||
|
}
|
||||||
|
if (badge.query) {
|
||||||
|
badgeEl.classList.add("badge-clickable");
|
||||||
|
badgeEl.setAttribute("role", "button");
|
||||||
|
badgeEl.tabIndex = 0;
|
||||||
|
const triggerSearch = () => {
|
||||||
|
if (!badge.query) return;
|
||||||
|
qInput.value = badge.query;
|
||||||
|
ensureQueryStringMode();
|
||||||
|
runSearch(0);
|
||||||
|
};
|
||||||
|
badgeEl.addEventListener("click", (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
triggerSearch();
|
||||||
|
});
|
||||||
|
badgeEl.addEventListener("keydown", (event) => {
|
||||||
|
if (event.key === "Enter" || event.key === " ") {
|
||||||
|
event.preventDefault();
|
||||||
|
triggerSearch();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
badgeRow.appendChild(badgeEl);
|
||||||
|
});
|
||||||
|
if (badgeRow.childElementCount) {
|
||||||
|
header.appendChild(badgeRow);
|
||||||
|
}
|
||||||
|
}
|
||||||
el.appendChild(header);
|
el.appendChild(header);
|
||||||
|
|
||||||
if (descriptionHtml) {
|
if (descriptionHtml) {
|
||||||
const desc = document.createElement("div");
|
const desc = document.createElement("div");
|
||||||
desc.className = "muted";
|
desc.className = "muted description-block";
|
||||||
desc.innerHTML = descriptionHtml;
|
desc.innerHTML = descriptionHtml;
|
||||||
el.appendChild(desc);
|
el.appendChild(desc);
|
||||||
}
|
}
|
||||||
@ -1130,7 +1220,7 @@ async function updateFrequencyChart(term, channels, year, queryMode) {
|
|||||||
const res = await fetch(`/api/search?${params.toString()}`);
|
const res = await fetch(`/api/search?${params.toString()}`);
|
||||||
const payload = await res.json();
|
const payload = await res.json();
|
||||||
renderResults(payload, page);
|
renderResults(payload, page);
|
||||||
updateFrequencyChart(q, channels, year, queryMode);
|
updateFrequencyChart(q, channels, year, queryMode, { exact, fuzzy, phrase });
|
||||||
}
|
}
|
||||||
|
|
||||||
searchBtn.addEventListener("click", () => runSearch(0));
|
searchBtn.addEventListener("click", () => runSearch(0));
|
||||||
|
|||||||
BIN
static/favicon.png
Normal file
BIN
static/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
@ -4,6 +4,7 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>Term Frequency Explorer</title>
|
<title>Term Frequency Explorer</title>
|
||||||
|
<link rel="icon" href="/static/favicon.png" type="image/png" />
|
||||||
<link rel="stylesheet" href="/static/style.css" />
|
<link rel="stylesheet" href="/static/style.css" />
|
||||||
<style>
|
<style>
|
||||||
#chart {
|
#chart {
|
||||||
@ -65,4 +66,3 @@
|
|||||||
<script src="/static/frequency.js"></script>
|
<script src="/static/frequency.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,8 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>This Little Corner (Python)</title>
|
<title>TLC Search</title>
|
||||||
|
<link rel="icon" href="/static/favicon.png" type="image/png" />
|
||||||
<link rel="stylesheet" href="https://unpkg.com/xp.css" />
|
<link rel="stylesheet" href="https://unpkg.com/xp.css" />
|
||||||
<link rel="stylesheet" href="/static/style.css" />
|
<link rel="stylesheet" href="/static/style.css" />
|
||||||
<script src="https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js"></script>
|
||||||
@ -11,7 +12,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<div class="window" style="max-width: 1200px; margin: 20px auto;">
|
<div class="window" style="max-width: 1200px; margin: 20px auto;">
|
||||||
<div class="title-bar">
|
<div class="title-bar">
|
||||||
<div class="title-bar-text">This Little Corner — Elastic Search</div>
|
<div class="title-bar-text">This Little Corner</div>
|
||||||
<div class="title-bar-controls">
|
<div class="title-bar-controls">
|
||||||
<button id="minimizeBtn" aria-label="Minimize"></button>
|
<button id="minimizeBtn" aria-label="Minimize"></button>
|
||||||
<button aria-label="Maximize"></button>
|
<button aria-label="Maximize"></button>
|
||||||
@ -59,19 +60,27 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field-row">
|
<div class="field-row toggle-row">
|
||||||
|
<div class="toggle-item toggle-item--first">
|
||||||
<input type="checkbox" id="exactToggle" checked />
|
<input type="checkbox" id="exactToggle" checked />
|
||||||
<label for="exactToggle">Exact</label>
|
<label for="exactToggle">Exact</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="toggle-item">
|
||||||
<input type="checkbox" id="fuzzyToggle" checked />
|
<input type="checkbox" id="fuzzyToggle" checked />
|
||||||
<label for="fuzzyToggle">Fuzzy</label>
|
<label for="fuzzyToggle">Fuzzy</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="toggle-item">
|
||||||
<input type="checkbox" id="phraseToggle" checked />
|
<input type="checkbox" id="phraseToggle" checked />
|
||||||
<label for="phraseToggle">Phrase</label>
|
<label for="phraseToggle">Phrase</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="toggle-item">
|
||||||
<input type="checkbox" id="queryStringToggle" />
|
<input type="checkbox" id="queryStringToggle" />
|
||||||
<label for="queryStringToggle">Query string mode</label>
|
<label for="queryStringToggle">Query string mode</label>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<div class="summary-row">
|
<div class="summary-row">
|
||||||
@ -84,7 +93,7 @@
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
<div class="summary-right">
|
<div class="summary-right">
|
||||||
<fieldset style="height: 100%;">
|
<fieldset>
|
||||||
<legend>Timeline</legend>
|
<legend>Timeline</legend>
|
||||||
<div id="frequencySummary" style="font-size: 11px; margin-bottom: 8px;"></div>
|
<div id="frequencySummary" style="font-size: 11px; margin-bottom: 8px;"></div>
|
||||||
<div id="frequencyChart"></div>
|
<div id="frequencyChart"></div>
|
||||||
|
|||||||
@ -119,6 +119,61 @@ body.dimmed {
|
|||||||
content: ' ▲';
|
content: ' ▲';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.toggle-row {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 4px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-row > * {
|
||||||
|
margin-left: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-item label {
|
||||||
|
cursor: pointer;
|
||||||
|
width: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-item--first {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-item input[type="checkbox"] {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-item input[type="checkbox"]:disabled + label {
|
||||||
|
color: GrayText;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-item input[type="checkbox"]:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-item input[type="checkbox"]:disabled + label {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description-block {
|
||||||
|
background: Window;
|
||||||
|
border: 1px solid #919b9c;
|
||||||
|
padding: 6px 8px;
|
||||||
|
margin-top: 6px;
|
||||||
|
font-size: 11px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
max-height: 6em;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.channel-options {
|
.channel-options {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
@ -165,16 +220,18 @@ body.dimmed {
|
|||||||
|
|
||||||
/* Results styling */
|
/* Results styling */
|
||||||
#results .item {
|
#results .item {
|
||||||
border-bottom: 1px solid ButtonShadow;
|
background: Window;
|
||||||
padding: 12px 0;
|
border: 2px solid #919b9c;
|
||||||
|
padding: 12px;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
|
box-shadow: 2px 2px 0 rgba(0, 0, 0, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
#results .item:last-child {
|
#results .item:last-child {
|
||||||
border-bottom: none;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#results .item strong {
|
#results .item strong {
|
||||||
@ -186,6 +243,7 @@ body.dimmed {
|
|||||||
.window-body {
|
.window-body {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
margin: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Badges */
|
/* Badges */
|
||||||
@ -209,6 +267,15 @@ body.dimmed {
|
|||||||
word-break: keep-all;
|
word-break: keep-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.badge-clickable {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-clickable:focus {
|
||||||
|
outline: 2px solid rgba(11, 110, 253, 0.6);
|
||||||
|
outline-offset: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Transcript and highlights */
|
/* Transcript and highlights */
|
||||||
.transcript {
|
.transcript {
|
||||||
background: Window;
|
background: Window;
|
||||||
@ -255,8 +322,7 @@ mark {
|
|||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
background: Window;
|
background: Window;
|
||||||
border: 2px solid;
|
border: 2px solid #919b9c;
|
||||||
border-color: ButtonShadow ButtonHighlight ButtonHighlight ButtonShadow;
|
|
||||||
max-height: 400px;
|
max-height: 400px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
@ -312,27 +378,9 @@ mark {
|
|||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.transcript-header {
|
.transcript-header,
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
background: ActiveCaption;
|
|
||||||
color: CaptionText;
|
|
||||||
padding: 2px 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.transcript-close {
|
.transcript-close {
|
||||||
cursor: pointer;
|
display: none;
|
||||||
font-size: 16px;
|
|
||||||
padding: 0 4px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.transcript-close:hover {
|
|
||||||
background: Highlight;
|
|
||||||
color: HighlightText;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Chart styling */
|
/* Chart styling */
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user