Skip to content

Instantly share code, notes, and snippets.

@uzluisf
Created April 4, 2024 01:51
Show Gist options
  • Save uzluisf/37ae18b331571b84212c4841b662c140 to your computer and use it in GitHub Desktop.
Save uzluisf/37ae18b331571b84212c4841b662c140 to your computer and use it in GitHub Desktop.
An Anki card for testing your listening skills.

Anki Random Audio Card

An Anki note type with 5 audios and 5 transcriptions for a single word/phrase, from which one is selected randomly whenever the card is shown. The card has only a front side with the audio, and a collapsible section with the audio's transcription and more info (e.g., definitions, image, etc).

Screenshots image image

Known Issues

  • If you fail the card, next time Anki shows it all the audios and transcriptions are shown instead of a single audio and a single transcription.

Fields

Word
IPA
Definitions
Audio1
Transcription1
Audio2
Transcription2
Audio3
Transcription3
Audio4
Transcription4
Audio5
Transcription5

Front Template

<div id="root">
<div id="inner-card">

   <div id="audio-container" class="audio">
      <!-- 
         NOTE: We must specify the fields because Anki needs to render them into the HTML
         first before JS can manipulate it
         -->
      {{#Audio1}} <span id="audio1">{{Audio1}}</span> {{/Audio1}}
      {{#Audio2}} <span id="audio2">{{Audio2}}</span> {{/Audio2}}
      {{#Audio3}} <span id="audio3">{{Audio3}}</span> {{/Audio3}}
      {{#Audio4}} <span id="audio4">{{Audio4}}</span> {{/Audio4}}
      {{#Audio5}} <span id="audio5">{{Audio5}}</span> {{/Audio5}}
   </div>

   <div id="details-container">

      <details>
         <summary><i>Test yourself!</i></summary>

         <div id="transcription-container" class="sentence">
            <!-- 
               NOTE: We must specify the fields because Anki needs to render them into the HTML
               first before JS can manipulate it
            -->
            {{#Transcription1}} <span id="transcription1">{{Transcription1}}</span> {{/Transcription1}}
            {{#Transcription2}} <span id="transcription2">{{Transcription2}}</span> {{/Transcription2}}
            {{#Transcription3}} <span id="transcription3">{{Transcription3}}</span> {{/Transcription3}}
            {{#Transcription4}} <span id="transcription4">{{Transcription4}}</span> {{/Transcription4}}
            {{#Transcription5}} <span id="transcription5">{{Transcription5}}</span> {{/Transcription5}}
         </div>

         <hr>

					<span class="entry">
         {{#Word}}
						<span class="word">
  						<span class="HYPHENATION"> {{Word}} </span>
 						 {{#IPA}}
  						<span class="PronCodes">
    						<span class="neutral span"> /</span>
    						<span class="PRON"> {{IPA}} </span>
    						<span class="neutral span">/</span>
  						</span>
 					 		{{/IPA}}
						</span>
         {{/Word}}

				 {{#Definitions}}
           <div class="definitions"> {{Definitions}} </div>
 			   {{/Definitions}}
				 </span>

        {{#Image}}
         <div class="image"> {{Image}} </div>
        {{/Image}}
      </details>
   </div>
</div>
</div>


<script>
   // These IDs corresponds to the declared elements above. Whenever you add more fields,
   // you must add them here as well. Otherwise, they won't participate in
   // the random selection.
   const AUDIO_IDS = ["audio1", "audio2", "audio3", "audio4", "audio5" ];
   const TRANSCRIPTION_IDS = ["transcription1", "transcription2", "transcription3", "transcription4", "transcription5" ]; 
   

	 const root = document.getElementById("root");
   const rootChildren = root.children;
   const innerCard = rootChildren[0];

   pickRandomAudioWithSentence(AUDIO_IDS, TRANSCRIPTION_IDS);
	
   /*
   * Functionalities.
   */

   /*
    * Pick random audio from fields (and corresponding transcription) and display it with remaining fields.
    */
   function pickRandomAudioWithSentence(audioIds, transcriptionIds) {
     // Get all non-empty audio elements.
   		const nonEmptyAudioElems = audioIds
   			.map(audioId => document.getElementById(audioId))
     		.filter(element => !!element && element.hasChildNodes());
   
     // Get a random non-empty audio element.
     const elemIndex = getRandomInt(nonEmptyAudioElems.length);
     const audioElem = nonEmptyAudioElems[elemIndex];
   
   
     // Get all non-empty transcription elements.
     const nonEmptyTranscriptionElems = transcriptionIds
   	   .map(transcriptionId => document.getElementById(transcriptionId))
   	   .filter(elem => elem !== null);
   
     // Get the first transcription element whose ID ends with the same ID as
     // the selected audio element.
     const transcriptionElem = nonEmptyTranscriptionElems.find(elem => elem.id.endsWith(audioElem.id.slice(-1)));
   
     // If we got an audio element, then update the audio container with it only.
     if (audioElem) {
       let audioContainer = document.getElementById("audio-container");
   	    audioContainer = removeAllChildNodes(audioContainer);
   	    audioContainer.appendChild(audioElem);
     }
  
     // If we got an audio element, then update the audio container with it only.
     if (transcriptionElem) {
       let transcriptionContainer = document.getElementById("transcription-container");
   	    transcriptionContainer = removeAllChildNodes(transcriptionContainer);
   	    transcriptionContainer.appendChild(transcriptionElem);
     }
   }
  
   function getRandomInt(max) { return Math.floor(Math.random() * max); }
   
   /*
    * Remove all child nodes from an HTML element.
    * 
    * NOTE: `replaceChildren` is the fully supported and faster way of doing this
    * but I couldn't get it to work.
    * See https://stackoverflow.com/a/3955238
    */
   function removeAllChildNodes(element) {
     while (element.firstChild) {
       element.removeChild(element.lastChild);
     }
   	return element;
   }
</script>

Back Template

Empty

CSS Styling

:root {
/** CSS DARK THEME PRIMARY COLORS */
--color-primary-100: #2196f3;
--color-primary-200: #50a1f5;
--color-primary-300: #6eacf6;
--color-primary-400: #87b8f8;
--color-primary-500: #9dc3f9;
--color-primary-600: #b2cffb;
/** CSS DARK THEME SURFACE COLORS */
--color-surface-100: #121212;
--color-surface-200: #282828;
--color-surface-300: #3f3f3f;
--color-surface-400: #575757;
--color-surface-500: #717171;
--color-surface-600: #8b8b8b;
--color-surface-800: #F2F1EB;
/** CSS DARK THEME MIXED SURFACE COLORS */
--color-surface-mixed-100: #2a4f79;
--color-surface-mixed-200: #436187;
--color-surface-mixed-300: #5b7396;
--color-surface-mixed-400: #7285a4;
--color-surface-mixed-500: #8999b3;

--color-white: #FFFFFF;
--color-silver-white: #F2F2F2;
}

.card {
  font-family: arial;
  font-size: 20px;
  text-align: center;
  color: var(--color-surface-200);
  background-color: white;
}

.nightMode .card {
  background-color: var(--color-surface-100);
}

#inner-card {
  padding: 1em;
  border-radius: 10px;
	background-color: var(--color-silver-white);
}

.nightMode #inner-card {
  color: var(--off-white);
  background-color: var(--color-surface-200);
}

b {
  color: var(--color-primary-100);
  text-decoration: underline;
  font-style: italic;
}

.nightMode b {
  color: var(--color-primary-400);
}

.sentence {
  font-size: 1em;
  font-style: italic;
  margin-top: 2em;
  margin-bottom: 2em;
}

.word {
  font-size: 30px;
  font-weight: bold;
  margin-bottom: 1em;
  color: var(--color-primary-100);
}

.definition {
  color: var(--color-surface-200);
  margin-bottom: 1em;
}

.nightMode .definition {
	color: var(--color-white);
}

.translation {
  font-size: 0.8em;
  font-weight: 100;
  margin-bottom: 1em;
  color: var(--color-surface-100);
}

.nightMode .translation {
  color: var(--color-surface-800);
}

.image {
	display: inline-block;
}

img {     
  border-radius: 15px;
	width: 75%;
}

hr {
	border: 1px dashed grey;
  background-color: white;
}

.nightMode hr {
	border: 1px dashed var(--color-surface-400);
  background-color: var(--color-surface-200);
}

#details-container {
  margin-top: 2em;
}

#audio-container {
    text-align: center;
}

.entry .word {
  text-align: left;
}

.neutral {
    color: black;
    font-style: normal;
    font-weight: normal;
    font-variant: normal;
}

.nightMode .neutral {
  color: white;
}

.HYPHENATION {
	color: red;
  font-size: 160%;
  font-weight: bold;
}

.Sense {
    display: block;
    margin-left: 20px;
    margin-bottom: 15px;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment