Angular Kana

by Marc Malignan

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.17/angular-ui-router.min.js"></script>
<script src="https://code.jquery.com/jquery-2.2.0.min.js"></script>
<div ng-app="kana">

  <script type="text/ng-template" id="page.html">
  	<nav>
    	<sub-link set="set" sub="gojuon" label="Gojūon"></sub-link>
      <sub-link set="set" sub="gojuon_dakuten" label="Gojūon + Dakuten"></sub-link>
      <sub-link set="set" sub="yoon" label="Yōon"></sub-link>
      <sub-link set="set" sub="yoon_dakuten" label="Yōon + Dakuten"></sub-link>
      <sub-link set="set" sub="test" label="Test"></sub-link>
    </nav>
    <div ui-view></div>
  </script>

  <script type="text/ng-template" id="table.html">
  	<div class="kana-table-wrapper">
      <div class="kana-table clearfix">
        <ul ng-repeat="col in chars">
          <li ng-repeat="c in col">
            <div class="spacing" ng-repeat="n in c.sp track by $index"></div>
            <div class="kana" ng-class="{yoon: sub==='yoon'||sub==='yoon_dakuten'}">
              <div class="jp" title="{{c.rom}}">{{c.jp}}</div>
              <div class="rom">
                <span ng-show="romaji">{{c.rom}}</span>
                <span ng-show="!romaji">&nbsp;</span>
              </div>
            </div>
          </li>
        </ul>
      </div>
    </div>
    <footer>
    	<a class="btn" ng-click="romaji=!romaji">
      	<span ng-show="romaji">Hide Rōmaji</span>
        <span ng-show="!romaji">Show Rōmaji</span>
      </a>
    </footer>
  </script>
  
  <script type="text/ng-template" id="test.html">
  	<div class="test">
    	<div class="kana" ng-class="{yoon: sub==='yoon'||sub==='yoon_dakuten'}">
      	<div class="jp">{{curr.jp}}</div>
    	</div>
      <div>
      	<input type="text" ng-model="reply">
      </div>
      <div class="score">Score : {{score}}/{{total}}</div>
    </div>
    <footer>
    	<a class="btn" ng-click="check()">OK</a>
      <a class="btn" ng-click="next()">Pass</a>
      <a class="btn"...

SCSS

@import url(https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900);
@import url(https://fonts.googleapis.com/css?family=Oswald:300,400,700);

$color-bg-white: #fafafa;
$color-bg-lightgray: #eee;
$color-bg-gray: #ddd;
$color-bg-darkgray: #666;
$color-bg-dark: #333;
$color-bg-true: #4cae4c;
$color-bg-false: #d43f3a;
$color-font-white: #fff;
$color-font-black: #333;
$color-accent: steelblue;

$nav-height: 60px;
$nav-font-size: 1.4em;
$nav-font-weight: 300;
$nav-border-size: 4px;

$kana-size: 40px;
$kana-jp-font-size: 1.4em;
$kana-rom-font-size: 1em;

$kana-table-spacing: 10px;
$kana-table-height: (5*1.5*$kana-size)+(4*$kana-table-spacing);

$content-padding: 20px;
$content-height: $kana-table-height + 80px + 40px + $content-padding;

$body-width: (11*$kana-size)+(10*$kana-table-spacing)+(2*$content-padding);

$transition: .2s;

/* ---------- GLOBAL ---------- */
* { box-sizing: border-box; }
.clearfix:after {
  content: '';
  display: block;
  clear: both;
}

/* ---------- BUTTONS ---------- */
a { 
  cursor: pointer; 
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}
a.btn {
  display: inline-block;
  padding: 10px 15px;
  color: $color-font-black;
  background: $color-bg-gray;
  font-family: 'Oswald', sans-serif;
  font-size: 1.2em;
  font-weight: 300;
  text-decoration: none;
  transition: all $transition;
}
a.btn.active,
a.btn:hover {
  background: $color-accent;
  color: $color-font-white;
}

/* ---------- BODY ---------- */
body {
  width: $body-width;
  margin: 20px auto;
  line-height: 20px;
  background: linear-gradient($color-bg-darkgray, $color-bg-dark) no-repeat;
  background-color: $color-bg-dark;
  color: $color-font-black;
  font-family: 'Roboto', sans-serif;
}

/* ---------- MAIN ---------- */
#main {
  background: $color-bg-lightgray;
  box-shadow: 0 4px 6px rgba(0,0,0,.3);
}

/* ---------- NAV ---------- */
header nav {
  background: $color-bg-dark;
  font-family: 'Oswald',...

JavaScript

var app = angular.module('kana', ['ui.router']);

app.config(function($stateProvider, $urlRouterProvider) {
  $stateProvider
  .state('page', { url: '/:set', controller: 'pageCtrl', templateUrl: 'page.html' })
  .state('page.test', { url: '/test', controller: 'testCtrl', template: '<test chars="chars"></test>' })
  .state('page.sub', { url: '/:sub', controller: 'tableCtrl', templateUrl: 'table.html' });
  $urlRouterProvider.otherwise('/hiragana/gojuon');
});

app.service('kana', function() {
	this.sp = function(n) {
  	return new Array(n);
  };
	this.hiragana = {
  	'gojuon': [
      [{jp:'あ',rom:'a'},  {jp:'い',rom:'i'},   {jp:'う',rom:'u'},   {jp:'え',rom:'e'},  {jp:'お',rom:'o'}],
      [{jp:'か',rom:'ka'}, {jp:'き',rom:'ki'},	{jp:'く',rom:'ku'},  {jp:'け',rom:'ke'}, {jp:'こ',rom:'ko'}],
      [{jp:'さ',rom:'sa'}, {jp:'し',rom:'shi'}, {jp:'す',rom:'su'},  {jp:'せ',rom:'se'}, {jp:'そ',rom:'so'}],
      [{jp:'た',rom:'ta'}, {jp:'ち',rom:'chi'}, {jp:'つ',rom:'tsu'}, {jp:'て',rom:'te'}, {jp:'と',rom:'to'}],
      [{jp:'な',rom:'na'}, {jp:'に',rom:'ni'},  {jp:'ぬ',rom:'nu'},  {jp:'ね',rom:'ne'}, {jp:'の',rom:'no'}],
      [{jp:'は',rom:'ha'}, {jp:'ひ',rom:'hi'},  {jp:'ふ',rom:'fu'},  {jp:'へ',rom:'he'}, {jp:'ほ',rom:'ho'}],
      [{jp:'ま',rom:'ma'}, {jp:'み',rom:'mi'},  {jp:'む',rom:'mu'},  {jp:'め',rom:'me'}, {jp:'も',rom:'mo'}],
      [{jp:'や',rom:'ya'}, {jp:'み',rom:'yu',sp:this.sp(1)}, {jp:'よ',rom:'yo',sp:this.sp(1)}],
      [{jp:'ら',rom:'ra'}, {jp:'り',rom:'ri'},  {jp:'る',rom:'ru'},  {jp:'れ',rom:'re'}, {jp:'ろ',rom:'ro'}],
      [{jp:'や',rom:'wa'}, {jp:'よ',rom:'wo',sp:this.sp(3)}],
      [{jp:'ん',rom:'n',sp:this.sp(4)}]
    ],
    'gojuon_dakuten': [
    	[{jp:'が',rom:'ga'},  {jp:'ぎ',rom:'gi'},   {jp:'ぐ',rom:'gu'},   {jp:'げ',rom:'ge'},  {jp:'ご',rom:'go'}],
      [{jp:'ざ',rom:'za'},  {jp:'じ',rom:'ji'},   {jp:'ず',rom:'zu'},   {jp:'ぜ',rom:'ze'},  {jp:'ぞ',rom:'zo'}],
      [{jp:'だ',rom:'da'},  {jp:'で',rom:'de',sp:this.sp(2)},  {jp:'ど',rom:'do'}],
      [{jp:'ば',rom:'ba'},  {jp:'び',rom:'bi'},  ...