First commit.

This commit is contained in:
2025-11-30 14:11:42 +05:30
commit a2fda5eb97
30 changed files with 2244 additions and 0 deletions

55
static/alar.js Normal file
View 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
View 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
View 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
View 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
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 852 B

135
static/flexit.css Normal file
View 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

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

1
static/logo.svg Normal file
View 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
View 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
View 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
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

1
static/up.svg Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

1
static/zerodha-logo.svg Normal file
View 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