Guest User

Back

a guest
Aug 8th, 2025
25
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 23.25 KB | None | 0 0
  1. <!---------- Header ------------->
  2.  
  3. <main lang="ja">
  4. <div class="template">
  5.  
  6. <!-- The first row (vocab box+picture) -->
  7. <div class="def-header">
  8. <div class="dh-left">
  9. <div class="show-furigana vocab">
  10. <ruby>{{Word}}<rt>{{Reading}}</rt></ruby>
  11. <div style='font-family: "Arial"; font-size: 20px;'>{{Pitch Graph}}</div>
  12. </div>
  13.  
  14. <!-- Pitch Accent -->
  15. {{#Pitch Position}}
  16. <span id="pitch-tags" class="tags"> {{Pitch Position}} </span>
  17. {{/Pitch Position}}
  18. </div>
  19. </div>
  20.  
  21. <!-- The entire definition blockquote -->
  22. <div class="def-info-container">
  23. <div class="def-info"></div>
  24. </div>
  25. <blockquote class="main-def def-blockquote">
  26. {{#Definition Picture}}
  27. <div class="def-image tappable">{{Definition Picture}}</div>
  28. {{/Definition Picture}}
  29. <div class="definition">
  30.  
  31. <div id="selection" data-display-name="Text Selection"></div>
  32.  
  33. {{#Definition}}
  34. <div id="primary" data-display-name="Primary Definition">{{Definition}}</div>
  35. {{/Definition}}
  36. <div id="glossaries" data-display-name="Glossaries"></div>
  37. </div>
  38. </blockquote>
  39. <!-- Image (MODIFIED)-->
  40. <div id="main_image" class="{{Tags}}">
  41. <a onclick="toggleNsfw()">{{Picture}}</a>
  42. </div>
  43. <hr>
  44. <!-- Sentence Moved (MODIFIED)-->
  45. <div class="sentence">
  46. {{furigana:Sentence}}
  47. </div>
  48.  
  49. <!-- This is for the sentence that you see on mobile (positioned under definition), on Desktop, the sentence goes above the definition box, and this is hidden -->
  50. <div class="sentence-mobile">
  51. {{furigana:Sentence}}
  52. </div>
  53.  
  54. <!-- Audio Buttons Moved (MODIFIED)-->
  55. <br />
  56. <div class="audio-buttons">{{Word Audio}} {{Sentence Audio}}</div>
  57.  
  58. <!------- Image modal --------->
  59. <div class="modal-bg tappable">
  60. <div class="img-popup"></div>
  61. </div>
  62.  
  63. <div style="text-align: center">
  64. <!----------- Scripts ------------>
  65. <script>
  66. // This code is concerned with calculating the Pitch Accent and constructing the pitch accent graphs
  67. function isOdaka(pitchNumber) {
  68. const kana = `{{kana:Reading}}` || `{{Pitch Graph}}`;
  69. return (
  70. kana !== null &&
  71. kana.replace(/[ァィゥェォャュョヮぁぃぅぇぉゃゅょゎ]/g, "").length === pitchNumber
  72. );
  73. }
  74.  
  75. function getPitchCategories() {
  76. const validTypes = "(heiban|atamadaka|nakadaka|odaka|kifuku)";
  77. return [...``.matchAll(validTypes)].map(m => m[0]);
  78. }
  79.  
  80. function hasVerbOrAdjEnding() {
  81. const endings = ["い", "う", "く", "す", "つ", "ぶ", "む", "る"];
  82. return endings.some(ending => `{{Word}}`.replace("</div>","").endsWith(ending));
  83. }
  84.  
  85. function getPitchType(pitchPosition) {
  86. const pitchCategories = getPitchCategories();
  87. const kifukuTags = ["adj-i", "v1", "v2", "v4", "v5", "vs-", "vz", "vk", "vn", "vr"];
  88. let canBeKifuku = pitchCategories.includes("kifuku");
  89. canBeKifuku ||= kifukuTags.some(tag => ``.includes(tag));
  90. if (canBeKifuku || (pitchCategories.length == 0 && hasVerbOrAdjEnding())) {
  91. return pitchPosition === 0 ? "heiban" : "kifuku";
  92. }
  93.  
  94. if (pitchPosition === 0) {
  95. return "heiban";
  96. } else if (pitchPosition === 1) {
  97. return "atamadaka";
  98. } else if (pitchPosition > 1) {
  99. return isOdaka(pitchPosition) ? "odaka" : "nakadaka";
  100. }
  101. }
  102.  
  103. // Show the color
  104. function paintTargetWord() {
  105. const pitchPositions = `{{Pitch Position}}`.match(/^\d+|\d+\b|\d+(?=\w)/g);
  106. if (pitchPositions === null) return;
  107.  
  108. const pitchType = getPitchType(Number(pitchPositions[0]));
  109. const sentences = Array.from(
  110. document.querySelectorAll(".sentence, .definition, .sentence-mobile"),
  111. );
  112. for (const sentence of sentences) {
  113. for (const targetWord of sentence.getElementsByTagName("b")) {
  114. targetWord.classList.add(pitchType);
  115. }
  116. }
  117.  
  118. const vocabElement = document.querySelector(".vocab");
  119. if (vocabElement !== null) {
  120. vocabElement.classList.add(pitchType);
  121. }
  122. }
  123.  
  124. // Seperate Tags by space, and show them in their own boxes
  125. function tweakHTML() {
  126. // Split tags
  127. const tagsContainer = document.querySelector(".tags-container");
  128. const tags = `{{Tags}}`.split(" ");
  129. if (tagsContainer) {
  130. tagsContainer.innerHTML = "";
  131. for (tag of tags) {
  132. const tagElem = document.createElement("div");
  133. tagElem.className = "tags";
  134. tagElem.innerText = tag;
  135. tagsContainer.appendChild(tagElem);
  136. }
  137. }
  138. }
  139.  
  140. function groupMoras(kana) {
  141. let currentChar = "";
  142. let nextChar = "";
  143. const groupedMoras = [];
  144. const check = ["ァ", "ィ", "ゥ", "ェ", "ォ", "ャ", "ュ", "ョ", "ヮ", "ぁ", "ぃ", "ぅ", "ぇ", "ぉ", "ゃ", "ゅ", "ょ", "ゎ"];
  145.  
  146. for (let i = 0; i < kana.length; i++) {
  147. currentChar = kana[i];
  148. nextChar = i < kana.length - 1 && kana[i + 1];
  149. if (check.includes(nextChar)) {
  150. groupedMoras.push(currentChar + nextChar);
  151. i += 1;
  152. } else {
  153. groupedMoras.push(currentChar);
  154. }
  155. }
  156. return groupedMoras;
  157. }
  158.  
  159. function getPitchPattern(pitchPosition) {
  160. // 0 = low
  161. // 1 = high
  162. // 2 = high to low
  163.  
  164. const kana = `{{kana:Reading}}` || `{{Pitch Graph}}`;
  165. const moras = groupMoras(kana);
  166. let pattern = [];
  167.  
  168. if (pitchPosition === 0) {
  169. // 平板
  170. pattern = [
  171. ...Array(moras[0].length).fill("0"),
  172. ...Array(kana.length - moras[0].length).fill("1"),
  173. ];
  174. } else if (pitchPosition === 1) {
  175. // 頭高
  176. pattern = [
  177. ...(moras[0].length === 2 ? ["1", "2"] : ["2"]),
  178. ...Array(kana.length - moras[0].length).fill("0"),
  179. ];
  180. } else if (pitchPosition > 1) {
  181. if (isOdaka(pitchPosition)) {
  182. // 尾高
  183. pattern = [
  184. ...Array(moras[0].length).fill("0"),
  185. ...Array(kana.length - moras[0].length - 1).fill("1"),
  186. "2",
  187. ];
  188. } else {
  189. // 中高
  190. let afterDrop = false;
  191. for (let i = 0; i < moras.length; i++) {
  192. if (i === 0) {
  193. pattern = Array(moras[0].length).fill("0");
  194. } else if (i + 1 === pitchPosition) {
  195. pattern =
  196. moras[i].length === 2
  197. ? [...pattern, "1", "2"]
  198. : [...pattern, "2"];
  199. afterDrop = true;
  200. } else if (afterDrop) {
  201. pattern = [...pattern, ...Array(moras[i].length).fill("0")];
  202. } else {
  203. pattern = [...pattern, ...Array(moras[i].length).fill("1")];
  204. }
  205. }
  206. }
  207. }
  208. return pattern;
  209. }
  210.  
  211. function constructPitch() {
  212. const pitchPositions = `{{Pitch Position}}`.match(/^\d+|\d+\b|\d+(?=\w)/g);
  213. if (!pitchPositions) return;
  214.  
  215. const kana = `{{kana:Reading}}` || `{{Pitch Graph}}`;
  216. const pitch = document.querySelector(".pitch");
  217. const pitchTags = document.querySelector("#pitch-tags");
  218.  
  219. const createPitchSpan = (pitchClass, pitchChar) => {
  220. const pitchSpan = document.createElement("span");
  221. const charSpan = document.createElement("span");
  222. const lineSpan = document.createElement("span");
  223.  
  224. pitchSpan.classList.add(pitchClass);
  225. charSpan.classList.add("pitch-char");
  226. charSpan.innerText = pitchChar;
  227. lineSpan.classList.add("pitch-line");
  228.  
  229. pitchSpan.appendChild(charSpan);
  230. pitchSpan.appendChild(lineSpan);
  231.  
  232. return pitchSpan;
  233. };
  234.  
  235. pitch.innerHTML = "";
  236. pitchTags.innerHTML = "";
  237. pitchTags.style.display = "inline-block";
  238. let uniquePitchPositions = [...new Set(pitchPositions)];
  239.  
  240. const pitchList = document.createElement("ul");
  241. const pitchTagList = document.createElement("ul");
  242.  
  243. for (let pitchPosition of uniquePitchPositions) {
  244. const pitchTag = document.createElement("li");
  245. pitchTag.textContent = pitchPosition;
  246.  
  247. const pattern = getPitchPattern(Number(pitchPosition));
  248.  
  249. const pitchItem = document.createElement("li");
  250. pitchItem.classList.add("pitch-item");
  251. pitchItem.classList.add(getPitchType(Number(pitchPosition)));
  252.  
  253. for (let i = 0; i < kana.length; i++) {
  254. if (pattern[i] === "0")
  255. pitchItem.appendChild(createPitchSpan("pitch-low", kana[i]));
  256. else if (pattern[i] === "1")
  257. pitchItem.appendChild(createPitchSpan("pitch-high", kana[i]));
  258. else if (pattern[i] === "2")
  259. pitchItem.appendChild(createPitchSpan("pitch-to-drop", kana[i]));
  260. else
  261. console.error(
  262. "pattern[i] found undefined value. pattern is",
  263. pattern,
  264. );
  265. }
  266. pitchTagList.appendChild(pitchTag);
  267. pitchList.appendChild(pitchItem);
  268. }
  269.  
  270. pitch.appendChild(pitchList);
  271. pitchTags.appendChild(pitchTagList);
  272. }
  273.  
  274. // Returns the dictionary content, without the dictionary name.
  275. function getDictionaryContent(dictionarySelector) {
  276. const dictionary = document.querySelector(dictionarySelector);
  277. if (!dictionary) return null;
  278. const contentInSpan = dictionary.querySelector(":scope > span");
  279. if (contentInSpan) return contentInSpan;
  280.  
  281. const hasDictName = document.querySelector(":scope > i");
  282. if (!hasDictName) return dictionary;
  283.  
  284. let dictionaryCopy = dictionary.cloneNode(true);
  285. dictName = dictionaryCopy.querySelector(":scope > i");
  286. dictName.remove();
  287. return dictionaryCopy;
  288. }
  289.  
  290. function isPrimaryEqualToGloss() {
  291. const isJPMNConverted = document.querySelector(".definition li[data-details]");
  292. if (isJPMNConverted) return false;
  293. // single dict formatting
  294. const isSingleDict = document.querySelectorAll("#glossaries > div > ol").length === 0;
  295. if (isSingleDict) {
  296. const primaryDictName = document.querySelector("#primary > div > i");
  297. const glossariesDictName = document.querySelector("#glossaries > div > i");
  298. // Compare dicts names if present
  299. if (primaryDictName && glossariesDictName) {
  300. return primaryDictName.textContent === glossariesDictName.textContent;
  301. }
  302. // Compare content otherwise
  303. const primaryDict = getDictionaryContent("#primary > div");
  304. const glossariesDict = getDictionaryContent("#glossaries > div");
  305. if (!primaryDict || !glossariesDict ) return false;
  306. return primaryDict.innerHTML.trim() === glossariesDict.innerHTML.trim();
  307. }
  308.  
  309. // multiple dicts
  310. const primaryDicts = document.querySelectorAll("#primary li[data-dictionary]");
  311. const glossariesDicts = document.querySelectorAll("#glossaries li[data-dictionary]");
  312. return primaryDicts.length === glossariesDicts.length
  313. }
  314.  
  315. // Removes Unnecessary definitions
  316. function cleanUpDefinitions() {
  317. let selection = document.getElementById("selection");
  318. let primary = document.getElementById("primary");
  319. let glossaries = document.getElementById("glossaries");
  320. if (selection && selection.textContent === "") {
  321. selection.remove();
  322. }
  323. if (primary && primary.textContent === "") {
  324. primary.remove();
  325. primary = null;
  326. }
  327. if (glossaries && glossaries.textContent === "") {
  328. glossaries.remove();
  329. glossaries = null;
  330. }
  331. else if (primary && glossaries && isPrimaryEqualToGloss()) {
  332. glossaries.remove();
  333. }
  334. }
  335.  
  336. // Display definition corresponding to index
  337. function updateDefDisplay() {
  338. const definitions = document.querySelectorAll(
  339. ".main-def > .definition > div"
  340. );
  341.  
  342. let n_defs = definitions.length;
  343. if (n_defs === 1) definitions[0].classList.remove("hidden");
  344. if (n_defs <= 1) return;
  345.  
  346. let currentIndex = document.head.getAttribute("data-def-index");
  347. currentIndex = currentIndex % n_defs;
  348. while (currentIndex < 0) currentIndex += n_defs;
  349.  
  350. for (let idx = 0; idx < n_defs; idx++) {
  351. definitions[idx].classList.add("hidden");
  352. }
  353. definitions[currentIndex].classList.remove("hidden");
  354.  
  355. const defDisplayName = definitions[currentIndex].getAttribute("data-display-name")
  356. const indexDisplay = document.querySelector(".def-info");
  357. indexDisplay.style.opacity = 1;
  358. indexDisplay.innerText = `${defDisplayName} ${currentIndex + 1}/${n_defs}`;
  359. }
  360.  
  361. function setUpDefToggle() {
  362. document.head.setAttribute("data-def-index", 0);
  363. cleanUpDefinitions();
  364.  
  365. // hide all but first definition
  366. let definitions = document.querySelectorAll(".main-def > .definition > div");
  367. Array.from(definitions).slice(1).forEach(def => { def.classList.add("hidden"); });
  368. // no need for toggling on less than 2 definitions
  369. if (definitions.length < 2) return;
  370.  
  371. let mainDefContainer = document.querySelector(".main-def");
  372. const leftEdge = document.createElement("div");
  373. const rightEdge = document.createElement("div");
  374. leftEdge.classList.add("left-edge");
  375. leftEdge.classList.add("tappable");
  376. rightEdge.classList.add("right-edge");
  377. rightEdge.classList.add("tappable");
  378. mainDefContainer.appendChild(leftEdge);
  379. mainDefContainer.appendChild(rightEdge);
  380.  
  381. const changeIndex = (value) => {
  382. // sync index between clicks and arrowkeys
  383. index = Number(document.head.getAttribute("data-def-index"));
  384. index += value;
  385. document.head.setAttribute("data-def-index", index);
  386. updateDefDisplay();
  387. };
  388.  
  389. leftEdge.addEventListener("click", (e) => changeIndex(-1));
  390. rightEdge.addEventListener("click", (e) => changeIndex(1));
  391.  
  392. // Add key listener only once per session
  393. if (document.head.classList.contains("has-listener")) return;
  394. document.addEventListener("keydown", (e) => {
  395. if (e.key === "ArrowLeft") changeIndex(-1);
  396. else if (e.key === "ArrowRight") changeIndex(1);
  397. });
  398.  
  399. document.head.classList.add("has-listener");
  400. }
  401.  
  402.  
  403. // Format plaintext frequencies into a list
  404. function formatFrequencyList() {
  405. const frequency = document.querySelector('.freq-list-container');
  406. if (!frequency) return;
  407. const frequencyList = frequency.querySelector('ul');
  408. // Already a list; nothing to do
  409. if (frequencyList) return;
  410.  
  411. const freqs = frequency.innerText.split(',');
  412. const freqHtml = `<ul>${freqs.map(freq => `<li>${freq.trim()}</li>`).join('')}</ul>`
  413. frequency.innerHTML = freqHtml;
  414. }
  415.  
  416. // Sets the height of dhLeft, dhRight, defHeader as a whole
  417. function setDHHeight() {
  418. var dhLeft = document.querySelector('.dh-left');
  419. var dhRight = document.querySelector('.dh-right .image img');
  420. var defHeader = document.querySelector('.def-header')
  421.  
  422. if (dhLeft && dhRight) {
  423. var dhLeftHeight = dhLeft.offsetHeight;
  424. dhRight.style.maxHeight = `${dhLeftHeight}px`;
  425. defHeader.style.maxHeight = `${dhLeftHeight}px`;
  426. }
  427. }
  428.  
  429. // Hides the dictionaries user selected in MainDefinition in Glossary field, if any
  430. function hideCorrectDefinition() {
  431. // Do nothing if css rule already exists
  432. if (document.querySelector("style#hide-main-def")) return;
  433.  
  434. let primaryDicts = document.querySelectorAll("#primary li[data-dictionary]");
  435. if (primaryDicts.length === 0) return;
  436.  
  437. let style = document.createElement('style');
  438. style.type = 'text/css';
  439. style.id = "hide-main-def";
  440.  
  441. const cssSelector = Array.from(primaryDicts).map((dict) =>
  442. `#glossaries li[data-dictionary="${dict.getAttribute("data-dictionary")}"]`
  443. ).join(", ");
  444. const cssRules = `${cssSelector} { display:none !important; }`;
  445. style.appendChild(document.createTextNode(cssRules));
  446.  
  447. let defContainer = document.querySelector("blockquote.main-def");
  448. defContainer.appendChild(style);
  449. }
  450.  
  451. // Moves Primary Dicts into the same list
  452. function movePrimaryDicts() {
  453. let primaryDicts = document.querySelectorAll("#primary li[data-dictionary]");
  454. let firstList = document.querySelector("#primary .yomitan-glossary > ol:has( li[data-dictionary])");
  455. for (let idx = 1; idx < primaryDicts.length; idx++) {
  456. firstList.appendChild(primaryDicts[idx]);
  457. }
  458. }
  459.  
  460. // Adjust font size dynamically for vocab on the back
  461. function adjustVocabFontSize() {
  462. try {
  463. console.log("adjustVocabFontSize running on back...");
  464. const vocabElement = document.querySelector('.vocab');
  465. if (!vocabElement) {
  466. console.log("vocab element not found on back");
  467. return;
  468. }
  469.  
  470. const containerWidth = window.innerWidth * 0.8; // 80vw consistently
  471. console.log("Back container width: " + containerWidth + "px");
  472.  
  473. if (!containerWidth) {
  474. console.log("Container width not available on back");
  475. return;
  476. }
  477.  
  478. let fontSize = 50; // Start with 55px to match Front Template
  479. vocabElement.style.fontSize = fontSize + 'px';
  480.  
  481. // Measure the width of the text content only (exclude furigana if present)
  482. let textWidth = vocabElement.scrollWidth;
  483. console.log("Back initial text width: " + textWidth + "px");
  484.  
  485. // Adjust font size until it fits
  486. let attempts = 0;
  487. while (textWidth > containerWidth && fontSize > 25 && attempts < 50) {
  488. fontSize -= 2;
  489. vocabElement.style.fontSize = fontSize + 'px';
  490. textWidth = vocabElement.scrollWidth;
  491. attempts++;
  492. console.log("Back adjusting font size: " + fontSize + "px, textWidth: " + textWidth + "px");
  493. }
  494.  
  495. // Ensure minimum readability
  496. if (fontSize <= 25) {
  497. vocabElement.style.fontSize = '25px';
  498. fontSize = 25;
  499. console.log("Back minimum adjusted font size reached: 25px");
  500. }
  501.  
  502. console.log("Back final font size: " + fontSize + "px");
  503. } catch (error) {
  504. console.error("Error in adjustVocabFontSize on back:", error);
  505. }
  506. }
  507.  
  508. // Run immediately and poll until the element is available
  509. function ensureFontSizeAdjustment() {
  510. adjustVocabFontSize();
  511. // Poll every 50ms until the element is found or 2 seconds pass
  512. let attempts = 0;
  513. const interval = setInterval(() => {
  514. const vocabElement = document.querySelector('.vocab');
  515. if (vocabElement && vocabElement.scrollWidth > 0) {
  516. adjustVocabFontSize();
  517. clearInterval(interval);
  518. console.log("Font size adjusted successfully on back after polling");
  519. }
  520. attempts++;
  521. if (attempts > 40) { // 40 * 50ms = 2 seconds
  522. clearInterval(interval);
  523. console.log("Stopped polling: vocab element not found or not measurable");
  524. }
  525. }, 50);
  526. }
  527.  
  528. // Initialize all functions!!!
  529. function initialize() {
  530. tweakHTML();
  531. paintTargetWord();
  532. constructPitch();
  533. setUpDefToggle();
  534. formatFrequencyList();
  535. setDHHeight();
  536. hideCorrectDefinition();
  537. movePrimaryDicts();
  538. ensureFontSizeAdjustment();
  539. }
  540.  
  541. // Run on load, resize, and as a fallback
  542. document.addEventListener('DOMContentLoaded', initialize);
  543. window.addEventListener('resize', adjustVocabFontSize);
  544. setTimeout(ensureFontSizeAdjustment, 0); // Immediate fallback
  545.  
  546. initialize();
  547. </script>
  548.  
  549. <script src="__persistence.js"></script>
  550.  
  551. <script>
  552. // nsfw https://github.com/MarvNC/JP-Resources
  553. (function () {
  554. const nsfwDefaultPC = false;
  555. const nsfwDefaultMobile = false;
  556. const imageDiv = document.getElementById('main_image');
  557. const image = imageDiv.querySelector('a img');
  558. if (!image) {
  559. imageDiv.parentNode.removeChild(imageDiv);
  560. return; // Exit if no image
  561. }
  562.  
  563. // Reset nsfwAllowed to false for each card load
  564. if (Persistence.isAvailable()) {
  565. Persistence.setItem('nsfwAllowed', false); // Always start blurred
  566. }
  567.  
  568. let loaded = false;
  569. setInterval(() => {
  570. if (!loaded) {
  571. if (typeof Persistence === 'undefined') {
  572. return;
  573. }
  574. loaded = true;
  575.  
  576. let onMobile = document.documentElement.classList.contains('mobile');
  577. let nsfwAllowed = onMobile ? nsfwDefaultMobile : nsfwDefaultPC;
  578. // Override with persisted value (should be false from reset)
  579. if (Persistence.isAvailable()) {
  580. nsfwAllowed = Persistence.getItem('nsfwAllowed') || nsfwAllowed;
  581. }
  582. setImageStyle(nsfwAllowed);
  583. }
  584. }, 50);
  585. })();
  586.  
  587. function toggleNsfw() {
  588. if (Persistence.isAvailable()) {
  589. let nsfwAllowed = !!Persistence.getItem('nsfwAllowed');
  590. nsfwAllowed = !nsfwAllowed;
  591. Persistence.setItem('nsfwAllowed', nsfwAllowed);
  592. setImageStyle(nsfwAllowed);
  593. } else {
  594. setImageStyle(undefined, true);
  595. }
  596. }
  597.  
  598. function setImageStyle(nsfwAllowed = undefined, toggle = false) {
  599. const imageDiv = document.getElementById('main_image');
  600. const image = imageDiv.querySelector('img');
  601.  
  602. if (nsfwAllowed != undefined) {
  603. imageDiv.classList.toggle('nsfwAllowed', nsfwAllowed);
  604. } else if (toggle) {
  605. imageDiv.classList.toggle('nsfwAllowed');
  606. }
  607. }
  608. </script>
  609.  
  610. </main>
Advertisement
Add Comment
Please, Sign In to add comment