First commit.
55
static/alar.js
Normal file
@@ -0,0 +1,55 @@
|
||||
function hasKannadaChar(str) {
|
||||
return /[\u0C80-\u0CFF]/.test(str);
|
||||
}
|
||||
|
||||
(function () {
|
||||
const reMatchNonKannadaBlobs = new RegExp(/[^\u0C80-\u0CFF]+/g);
|
||||
// In the results (definitions), if there are Kannada words, hyperlink
|
||||
// them to search.
|
||||
let defs = document.querySelectorAll(".defs li");
|
||||
if (!defs || defs.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < defs.length; i++) {
|
||||
// Go through each definition. Ignore the ASCII ones.
|
||||
if (!hasKannadaChar(defs[i].innerText)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Split the word and iterate through it, turning non-ASCII words into
|
||||
// hyperlinks;
|
||||
const parts = defs[i].innerText.split(" ");
|
||||
const s = document.createElement("span");
|
||||
|
||||
parts.forEach((v) => {
|
||||
if (!hasKannadaChar(v)) {
|
||||
// ASCII word. Append the text as-is.
|
||||
s.appendChild(document.createTextNode(v));
|
||||
} else {
|
||||
// Non-ASCII word. Turn into a link.
|
||||
const a = document.createElement("a");
|
||||
|
||||
// Some Kannada words have non-Kannada characters around them,
|
||||
// the hyperlink should be formed only for Kannada words, while retaining all the characters order
|
||||
const kannadaWord = v.replace(reMatchNonKannadaBlobs, "");
|
||||
const nonKannadaWords = v.split(kannadaWord);
|
||||
|
||||
nonKannadaWords[0] && s.appendChild(document.createTextNode(nonKannadaWords[0]));
|
||||
|
||||
a.setAttribute("href", kannadaWord);
|
||||
a.appendChild(document.createTextNode(kannadaWord));
|
||||
s.appendChild(a);
|
||||
|
||||
nonKannadaWords[1] && s.appendChild(document.createTextNode(nonKannadaWords[1]));
|
||||
}
|
||||
|
||||
// Append a space.
|
||||
s.appendChild(document.createTextNode(" "));
|
||||
});
|
||||
|
||||
defs[i].innerHTML = "";
|
||||
defs[i].appendChild(s);
|
||||
}
|
||||
|
||||
})();
|
||||
130
static/alar.svg
Normal file
@@ -0,0 +1,130 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="7.25in"
|
||||
height="10.5in"
|
||||
viewBox="0 0 184.15 266.7"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
|
||||
sodipodi:docname="alar.svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1"
|
||||
inkscape:cx="399.34639"
|
||||
inkscape:cy="680.28571"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1850"
|
||||
inkscape:window-height="1016"
|
||||
inkscape:window-x="70"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
showguides="false"
|
||||
inkscape:guide-bbox="true"
|
||||
units="in">
|
||||
<sodipodi:guide
|
||||
position="35.189583,91.016669"
|
||||
orientation="1,0"
|
||||
id="guide868"
|
||||
inkscape:locked="false" />
|
||||
<sodipodi:guide
|
||||
position="179.12291,98.689586"
|
||||
orientation="1,0"
|
||||
id="guide870"
|
||||
inkscape:locked="false" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-30.3)">
|
||||
<g
|
||||
aria-label="ಅಲರ್"
|
||||
style="font-style:normal;font-weight:normal;font-size:61.74640656px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.54366004"
|
||||
id="text817"
|
||||
inkscape:export-filename="/home/kailash/code/go/my/knadh/dictionary-site/templates/static/logo.svg.png"
|
||||
inkscape:export-xdpi="44.132771"
|
||||
inkscape:export-ydpi="44.132771">
|
||||
<path
|
||||
d="m 41.731897,96.304657 q 0,1.447237 0.78392,2.472362 0.78392,1.025126 1.929649,1.025126 0.964825,0 1.628141,-1.025126 0.723619,-1.025125 0.723619,-2.472362 0,-1.447237 -0.723619,-2.472363 -0.663316,-1.085427 -1.628141,-1.085427 -1.145729,0 -1.929649,1.085427 -0.78392,1.025126 -0.78392,2.472363 z m 25.025134,6.271363 v 5.48743 H 53.068585 v -5.48743 h 6.753771 Q 58.556024,101.973 57.349993,99.621241 56.505772,97.9931 56.505772,96.304657 q 0,-3.798996 2.894473,-6.331661 2.592966,-2.291458 6.994977,-2.291458 4.522615,0 7.537691,4.100504 3.015076,4.100504 3.015076,11.035178 0,9.46734 -6.150755,15.19599 -6.150756,5.72864 -15.37689,5.72864 -9.045229,0 -14.532668,-5.66834 -5.427137,-5.66835 -5.427137,-15.13569 v 0 q 0,-6.934671 2.532664,-11.095476 2.592965,-4.160806 6.874374,-4.160806 3.075378,0 5.246233,2.532665 2.170855,2.532664 2.170855,6.090454 0,3.55779 -2.291458,6.090453 -2.291458,2.53267 -5.547741,2.53267 -0.663317,0 -1.56784,-0.18091 -0.844221,-0.24121 -1.929648,-0.60301 0,6.75377 3.9799,10.25126 5.246233,4.70351 10.793974,4.2814 8.140706,-0.60301 11.095481,-4.34171 6.814072,-8.56281 3.678393,-16.944726 -1.507538,-3.9799 -3.738695,-4.160805 -3.015076,-0.241206 -4.34171,2.231156 -0.78392,1.386936 -0.241206,3.376886 0.663317,2.592969 4.582916,3.738699 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:61.74876404px;font-family:'Lohit Kannada';-inkscape-font-specification:'Lohit Kannada Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:-1.32291663px;writing-mode:lr-tb;text-anchor:start;stroke-width:1.54366004"
|
||||
id="path866"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
d="m 90.881358,95.520737 q 0,1.326634 0.904523,2.231157 0.904523,0.904522 2.231157,0.904522 1.326633,0 2.231156,-0.904522 0.904523,-0.904523 0.904523,-2.231157 0,-1.326634 -0.904523,-2.231157 -0.904523,-0.904522 -2.231156,-0.904522 -1.326634,0 -2.231157,0.904522 -0.904523,0.904523 -0.904523,2.231157 z m 9.829152,22.733673 q 6.93467,0 11.09548,-4.0402 4.1608,-4.1005 4.1608,-10.85427 0,-3.37689 -2.17085,-6.331665 -2.11055,-2.954775 -5.66834,-4.643217 l 3.13567,-4.70352 q 4.64322,2.170855 7.41709,6.452264 2.77387,4.221107 2.77387,9.226138 0,9.22613 -5.66834,14.83417 -5.66834,5.54774 -15.07538,5.54774 -9.467343,0 -15.135686,-5.54774 -5.668344,-5.60804 -5.668344,-14.83417 0,-6.512569 4.100504,-11.095485 4.160806,-4.582917 10.010054,-4.582917 3.55779,0 6.090452,2.291458 2.53267,2.291459 2.53267,5.547741 0,3.376886 -2.53267,5.788943 -2.532662,2.41206 -6.090452,2.41206 -2.834172,0 -4.944726,-1.44723 -2.110553,-1.44724 -2.472362,-3.618094 -0.603015,0.723619 -0.904523,1.929654 -0.301508,1.20603 -0.301508,2.77387 0,6.75377 4.160806,10.85427 4.221107,4.0402 11.155785,4.0402 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:61.74876404px;font-family:'Lohit Kannada';-inkscape-font-specification:'Lohit Kannada Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:-1.32291663px;writing-mode:lr-tb;text-anchor:start;stroke-width:1.54366004"
|
||||
id="path868"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
d="m 130.08107,105.7117 q 0,5.18593 3.43719,8.86432 3.43718,3.67839 8.32161,3.67839 4.88442,0 8.32161,-3.67839 3.43719,-3.67839 3.43719,-8.86432 0,-5.18594 -3.43719,-8.864329 -3.43719,-3.678394 -8.32161,-3.678394 -4.88443,0 -8.32161,3.678394 -3.43719,3.678389 -3.43719,8.864329 z m 0.36181,-12.542723 h -5.84925 v -4.703519 h 37.38695 q 0.36181,-4.34171 3.19598,-7.236183 2.83417,-2.954775 6.57286,-2.954775 2.47237,0 4.52262,1.507538 2.05025,1.507539 2.95477,3.979901 l -3.91959,2.35176 q -0.84423,-1.447237 -1.80905,-2.291458 -0.96483,-0.844222 -1.74875,-0.844222 -1.92964,0 -3.31658,1.628142 -1.38693,1.567839 -1.38693,3.859297 h 2.35176 q 3.25628,0 5.54774,2.291458 2.29145,2.291458 2.29145,5.547741 0,3.256282 -2.29145,5.547743 -2.29146,2.29146 -5.54774,2.29146 -3.25629,0 -5.54775,-2.65327 -2.29145,-2.653269 -2.29145,-6.391964 v -1.929649 h -8.38192 q 2.83418,2.35176 4.34171,5.668344 1.56784,3.256279 1.56784,6.874379 0,7.47738 -5.06532,12.78392 -5.06533,5.24623 -12.18091,5.24623 -7.11558,0 -12.18091,-5.24623 -5.06533,-5.30654 -5.06533,-12.78392 0,-3.6181 1.50754,-6.874379 1.56784,-3.316584 4.34171,-5.668344 z m 35.81911,1.929649 q 0,1.809046 0.90452,3.075378 0.90452,1.266332 2.23116,1.266332 1.32663,0 2.23115,-1.145729 0.90452,-1.145729 0.90452,-2.77387 0,-0.964824 -0.90452,-1.628141 -0.90452,-0.723619 -2.23115,-0.723619 h -3.13568 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:61.74876404px;font-family:'Lohit Kannada';-inkscape-font-specification:'Lohit Kannada Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:-1.32291663px;writing-mode:lr-tb;text-anchor:start;stroke-width:1.54366004"
|
||||
id="path870"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:5.35675001px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.13391875"
|
||||
x="46.978397"
|
||||
y="146.70674"
|
||||
id="text866"
|
||||
transform="scale(0.9958963,1.0041206)"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan864"
|
||||
x="46.978397"
|
||||
y="146.70674"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.76250029px;font-family:Inter;-inkscape-font-specification:Inter;fill:#666666;stroke-width:0.13391875"><tspan
|
||||
style="font-style:italic;font-size:4.76250029px;stroke-width:0.13391875"
|
||||
id="tspan872">Alar -</tspan> V Krishna's Kannada - English dictionary</tspan></text>
|
||||
<text
|
||||
id="text894"
|
||||
y="136.81509"
|
||||
x="47.357018"
|
||||
style="font-style:normal;font-weight:normal;font-size:6.20058775px;line-height:1.25;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.15501469"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Inter;-inkscape-font-specification:Inter;text-align:start;text-anchor:start;fill:#666666;stroke-width:0.15501469"
|
||||
y="136.81509"
|
||||
x="47.357018"
|
||||
id="tspan892"
|
||||
sodipodi:role="line">ಶ್ರೀ . ವಿ ಕ್ರಿಷ್ಣ ರವರ ಕನ್ನಡ - ಇಂಗ್ಲಿಷ್ ನಿಘಂಟು</tspan></text>
|
||||
<text
|
||||
id="text864"
|
||||
y="141.99811"
|
||||
x="234.03926"
|
||||
style="font-style:normal;font-weight:normal;font-size:13.59828949px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.33995721"
|
||||
xml:space="preserve"><tspan
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:13.59880829px;font-family:'Lohit Kannada';-inkscape-font-specification:'Lohit Kannada Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:-0.2913433px;writing-mode:lr-tb;text-anchor:start;stroke-width:0.33995721"
|
||||
y="141.99811"
|
||||
x="234.03926"
|
||||
id="tspan862"
|
||||
sodipodi:role="line">ಅಲರ್</tspan></text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 10 KiB |
1
static/audio.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="15.298" height="11.711" viewBox="0 0 2.295 1.756" stroke="#999" stroke-width=".163" xmlns:v="https://vecta.io/nano"><path d="M1.168.081L.61.564H.081v.621h.52l.566.49z" fill="#999" stroke-linejoin="round"/><path d="M1.67.531c.139.211.139.485 0 .696M1.935.089a1.26 1.26 0 0 1 0 1.581" fill="none" stroke-linecap="round"/></svg>
|
||||
|
After Width: | Height: | Size: 372 B |
137
static/autocomp.js
Normal file
@@ -0,0 +1,137 @@
|
||||
function autocomp(el, options = {}) {
|
||||
const opt = {
|
||||
onQuery: null, onNavigate: null, onSelect: null, onRender: null, debounce: 100, autoSelect: true,...options
|
||||
};
|
||||
|
||||
let box, cur = opt.autoSelect ? 0 : -1, items = [], val, req;
|
||||
|
||||
// Disable browser's default autocomplete behaviour on the input.
|
||||
el.autocomplete = "off";
|
||||
|
||||
// Attach all the events required for the interactions in one go.
|
||||
["input", "keydown", "blur"].forEach(k => el.addEventListener(k, handleEvent));
|
||||
|
||||
function handleEvent(e) {
|
||||
if (e.type === "keydown" && !handleKeydown(e)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.type === "blur") {
|
||||
return destroy();
|
||||
}
|
||||
|
||||
const newVal = e.target.value;
|
||||
if (!newVal) {
|
||||
destroy();
|
||||
val = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (newVal === val && box) {
|
||||
return;
|
||||
}
|
||||
val = newVal;
|
||||
|
||||
// Clear (debounce) any existing pending requests and queue
|
||||
// the next search request.
|
||||
clearTimeout(req);
|
||||
req = setTimeout(query, opt.debounce);
|
||||
}
|
||||
|
||||
function handleKeydown(e) {
|
||||
if (!box) {
|
||||
return e.keyCode === 38 || e.keyCode === 40
|
||||
}
|
||||
|
||||
switch (e.keyCode) {
|
||||
case 38: return navigate(-1, e); // Up arrow.
|
||||
case 40: return navigate(1, e); // Down arrow
|
||||
case 9: // Tab
|
||||
case 13: // Enter
|
||||
e.preventDefault();
|
||||
select(cur);
|
||||
destroy();
|
||||
return;
|
||||
case 27: // Escape.
|
||||
destroy();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
async function query() {
|
||||
if (!val) {
|
||||
return;
|
||||
}
|
||||
|
||||
items = await opt.onQuery(val);
|
||||
if (!items.length) {
|
||||
return destroy();
|
||||
}
|
||||
|
||||
if (!box) {
|
||||
createBox();
|
||||
}
|
||||
|
||||
renderResults();
|
||||
}
|
||||
|
||||
function createBox() {
|
||||
box = document.createElement("div");
|
||||
Object.assign(box.style, {
|
||||
width: window.getComputedStyle(el).width,
|
||||
position: "absolute",
|
||||
left: `${el.offsetLeft}px`,
|
||||
top: `${el.offsetTop + el.offsetHeight}px`
|
||||
});
|
||||
|
||||
box.classList.add("autocomp");
|
||||
el.parentNode.insertBefore(box, el.nextSibling);
|
||||
}
|
||||
|
||||
function renderResults() {
|
||||
box.innerHTML = "";
|
||||
items.forEach((item, idx) => {
|
||||
const div = document.createElement("div");
|
||||
div.classList.add("autocomp-item");
|
||||
|
||||
// If there's a custom renderer callback, use it, else, simply insert the value/text as-is.
|
||||
opt.onRender ? div.appendChild(opt.onRender(item)) : div.innerText = item;
|
||||
if (idx === cur) {
|
||||
div.classList.add("autocomp-sel");
|
||||
}
|
||||
|
||||
div.addEventListener("mousedown", () => select(idx));
|
||||
box.appendChild(div);
|
||||
});
|
||||
}
|
||||
|
||||
function navigate(direction, e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Remove the previous item's highlight;
|
||||
const prev = box.querySelector(`:nth-child(${cur + 1})`);
|
||||
prev?.classList.remove("autocomp-sel");
|
||||
|
||||
// Increment the cursor and highlight the next item, cycled between [0, n].
|
||||
cur = (cur + direction + items.length) % items.length;
|
||||
box.querySelector(`:nth-child(${cur + 1})`).classList.add("autocomp-sel");
|
||||
}
|
||||
|
||||
function select(idx) {
|
||||
if (!opt.onSelect) {
|
||||
return;
|
||||
}
|
||||
|
||||
val = opt.onSelect(items[idx]);
|
||||
el.value = val || items[idx];
|
||||
}
|
||||
|
||||
function destroy() {
|
||||
items = [];
|
||||
cur = opt.autoSelect ? 0 : -1;
|
||||
if (box) {
|
||||
box.remove();
|
||||
box = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
1
static/down.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="6.857" viewBox="0 0 0.36 0.206" fill="none" xmlns:v="https://vecta.io/nano"><path d="M.026.026L.18.18.334.026" stroke="#000" stroke-width=".051" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
|
After Width: | Height: | Size: 258 B |
2
static/export.svg
Normal file
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg fill="#aaa" width="18px" height="18px" viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg"><path d="M30.3 13.7L25 8.4l-5.3 5.3-1.4-1.4L25 5.6l6.7 6.7z"/><path d="M24 7h2v21h-2z"/><path d="M35 40H15c-1.7 0-3-1.3-3-3V19c0-1.7 1.3-3 3-3h7v2h-7c-.6 0-1 .4-1 1v18c0 .6.4 1 1 1h20c.6 0 1-.4 1-1V19c0-.6-.4-1-1-1h-7v-2h7c1.7 0 3 1.3 3 3v18c0 1.7-1.3 3-3 3z"/></svg>
|
||||
|
After Width: | Height: | Size: 485 B |
BIN
static/favicon.png
Normal file
|
After Width: | Height: | Size: 852 B |
135
static/flexit.css
Normal file
@@ -0,0 +1,135 @@
|
||||
.container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.row {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex: 0 1 auto;
|
||||
flex-flow: row wrap;
|
||||
}
|
||||
.row.nogutter {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
.row.nogutter > .columns {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
.columns {
|
||||
box-sizing: border-box;
|
||||
flex: 0 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
.one {
|
||||
flex-basis: 8.33333333%;
|
||||
}
|
||||
.two {
|
||||
flex-basis: 16.66666667%;
|
||||
}
|
||||
.three {
|
||||
flex-basis: 25%;
|
||||
}
|
||||
.four {
|
||||
flex-basis: 33.3333333333%;
|
||||
}
|
||||
.five {
|
||||
flex-basis: 41.66666667%;
|
||||
}
|
||||
.six {
|
||||
flex-basis: 50%;
|
||||
}
|
||||
.seven {
|
||||
flex-basis: 58.33333333%;
|
||||
}
|
||||
.eight {
|
||||
flex-basis: 66.66666667%;
|
||||
}
|
||||
.nine {
|
||||
flex-basis: 75%;
|
||||
}
|
||||
.ten {
|
||||
flex-basis: 83.33333333%;
|
||||
}
|
||||
.eleven {
|
||||
flex-basis: 91.66666667%;
|
||||
}
|
||||
.twelve {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
.col-offset-0 {
|
||||
margin-left: 0;
|
||||
}
|
||||
.col-offset-1 {
|
||||
margin-left: 8.33333333%;
|
||||
}
|
||||
.col-offset-2 {
|
||||
margin-left: 16.66666667%;
|
||||
}
|
||||
.col-offset-3 {
|
||||
margin-left: 25%;
|
||||
}
|
||||
.col-offset-4 {
|
||||
margin-left: 33.33333333%;
|
||||
}
|
||||
.col-offset-5 {
|
||||
margin-left: 41.66666667%;
|
||||
}
|
||||
.col-offset-6 {
|
||||
margin-left: 50%;
|
||||
}
|
||||
.col-offset-7 {
|
||||
margin-left: 58.33333333%;
|
||||
}
|
||||
.col-offset-8 {
|
||||
margin-left: 66.66666667%;
|
||||
}
|
||||
.col-offset-9 {
|
||||
margin-left: 75%;
|
||||
}
|
||||
.col-offset-10 {
|
||||
margin-left: 83.33333333%;
|
||||
}
|
||||
.col-offset-11 {
|
||||
margin-left: 91.66666667%;
|
||||
}
|
||||
.between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
.end {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.around {
|
||||
justify-content: space-around;
|
||||
}
|
||||
.row-align-center {
|
||||
align-items: center;
|
||||
}
|
||||
.space-right {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.space-left {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.space-bottom {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.space-top {
|
||||
margin-top: 10px;
|
||||
}
|
||||
@media (max-width: 980px) {
|
||||
.columns {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
.offset-0,
|
||||
.offset-1,
|
||||
.offset-2 {
|
||||
margin: unset;
|
||||
}
|
||||
.container {
|
||||
padding: 0 15px;
|
||||
}
|
||||
}
|
||||
1
static/idaf-logo.svg
Normal file
|
After Width: | Height: | Size: 11 KiB |
1
static/logo.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="418.375" height="132.314" viewBox="0 0 110.695 35.008" xmlns:v="https://vecta.io/nano"><path d="M4.829 13.883q0 1.114.604 1.904.604.789 1.486.789.743 0 1.254-.789.557-.789.557-1.904 0-1.114-.557-1.904-.511-.836-1.254-.836-.882 0-1.486.836-.604.789-.604 1.904zm19.269 4.829v4.225h-10.54v-4.225h5.2q-.975-.464-1.904-2.275-.65-1.254-.65-2.554 0-2.925 2.229-4.875 1.996-1.764 5.386-1.764 3.482 0 5.804 3.157 2.322 3.157 2.322 8.497 0 7.29-4.736 11.7-4.736 4.411-11.84 4.411-6.965 0-11.19-4.364Q0 26.279 0 18.99h0q0-5.339 1.95-8.543 1.996-3.204 5.293-3.204 2.368 0 4.039 1.95 1.671 1.95 1.671 4.689 0 2.739-1.764 4.689-1.764 1.95-4.272 1.95-.511 0-1.207-.139-.65-.186-1.486-.464 0 5.2 3.064 7.893 4.039 3.622 8.311 3.297 6.268-.464 8.543-3.343 5.247-6.593 2.832-13.047-1.161-3.064-2.879-3.204-2.322-.186-3.343 1.718-.604 1.068-.186 2.6.511 1.997 3.529 2.879zm18.574-5.433q0 1.021.696 1.718.696.696 1.718.696 1.021 0 1.718-.696.696-.696.696-1.718 0-1.021-.696-1.718-.696-.696-1.718-.696-1.021 0-1.718.696-.696.696-.696 1.718zm7.568 17.504q5.339 0 8.543-3.111 3.204-3.157 3.204-8.357 0-2.6-1.671-4.875-1.625-2.275-4.364-3.575l2.414-3.622q3.575 1.671 5.711 4.968 2.136 3.25 2.136 7.104 0 7.104-4.364 11.422-4.364 4.272-11.608 4.272-7.29 0-11.654-4.272-4.364-4.318-4.364-11.422 0-5.014 3.157-8.543 3.204-3.529 7.707-3.529 2.739 0 4.689 1.764 1.95 1.764 1.95 4.272 0 2.6-1.95 4.457-1.95 1.857-4.689 1.857-2.182 0-3.807-1.114-1.625-1.114-1.904-2.786-.464.557-.696 1.486-.232.929-.232 2.136 0 5.2 3.204 8.357 3.25 3.111 8.59 3.111zm22.615-9.657q0 3.993 2.647 6.825 2.647 2.832 6.407 2.832 3.761 0 6.407-2.832 2.647-2.832 2.647-6.825 0-3.993-2.647-6.825-2.647-2.832-6.407-2.832-3.761 0-6.407 2.832-2.647 2.832-2.647 6.825zm.279-9.657H68.63V7.847h28.787q.279-3.343 2.461-5.572Q102.059 0 104.938 0q1.904 0 3.482 1.161 1.579 1.161 2.275 3.064l-3.018 1.811q-.65-1.114-1.393-1.764-.743-.65-1.346-.65-1.486 0-2.554 1.254-1.068 1.207-1.068 2.972h1.811q2.507 0 4.272 1.764 1.764 1.764 1.764 4.272 0 2.507-1.764 4.272-1.764 1.764-4.272 1.764-2.507 0-4.272-2.043-1.764-2.043-1.764-4.922v-1.486h-6.454q2.182 1.811 3.343 4.364 1.207 2.507 1.207 5.293 0 5.757-3.9 9.843-3.9 4.039-9.379 4.039-5.479 0-9.379-4.039-3.9-4.086-3.9-9.843 0-2.786 1.161-5.293 1.207-2.554 3.343-4.364zm27.58 1.486q0 1.393.696 2.368.696.975 1.718.975 1.021 0 1.718-.882.696-.882.696-2.136 0-.743-.696-1.254-.696-.557-1.718-.557h-2.414z"/></svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
304
static/main.js
Normal file
@@ -0,0 +1,304 @@
|
||||
async function screenshotElement(element) {
|
||||
const canvas = await html2canvas(element);
|
||||
canvas.toBlob(blob => {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'screenshot.png';
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
});
|
||||
}
|
||||
|
||||
(() => {
|
||||
const elForm = document.querySelector("form.search-form");
|
||||
const elQ = document.querySelector("#q");
|
||||
const elSelDict = document.querySelector("form.search-form select");
|
||||
const defaultLang = elSelDict.value;
|
||||
|
||||
|
||||
// ===================================
|
||||
// Helper functions.
|
||||
|
||||
function selectDict(dict) {
|
||||
// dict is in the format '$fromLang/$toLang'.
|
||||
const langs = dict.split("/");
|
||||
Object.assign(localStorage, { dict, from_lang: langs[0], to_lang: langs[1] });
|
||||
elSelDict.value = dict;
|
||||
elForm.setAttribute("action", `${_ROOT_URL}/dictionary/${dict}`);
|
||||
}
|
||||
|
||||
// Capture the form submit and send it as a canonical URL instead
|
||||
// of the ?q query param.
|
||||
function search(q) {
|
||||
let val = q.trim();
|
||||
|
||||
const uri = elForm.getAttribute("action");
|
||||
document.location.href = `${uri}/${encodeURIComponent(val).replace(/%20/g, "+")}`;
|
||||
}
|
||||
|
||||
// ===================================
|
||||
// Events
|
||||
|
||||
// On ~ press, focus search input.
|
||||
document.onkeydown = (function (e) {
|
||||
if (e.key !== "`" && e.key !== "~") {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
q.focus();
|
||||
q.select();
|
||||
});
|
||||
|
||||
// Bind to language change.
|
||||
elSelDict.addEventListener("change", function (e) {
|
||||
selectDict(e.target.value);
|
||||
});
|
||||
|
||||
// Bind to form submit.
|
||||
elForm.addEventListener("submit", function (e) {
|
||||
e.preventDefault();
|
||||
search(elQ.value);
|
||||
});
|
||||
|
||||
// "More" link next to entries/defs that expands an entry.
|
||||
document.querySelectorAll(".more-toggle").forEach((el) => {
|
||||
el.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
let state = "block";
|
||||
if (el.dataset.open) {
|
||||
delete (el.dataset.open);
|
||||
state = "none";
|
||||
} else {
|
||||
el.dataset.open = true;
|
||||
state = "block";
|
||||
}
|
||||
|
||||
const container = document.getElementById(el.dataset.id);
|
||||
container.style.display = state;
|
||||
|
||||
// fetch() content from the dataset data.guid from /api/entry/{guid}
|
||||
// and populate the div.more innerHTML with the content.
|
||||
if (state === "block" && !el.dataset.fetched) {
|
||||
container.innerHTML = `<div class="loader"></div>`;
|
||||
|
||||
fetch(`${window._ROOT_URL}/api/dictionary/entries/${el.dataset.entryGuid}`)
|
||||
.then((resp) => resp.json())
|
||||
.then((data) => {
|
||||
const d = data.data;
|
||||
container.innerHTML = "";
|
||||
|
||||
// If there's data.data.meta.synonyms, add them to the synonyms div.
|
||||
if (d.meta.synonyms) {
|
||||
const syns = document.createElement("div");
|
||||
syns.classList.add("synonyms");
|
||||
syns.innerHTML = d.meta.synonyms.map((s) => `<a href="${window._ROOT_URL}/dictionary/${el.dataset.fromLang}/${el.dataset.toLang}/${s}">${s}</a>`).join("");
|
||||
container.appendChild(syns);
|
||||
}
|
||||
|
||||
// Each data.content[] word, add to target.
|
||||
const more = document.createElement("div");
|
||||
more.classList.add("words");
|
||||
more.innerHTML = d.content.slice(window._MAX_CONTENT_ITEMS).map((c) => `<span class="word">${c}</span>`).join("");
|
||||
container.appendChild(more);
|
||||
|
||||
el.dataset.fetched = true;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Error fetching entry content:", err);
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// ===================================
|
||||
|
||||
// Select a language based on the page URL.
|
||||
let dict = localStorage.dict || defaultLang;
|
||||
const uri = /(dictionary)\/((.+?)\/(.+?))\//i.exec(document.location.href);
|
||||
if (uri && uri.length == 5) {
|
||||
dict = uri[2];
|
||||
}
|
||||
|
||||
selectDict(dict);
|
||||
elQ.focus();
|
||||
elQ.select();
|
||||
})();
|
||||
|
||||
// Screenshot sharing.
|
||||
(() => {
|
||||
document.querySelectorAll("a.export").forEach((el) => {
|
||||
el.onclick = async (e) => {
|
||||
e.preventDefault();
|
||||
const guid = el.dataset.guid;
|
||||
const entryEl = document.querySelector(`.entry[data-guid='${guid}']`);
|
||||
if (!entryEl) {
|
||||
alert("Could not find entry to export");
|
||||
return;
|
||||
}
|
||||
|
||||
const title = entryEl.dataset.head;
|
||||
// Make the filename by stripping spaces from the head word(s).
|
||||
const filename = title.replace(/\s+/g, "_").toLowerCase();
|
||||
|
||||
try {
|
||||
await shareDOM(entryEl, `${title} meaning`, `${localStorage.from_lang} to ${localStorage.to_lang} meaning\n\n${window.location.href}`, `${filename}.png`);
|
||||
} catch (err) {
|
||||
console.error("Error sharing entry:", err);
|
||||
alert(`Error sharing entry: ${err?.message || err}`);
|
||||
}
|
||||
};
|
||||
});
|
||||
})();
|
||||
|
||||
// Play audio.
|
||||
(() => {
|
||||
document.querySelectorAll("a[data-audio]").forEach((el) => {
|
||||
el.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
const audio = new Audio(el.getAttribute("href"));
|
||||
audio.play().catch((err) => {
|
||||
console.error("error playing audio:", err);
|
||||
alert("error playing audio");
|
||||
});
|
||||
};
|
||||
});
|
||||
})();
|
||||
|
||||
|
||||
// Submission form.
|
||||
(() => {
|
||||
function filterTypes(e) {
|
||||
// Filter the types select field with elements that are supported by the language.
|
||||
const types = e.target.closest("fieldset").querySelector("select[name=relation_type]");
|
||||
types.querySelectorAll("option").forEach((o) => o.style.display = "none");
|
||||
types.querySelectorAll(`option[data-lang=${e.target.value}]`).forEach((o) => o.style.display = "block");
|
||||
types.selectedIndex = 1;
|
||||
}
|
||||
|
||||
|
||||
if (document.querySelector(".form-submit")) {
|
||||
document.querySelectorAll("select[name=relation_lang]").forEach((e) => {
|
||||
e.onchange = filterTypes;
|
||||
});
|
||||
|
||||
// +definition button.
|
||||
document.querySelector(".btn-add-relation").onclick = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (document.querySelectorAll(".add-relations li").length >= 20) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Clone and add a relation fieldset.
|
||||
const d = document.querySelector(".add-relations li").cloneNode(true);
|
||||
d.dataset.added = true
|
||||
d.querySelector("select[name=relation_lang]").onchange = filterTypes;
|
||||
document.querySelector(".add-relations").appendChild(d);
|
||||
|
||||
// Remove definition link.
|
||||
d.querySelector(".btn-remove-relation").onclick = (e) => {
|
||||
e.preventDefault();
|
||||
d.remove();
|
||||
};
|
||||
};
|
||||
}
|
||||
})();
|
||||
|
||||
// Edit form.
|
||||
(() => {
|
||||
document.querySelectorAll(".edit").forEach((o) => {
|
||||
o.onclick = ((e) => {
|
||||
e.preventDefault();
|
||||
const btn = e.target;
|
||||
|
||||
// Form is already open.
|
||||
if (btn.close) {
|
||||
btn.close();
|
||||
return;
|
||||
}
|
||||
|
||||
const form = document.querySelector(".form-comments").cloneNode(true);
|
||||
o.parentNode.appendChild(form);
|
||||
form.style.display = "block";
|
||||
|
||||
const txt = form.querySelector("textarea");
|
||||
txt.focus();
|
||||
txt.onkeydown = (e) => {
|
||||
if (e.key === "Escape" && txt.value === "") {
|
||||
btn.close();
|
||||
}
|
||||
};
|
||||
|
||||
btn.close = () => {
|
||||
btn.close = null;
|
||||
form.remove();
|
||||
};
|
||||
|
||||
// Handle form submission.
|
||||
form.onsubmit = () => {
|
||||
fetch(`${window._ROOT_URL}/api/submissions/comments`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
from_guid: btn.dataset.from,
|
||||
to_guid: btn.dataset.to,
|
||||
comments: txt.value
|
||||
})
|
||||
}).catch((err) => {
|
||||
alert(`Error submitting: ${err}`);
|
||||
});
|
||||
|
||||
alert(form.dataset.success);
|
||||
btn.close();
|
||||
};
|
||||
|
||||
form.querySelector("button.close").onclick = btn.close;
|
||||
});
|
||||
})
|
||||
})();
|
||||
|
||||
// Autocomplete.
|
||||
(() => {
|
||||
if(!autocomp) {
|
||||
return;
|
||||
}
|
||||
|
||||
const elForm = document.querySelector("form.search-form");
|
||||
const elQ = document.querySelector("#q");
|
||||
let debounce;
|
||||
|
||||
autocomp(elQ, {
|
||||
autoSelect: false,
|
||||
onQuery: async (val) => {
|
||||
const langCode = localStorage.from_lang;
|
||||
clearTimeout(debounce);
|
||||
return new Promise(resolve => {
|
||||
debounce = setTimeout(async () => {
|
||||
const response = await fetch(`${_ROOT_URL}/atl/${langCode}/${val.toLowerCase()}`);
|
||||
const data = await response.json();
|
||||
|
||||
const a = data.greedy_tokenized.map(item => item.word).slice(0, 3).sort((a, b) => a.length - b.length);
|
||||
const b = data.dictionary_suggestions.map(item => item.word).slice(0, 6).sort((a, b) => a.length - b.length);
|
||||
|
||||
debounce = null;
|
||||
resolve([...new Set(a.concat(b))]);
|
||||
}, 50);
|
||||
});
|
||||
},
|
||||
|
||||
onSelect: (val) => {
|
||||
// autocomp search isn't complete. Use the user's input instead of autocomp selection.
|
||||
if (val) {
|
||||
elQ.value = val;
|
||||
}
|
||||
|
||||
elForm.dispatchEvent(new Event("submit", { cancelable: true }));
|
||||
return elQ.value;
|
||||
}
|
||||
});
|
||||
})();
|
||||
1
static/search.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20.962" height="21" xmlns:v="https://vecta.io/nano"><path d="M14.508 8.857c0-1.565-.535-2.902-1.642-4.009s-2.444-1.642-4.009-1.642-2.902.535-4.009 1.642-1.642 2.444-1.642 4.009.535 2.902 1.642 4.009 2.444 1.642 4.009 1.642 2.902-.535 4.009-1.642c1.069-1.145 1.642-2.482 1.642-4.009zm6.452 10.499c0 .42-.153.802-.496 1.145s-.687.496-1.145.496a1.45 1.45 0 0 1-1.145-.496l-4.276-4.353c-1.527 1.069-3.207 1.565-5.04 1.565a8.63 8.63 0 0 1-3.436-.687c-1.107-.458-2.062-1.107-2.825-1.909a10.51 10.51 0 0 1-1.909-2.825C.229 11.186 0 10.041 0 8.857S.229 6.49.687 5.421c.458-1.107 1.107-2.062 1.909-2.825A10.51 10.51 0 0 1 5.421.687C6.528.229 7.674 0 8.857 0s2.367.229 3.436.687c1.107.458 2.062 1.107 2.825 1.909a10.51 10.51 0 0 1 1.909 2.825c.458 1.107.687 2.253.687 3.436 0 1.833-.535 3.513-1.565 5.04l4.314 4.314a1.45 1.45 0 0 1 .496 1.145z" fill="#fff"/></svg>
|
||||
|
After Width: | Height: | Size: 901 B |
265
static/share.js
Normal file
@@ -0,0 +1,265 @@
|
||||
async function screenshotDOM(el, opts = {}) {
|
||||
const {
|
||||
scale = 2,
|
||||
type = 'image/png',
|
||||
quality = 0.92,
|
||||
background = '#fff',
|
||||
// if you want to *allow* splitting (and risk the bug), set to false
|
||||
preventCapsuleSplit = true
|
||||
} = opts;
|
||||
|
||||
if (!(el instanceof Element)) throw new TypeError('Expected a DOM Element');
|
||||
|
||||
if (document.fonts && document.fonts.status !== 'loaded') {
|
||||
try { await document.fonts.ready; } catch { }
|
||||
}
|
||||
|
||||
const { width, height } = (() => {
|
||||
const r = el.getBoundingClientRect();
|
||||
return { width: Math.ceil(r.width), height: Math.ceil(r.height) };
|
||||
})();
|
||||
if (!width || !height) throw new Error('Element has zero width/height.');
|
||||
|
||||
const clone = el.cloneNode(true);
|
||||
await inlineEverything(el, clone);
|
||||
|
||||
if (background !== 'transparent') clone.style.background = background;
|
||||
if (!clone.getAttribute('xmlns')) clone.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml');
|
||||
|
||||
const xhtml = new XMLSerializer().serializeToString(clone);
|
||||
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}">
|
||||
<foreignObject x="0" y="0" width="100%" height="100%">${xhtml}</foreignObject>
|
||||
</svg>`;
|
||||
|
||||
const svgBlob = new Blob([svg], { type: 'image/svg+xml;charset=utf-8' });
|
||||
const url = URL.createObjectURL(svgBlob);
|
||||
|
||||
try {
|
||||
const img = await loadImage(url);
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = Math.ceil(width * scale);
|
||||
canvas.height = Math.ceil(height * scale);
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (background !== 'transparent' || type !== 'image/png') {
|
||||
ctx.fillStyle = background === 'transparent' ? '#0000' : background;
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
}
|
||||
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||
const blob = await new Promise(res => canvas.toBlob(res, type, quality));
|
||||
if (!blob) throw new Error('Canvas.toBlob returned null.');
|
||||
return blob;
|
||||
} finally {
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
function loadImage(src) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.crossOrigin = 'anonymous';
|
||||
img.decoding = 'async';
|
||||
img.onload = () => resolve(img);
|
||||
img.onerror = reject;
|
||||
img.src = src;
|
||||
});
|
||||
}
|
||||
|
||||
async function inlineEverything(srcNode, dstNode) {
|
||||
copyComputedStyle(srcNode, dstNode);
|
||||
|
||||
// Remove interactive states that may be captured on touch devices
|
||||
if (dstNode instanceof Element) {
|
||||
// Reset outline from :focus state
|
||||
dstNode.style.setProperty('outline', 'none', 'important');
|
||||
// Reset any pointer-events to ensure no hover states
|
||||
const cs = window.getComputedStyle(srcNode);
|
||||
// Only override cursor if it's pointer (indicating interactivity)
|
||||
if (cs.cursor === 'pointer') {
|
||||
dstNode.style.setProperty('cursor', 'default', 'important');
|
||||
}
|
||||
}
|
||||
|
||||
if (srcNode instanceof HTMLTextAreaElement) {
|
||||
dstNode.textContent = srcNode.value;
|
||||
} else if (srcNode instanceof HTMLInputElement) {
|
||||
dstNode.setAttribute('value', srcNode.value);
|
||||
if ((srcNode.type === 'checkbox' || srcNode.type === 'radio') && srcNode.checked) {
|
||||
dstNode.setAttribute('checked', '');
|
||||
}
|
||||
} else if (srcNode instanceof HTMLSelectElement) {
|
||||
const sel = Array.from(srcNode.options).filter(o => o.selected).map(o => o.value);
|
||||
Array.from(dstNode.options).forEach(o => (o.selected = sel.includes(o.value)));
|
||||
}
|
||||
|
||||
if (srcNode instanceof HTMLCanvasElement) {
|
||||
try {
|
||||
const dataURL = srcNode.toDataURL();
|
||||
const img = document.createElement('img');
|
||||
img.src = dataURL;
|
||||
copyBoxSizing(dstNode, img);
|
||||
dstNode.replaceWith(img);
|
||||
dstNode = img;
|
||||
} catch { }
|
||||
}
|
||||
|
||||
// Handle img elements to ensure they render properly
|
||||
if (srcNode instanceof HTMLImageElement && dstNode instanceof HTMLImageElement) {
|
||||
// Convert image to data URL for inlining
|
||||
if (srcNode.complete && srcNode.naturalWidth > 0) {
|
||||
try {
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
canvas.width = srcNode.naturalWidth;
|
||||
canvas.height = srcNode.naturalHeight;
|
||||
ctx.drawImage(srcNode, 0, 0);
|
||||
dstNode.src = canvas.toDataURL('image/png');
|
||||
// Also set width/height to ensure proper rendering
|
||||
const cs = window.getComputedStyle(srcNode);
|
||||
dstNode.style.width = cs.width;
|
||||
dstNode.style.height = cs.height;
|
||||
dstNode.style.objectFit = cs.objectFit;
|
||||
} catch (err) {
|
||||
// If cross-origin or other error, keep original src
|
||||
console.log('Could not inline image:', err);
|
||||
dstNode.src = srcNode.src;
|
||||
}
|
||||
} else {
|
||||
// Image not loaded or has no dimensions, use src as-is
|
||||
dstNode.src = srcNode.src || srcNode.getAttribute('src') || '';
|
||||
}
|
||||
// Remove alt text from being rendered
|
||||
dstNode.removeAttribute('alt');
|
||||
}
|
||||
|
||||
materializePseudo(srcNode, dstNode, '::before');
|
||||
materializePseudo(srcNode, dstNode, '::after');
|
||||
|
||||
const sKids = srcNode.childNodes;
|
||||
const dKids = dstNode.childNodes;
|
||||
for (let i = 0; i < sKids.length; i++) {
|
||||
const s = sKids[i], d = dKids[i];
|
||||
if (s && d && s.nodeType === 1 && d.nodeType === 1) await inlineEverything(s, d);
|
||||
}
|
||||
|
||||
function copyComputedStyle(src, dst) {
|
||||
const cs = window.getComputedStyle(src);
|
||||
let cssText = '';
|
||||
for (const prop of cs) cssText += `${prop}:${cs.getPropertyValue(prop)};`;
|
||||
dst.setAttribute('style', (dst.getAttribute('style') || '') + cssText);
|
||||
|
||||
// Always keep transform origin consistent
|
||||
if (cs.transformOrigin) dst.style.transformOrigin = cs.transformOrigin;
|
||||
|
||||
// Keep each line painting its own decoration (helps some engines)
|
||||
dst.style.setProperty('box-decoration-break', 'clone', 'important');
|
||||
dst.style.setProperty('-webkit-box-decoration-break', 'clone', 'important');
|
||||
|
||||
// --- CAPSULE FIX: prevent decorated inline from splitting across lines ---
|
||||
if (preventCapsuleSplit) {
|
||||
const hasBg = (cs.backgroundImage && cs.backgroundImage !== 'none') ||
|
||||
(cs.backgroundColor && cs.backgroundColor !== 'rgba(0, 0, 0, 0)' && cs.backgroundColor !== 'transparent');
|
||||
const hasRadius = ['borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomRightRadius', 'borderBottomLeftRadius']
|
||||
.some(k => parseFloat(cs[k]) > 0);
|
||||
const hasPad = ['paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft']
|
||||
.some(k => parseFloat(cs[k]) > 0);
|
||||
const isInlineLevel = cs.display.startsWith('inline');
|
||||
|
||||
if (isInlineLevel && (hasBg || hasRadius || hasPad)) {
|
||||
// Make it behave like a chip for capture: no internal wrapping
|
||||
dst.style.setProperty('display', 'inline-block', 'important');
|
||||
dst.style.setProperty('white-space', 'nowrap', 'important');
|
||||
// In case original allowed breaking long words, emulate by clipping
|
||||
if (cs.overflowWrap === 'break-word' || cs.wordBreak === 'break-all') {
|
||||
dst.style.setProperty('max-width', cs.maxWidth && cs.maxWidth !== 'none' ? cs.maxWidth : '100%', 'important');
|
||||
dst.style.setProperty('overflow', 'hidden', 'important');
|
||||
dst.style.setProperty('text-overflow', 'ellipsis', 'important');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function copyBoxSizing(src, dst) {
|
||||
const cs = window.getComputedStyle(src);
|
||||
dst.style.width = cs.width;
|
||||
dst.style.height = cs.height;
|
||||
dst.style.display = cs.display;
|
||||
dst.style.objectFit = 'contain';
|
||||
}
|
||||
|
||||
function materializePseudo(src, dst, which) {
|
||||
const ps = window.getComputedStyle(src, which);
|
||||
if (!ps || ps.content === '' || ps.content === 'none') return;
|
||||
const span = document.createElement('span');
|
||||
let cssText = '';
|
||||
for (const prop of ps) cssText += `${prop}:${ps.getPropertyValue(prop)};`;
|
||||
span.setAttribute('style', cssText);
|
||||
|
||||
const content = ps.content;
|
||||
const quoted = /^(['"]).*\1$/.test(content);
|
||||
if (quoted) span.textContent = content.slice(1, -1).replace(/\\n/g, '\n');
|
||||
else if (content.startsWith('attr(')) {
|
||||
const attrName = content.slice(5, -1).trim();
|
||||
span.textContent = src.getAttribute(attrName) || '';
|
||||
} else span.textContent = '';
|
||||
|
||||
if (which === '::before') dst.insertBefore(span, dst.firstChild);
|
||||
else dst.appendChild(span);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function shareDOM(target, title, text, filename) {
|
||||
try {
|
||||
// Remove any active/focus states before capturing
|
||||
// This is especially important on touch devices (Android)
|
||||
if (document.activeElement) {
|
||||
document.activeElement.blur();
|
||||
}
|
||||
|
||||
// Wait a brief moment for any :active states to clear
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// Make a crisp image (PNG keeps transparency; use JPEG for smaller files)
|
||||
const blob = await screenshotDOM(target, {
|
||||
scale: Math.max(2, window.devicePixelRatio || 2),
|
||||
type: 'image/png',
|
||||
background: 'white'
|
||||
});
|
||||
|
||||
const file = new File([blob], filename || 'share.png', {
|
||||
type: blob.type,
|
||||
lastModified: Date.now()
|
||||
});
|
||||
|
||||
// Web Share API with files (Android Chrome, iOS/iPadOS Safari 16+).
|
||||
if (navigator.canShare && navigator.canShare({ files: [file] })) {
|
||||
await navigator.share({ title, text, files: [file] });
|
||||
return;
|
||||
}
|
||||
|
||||
// Copy image to clipboard (desktop Chrome/Edge, some Android)
|
||||
if (navigator.clipboard && window.ClipboardItem) {
|
||||
try {
|
||||
await navigator.clipboard.write([new ClipboardItem({ [blob.type]: blob })]);
|
||||
alert('Screenshot copied to clipboard');
|
||||
return;
|
||||
} catch (clipErr) {
|
||||
// Clipboard write failed (common on Android), fall through to download
|
||||
console.log('Clipboard write not allowed, downloading instead:', clipErr);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: prompt a download
|
||||
const dlUrl = URL.createObjectURL(blob);
|
||||
const a = Object.assign(document.createElement('a'), {
|
||||
href: dlUrl,
|
||||
download: 'share.png'
|
||||
});
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
URL.revokeObjectURL(dlUrl);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
alert(`share failed: ${err?.message || err}`);
|
||||
} finally { }
|
||||
}
|
||||
741
static/style.css
Normal file
@@ -0,0 +1,741 @@
|
||||
:root {
|
||||
--primary: #111;
|
||||
--secondary: #333;
|
||||
--light: #666;
|
||||
--lighter: #aaa;
|
||||
--lightest: #eee;
|
||||
--lightestest: #f4f4f4;
|
||||
--white: #fff;
|
||||
--blue: #36c;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background: #fbfbfb;
|
||||
font-family: "Helvetica Neue", "Segoe UI", Helvetica, sans-serif;
|
||||
margin: 0;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
font-size: 1em;
|
||||
line-height: 1.3;
|
||||
height: 100%;
|
||||
min-width: 320px;
|
||||
}
|
||||
|
||||
:focus {
|
||||
outline-style: solid;
|
||||
outline-width: 2px;
|
||||
outline-color: var(--blue);
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
hr {
|
||||
height: 2px;
|
||||
border: 0;
|
||||
background: #ddd;
|
||||
display: block;
|
||||
margin: 30px 0;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5 {
|
||||
margin: 10px 0 10px 0;
|
||||
}
|
||||
|
||||
h4,
|
||||
h5 {
|
||||
margin: 0 0 10px 0;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--blue);
|
||||
}
|
||||
|
||||
input:not([type="radio"]),
|
||||
select,
|
||||
button,
|
||||
textarea,
|
||||
.button {
|
||||
padding: 10px 15px;
|
||||
font-family: "Helvetica Neue", "Segoe UI", Helvetica, sans-serif;
|
||||
font-size: 1.2rem;
|
||||
line-height: 1;
|
||||
color: var(--primary);
|
||||
border-radius: 5px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
select {
|
||||
background-color: var(--white);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
color: var(--light);
|
||||
}
|
||||
|
||||
button,
|
||||
.button {
|
||||
background: var(--primary);
|
||||
border-color: var(--primary);
|
||||
color: var(--white);
|
||||
width: auto;
|
||||
cursor: pointer;
|
||||
padding: 10px 20px;
|
||||
}
|
||||
|
||||
.button-outline {
|
||||
background: var(--white);
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.button-outline:hover {
|
||||
background: var(--lighter);
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
button:hover,
|
||||
.button:hover {
|
||||
border-color: #444;
|
||||
background: #444;
|
||||
}
|
||||
|
||||
.noul {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.text-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.box {
|
||||
box-shadow: 2px 2px 1px #f6f6f6;
|
||||
border: 1px solid #e6e6e6;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.loader {
|
||||
width: 16px;
|
||||
padding: 3px;
|
||||
aspect-ratio: 1;
|
||||
border-radius: 50%;
|
||||
background: #aaa;
|
||||
--_m:
|
||||
conic-gradient(#0000 10%, #000),
|
||||
linear-gradient(#000 0 0) content-box;
|
||||
-webkit-mask: var(--_m);
|
||||
mask: var(--_m);
|
||||
-webkit-mask-composite: source-out;
|
||||
mask-composite: subtract;
|
||||
animation: l3 1s infinite linear;
|
||||
}
|
||||
|
||||
@keyframes l3 {
|
||||
to {
|
||||
transform: rotate(1turn)
|
||||
}
|
||||
}
|
||||
|
||||
/* Custom */
|
||||
.main {
|
||||
margin: 30px auto 15px auto;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.home .container {
|
||||
max-width: 700px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.logo a {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.logo img {
|
||||
height: auto;
|
||||
max-height: 40px;
|
||||
}
|
||||
|
||||
.home .logo img {
|
||||
max-width: 80%;
|
||||
}
|
||||
|
||||
.intro {
|
||||
display: none;
|
||||
color: #444;
|
||||
margin: 30px 0 30px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.search-form>div {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.search-form .input-group {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.search-form input {
|
||||
flex-grow: 1;
|
||||
padding: 10px 15px;
|
||||
border-width: 1px 0px;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.search-form select {
|
||||
background-color: var(--white);
|
||||
padding: 5px 15px;
|
||||
border-radius: 30px 0 0 30px;
|
||||
color: var(--light)
|
||||
}
|
||||
|
||||
.search-form select:focus {
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.search-form button {
|
||||
line-height: 0;
|
||||
padding: 7px 15px 7px 10px;
|
||||
border-radius: 0 30px 30px 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.search-form button img {
|
||||
max-height: 32px;
|
||||
min-width: 13px;
|
||||
}
|
||||
|
||||
.page {
|
||||
margin: 30px 0 30px 0;
|
||||
}
|
||||
|
||||
.word {
|
||||
background: var(--lightest);
|
||||
padding: 0 5px;
|
||||
border-radius: 30px;
|
||||
margin: 0 5px 5px 0;
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
||||
.entries {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.entries .entry {
|
||||
background-color: var(--white);
|
||||
margin-bottom: 30px;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 2px 2px 1px #f6f6f6;
|
||||
border: 1px solid #e6e6e6;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.entries .head {
|
||||
position: relative;
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.entries .title {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.entries .title-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.entries .audio {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
transition: ease-in-out 0.035s;
|
||||
}
|
||||
.entries .audio:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.entries .pronun {
|
||||
color: var(--light);
|
||||
}
|
||||
|
||||
.entries .defs {
|
||||
padding: 0 0 0 30px;
|
||||
}
|
||||
|
||||
.entries .defs:not(:last-child) {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.entries li::marker {
|
||||
color: var(--lighter);
|
||||
}
|
||||
|
||||
.entries .defs li {
|
||||
margin-bottom: 5px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.entries .defs li::marker {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.entries .defs .more {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.entries .defs .synonyms {
|
||||
margin: 5px 0 10px 0;
|
||||
}
|
||||
|
||||
.entries .defs .synonyms a {
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
color: var(--light);
|
||||
font-size: 0.775rem;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.entries .defs .synonyms a:hover {
|
||||
color: var(--blue);
|
||||
}
|
||||
|
||||
.entries .more-toggle {
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
transform: translate(0, 3px);
|
||||
transition: transform 0.15s ease;
|
||||
position: absolute;
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.entries .more-toggle::before {
|
||||
width: 1em;
|
||||
line-height: 0;
|
||||
content: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='rgba%280,0,0,.5%29' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");
|
||||
transform-origin: .5em 50%;
|
||||
}
|
||||
|
||||
.entries .more-toggle[data-open] {
|
||||
transform: rotate(-180deg) translate(0, 3px);
|
||||
}
|
||||
|
||||
.entries .defs .types {
|
||||
list-style-type: none;
|
||||
display: block;
|
||||
margin: 0 0 10px -15px;
|
||||
color: var(--light);
|
||||
text-decoration: underline;
|
||||
text-decoration-style: dashed;
|
||||
text-decoration-color: var(--lighter);
|
||||
font-size: 0.675rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.entries .defs .types span {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.entry .edit {
|
||||
padding: 3px;
|
||||
display: none;
|
||||
text-decoration: none;
|
||||
z-index: 1000;
|
||||
line-height: 1;
|
||||
text-shadow: 0px 3px 3px #fff;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.entries .defs li:hover .edit {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.entry .head:hover .edit {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.entries .tags {
|
||||
display: inline-block;
|
||||
background-color: var(--lightestest);
|
||||
border: 1px solid var(--lightest);
|
||||
border-radius: 3px;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1;
|
||||
padding: 2px 5px;
|
||||
color: var(--lighter);
|
||||
}
|
||||
.entries .meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.related .word {
|
||||
margin: 0 5px 8px 0;
|
||||
padding: 5px 10px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.form-submit fieldset {
|
||||
padding: 0;
|
||||
margin: 0 0 20px 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.form-submit select {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.form-submit select,
|
||||
.form-submit textarea {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form-submit li {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-submit li:first-child .btn-remove-relation {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.form-submit input,
|
||||
.form-submit textarea {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form-comments {
|
||||
margin: 10px 0 45px 0;
|
||||
display: none;
|
||||
padding: 15px;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.form-comments textarea {
|
||||
width: 100%;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.form-comments h3 {
|
||||
margin: 0 0 15px 0;
|
||||
}
|
||||
|
||||
.form-comments button {
|
||||
margin: 0 10px 10px 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.glossary .index {
|
||||
background: #f7f7f7;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.glossary .index a {
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.glossary .index .sel,
|
||||
.glossary .index a:hover {
|
||||
background: var(--primary);
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
.glossary .words {
|
||||
/* column-count: auto;
|
||||
column-gap: 0; */
|
||||
column-count: 3;
|
||||
column-gap: 40px;
|
||||
}
|
||||
|
||||
.glossary .words li {
|
||||
border-bottom: 1px solid #eee;
|
||||
page-break-inside: avoid;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.glossary .words a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.pagination a {
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.pagination .pg-page {
|
||||
padding: 0 6px;
|
||||
}
|
||||
|
||||
.pagination .pg-selected {
|
||||
font-weight: bold;
|
||||
border-bottom: 2px solid var(--primary);
|
||||
}
|
||||
|
||||
.pagination.top {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.pagination.bottom {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
/* Custom pages */
|
||||
.page.type textarea {
|
||||
width: 100%;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.page {
|
||||
background-color: #fff;
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.page li {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.page a {
|
||||
color: var(--blue);
|
||||
}
|
||||
|
||||
.page a:hover {
|
||||
color: var(--secondary);
|
||||
}
|
||||
|
||||
.nav {
|
||||
border-bottom: 1px solid #ddd;
|
||||
padding: 30px 0;
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.nav a {
|
||||
text-decoration: none;
|
||||
color: var(--light);
|
||||
margin: 0 15px;
|
||||
}
|
||||
|
||||
.nav a:hover {
|
||||
color: var(--blue);
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding-bottom: 30px;
|
||||
text-align: center;
|
||||
font-size: 0.75rem;
|
||||
color: var(--light);
|
||||
}
|
||||
|
||||
.footer a {
|
||||
color: var(--light);
|
||||
display: inline-block;
|
||||
margin: 0 10px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.footer a:hover {
|
||||
color: var(--blue);
|
||||
}
|
||||
|
||||
.footer img {
|
||||
max-height: 9px;
|
||||
}
|
||||
|
||||
.footer .slash {
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.footer .credit img {
|
||||
height: 11px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.footer .credit:hover img {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Homepage */
|
||||
.home {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
min-height: 100%;
|
||||
padding: 15px 0;
|
||||
}
|
||||
|
||||
.home .header {
|
||||
text-align: center;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.home .intro {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.home .logo {
|
||||
width: 100%;
|
||||
flex: none;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.home .logo img {
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.home .search {
|
||||
width: 100%;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
/* Autocomplete */
|
||||
.autocomp {
|
||||
background: #f8f8f8;
|
||||
border-radius: 0 0 5px 5px;
|
||||
border: 1px solid #ccc;
|
||||
border-top: 0;
|
||||
box-shadow: 2px 2px 2px #eee;
|
||||
text-align: left;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.autocomp-item {
|
||||
padding-bottom: 5px;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.autocomp-item:hover,
|
||||
.autocomp-sel {
|
||||
background: #f1f1f1;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
.search-form select {
|
||||
min-width: 0;
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
.search-form .input-group {
|
||||
min-width: 0;
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
.search-form input {
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 720px) {
|
||||
.logo {
|
||||
text-align: center;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.logo img {
|
||||
max-height: 42px;
|
||||
}
|
||||
|
||||
.main {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.footer a, .nav a {
|
||||
display: block;
|
||||
line-height: 1.4rem;
|
||||
}
|
||||
|
||||
.footer a.icon {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 320px) {
|
||||
.search-form>div {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-form select {
|
||||
flex-basis: 100%;
|
||||
border-radius: 30px;
|
||||
padding: 10px 15px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.search-form .input-group {
|
||||
flex-basis: 100%;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.search-form input {
|
||||
border-width: 1px 0 1px 1px;
|
||||
border-radius: 30px 0 0 30px;
|
||||
padding: 10px 15px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.entries .entry {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.entries .defs:not(:last-child) {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
}
|
||||
BIN
static/thumb.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
1
static/up.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="6.857" viewBox="0 0 0.36 0.206" fill="none" xmlns:v="https://vecta.io/nano"><path d="M.026.18L.18.026.334.18" stroke="#000" stroke-width=".051" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
|
After Width: | Height: | Size: 257 B |
BIN
static/vkrishna.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
1
static/zerodha-logo.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" baseProfile="tiny" width="609" height="80" xmlns:v="https://vecta.io/nano"><path d="M66.252 21.469c4.404 5.71 8.056 12.124 10.886 19.04V3.931H46.077c7.472 4.037 14.317 9.943 20.175 17.538zM21.473 7.828c-5.754 0-11.289 1.23-16.473 3.506v64.735h68.963c-.534-37.754-23.875-68.241-52.49-68.241" fill-rule="evenodd" fill="#387ed1"/><path fill="#387ed1" d="M118.349 64.918l38.533-46.65H119.56V8.098h52.25v8.49l-38.533 46.651h38.533v10.169h-53.461v-8.49zm67.828-56.82h48.331v10.356H197.56v16.794h32.751v10.357H197.56v17.446h37.413v10.356h-48.796V8.098zm62.512 0h29.11c4.105 0 7.744.576 10.917 1.726s5.814 2.752 7.93 4.805c1.741 1.805 3.08 3.888 4.013 6.251s1.398 4.977 1.398 7.837v.187c0 2.675-.388 5.085-1.165 7.231s-1.853 4.044-3.22 5.691-3.001 3.049-4.898 4.198-3.997 2.038-6.297 2.659l17.633 24.725h-13.529l-16.097-22.765h-.186-14.227v22.765h-11.383V8.098zm28.231 32.375c4.115 0 7.393-.982 9.824-2.951s3.652-4.639 3.652-8.012v-.187c0-3.56-1.188-6.263-3.559-8.106s-5.709-2.764-10.014-2.764h-16.752v22.019h16.849zm70.389 34.055c-5.039 0-9.641-.886-13.807-2.658s-7.744-4.182-10.73-7.232-5.318-6.607-6.996-10.682-2.521-8.413-2.521-13.016v-.187c0-4.603.84-8.941 2.521-13.016s4.041-7.651 7.09-10.73 6.654-5.52 10.824-7.324 8.768-2.706 13.807-2.706 9.641.886 13.809 2.659 7.744 4.184 10.73 7.231 5.316 6.609 6.996 10.683 2.521 8.413 2.521 13.015v.187c0 4.604-.84 8.943-2.521 13.015s-4.043 7.651-7.09 10.73-6.656 5.521-10.824 7.324-8.77 2.707-13.809 2.707zm.187-10.543c3.221 0 6.178-.606 8.871-1.819s5.002-2.86 6.922-4.944 3.422-4.526 4.506-7.325 1.625-5.785 1.625-8.956v-.187c0-3.172-.541-6.173-1.625-9.004s-2.604-5.286-4.553-7.37-4.289-3.747-7.016-4.992-5.697-1.866-8.918-1.866-6.178.606-8.871 1.819-5.002 2.862-6.922 4.945-3.422 4.525-4.508 7.324-1.625 5.785-1.625 8.957v.187c0 3.172.541 6.173 1.625 9.002s2.604 5.289 4.555 7.372 4.289 3.748 7.014 4.991 5.699 1.866 8.92 1.866zm47.955-55.887h24.354c5.1 0 9.779.825 14.043 2.473s7.928 3.934 11.008 6.857 5.457 6.361 7.139 10.311 2.52 8.227 2.52 12.829v.187c0 4.604-.84 8.896-2.52 12.875s-4.059 7.435-7.139 10.356-6.748 5.226-11.008 6.904-8.943 2.519-14.043 2.519h-24.354V8.098zm24.26 54.954c3.42 0 6.531-.545 9.33-1.639s5.18-2.622 7.137-4.589 3.482-4.312 4.572-7.03 1.635-5.667 1.635-8.854v-.187c0-3.186-.547-6.152-1.635-8.9s-2.613-5.106-4.572-7.074-4.338-3.514-7.137-4.639-5.91-1.687-9.33-1.687h-12.877v44.598h12.877zm48.703-54.954h11.383v27.151h31.352V8.098h11.383v65.311h-11.383V45.885h-31.352v27.523h-11.383V8.098zm94.049-.467H573.1l28.736 65.777h-12.129l-6.625-15.768h-30.883l-6.719 15.768h-11.756l28.739-65.777zm16.422 39.841l-11.291-26.125-11.197 26.125h22.488z"/></svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |