// <http://code.google.com/p/sprintf/> v0.4
if(!window.sprintf)
var sprintf=function(){var i=0,a,f=arguments[i++],o=[],m,p,c,x;while(f){if(m=
/^[^\x25]+/.exec(f))o.push(m[0]);else if(m=/^\x25{2}/.exec(f))o.push('%');else
if(m=/^\x25(?:(\d+)\$)?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.
exec(f)){if(((a=arguments[m[1]||i++])==null)||(a==undefined))throw(
"Too few arguments.");if(/[^s]/.test(m[7])&&(typeof(a)!='number'))throw(
"Expecting number but found "+typeof(a));switch(m[7]){case'b':a=a.toString(2);
break;case'c':a=String.fromCharCode(a);break;case'd':a=parseInt(a);break;
case'e':a=m[6]?a.toExponential(m[6]):a.toExponential();break;case'f':a=m[6]?
parseFloat(a).toFixed(m[6]):parseFloat(a);break;case'o':a=a.toString(8);break;
case's':a=((a=String(a))&&m[6]?a.substring(0,m[6]):a);break;case'u':a=Math.abs(
a);break;case'x':a=a.toString(16);break;case'X':a=a.toString(16).toUpperCase();
break;}a=(/[def]/.test(m[7])&&m[2]&&a>0?'+'+a:a);c=m[3]?m[3]=='0'?'0':
m[3].charAt(1):' ';x=m[5]-String(a).length;p=m[5]?(function(i,m){for(var
o=[];m>0;o[--m]=i){}return(o.join(''));})(c,x):'';o.push(m[4]?a+p:p+a);}
else throw("Huh ?!");f=f.substring(m[0].length);}return o.join('');};

// Package.
var KKW = (function(){

var data, run;

var IE = 0 /*@cc_on||@_jscript_version@*/;

var each = [].forEach || function(f) {
    var len = this.length;
    for (var i = 0; i < len; i++) if (i in this) f(this[i], i, this);
};

// CSS selectors.
var $ = window.$ = document.querySelectorAll ? function(sel, obj) {
    if (!obj) {
	obj = document;
	if (/^#[\w\-]+$/.test(sel)) {
	    var a = obj.getElementById(sel.slice(1));
	    a = a ? [a] : [];
	    a.each = each;
	    return a;
	}
    }
    var a = obj.querySelectorAll(sel);
    a.each = each;
    return a;
} : function(sel, obj) {
    if (!obj) obj = document;
    var a = [];
    a.each = each;
    var m = sel.match(/^([\w\-]*|\*)([\.#]?)([\w\-]*)$/);
    if (!m) return a;
    var t = m[1].toUpperCase();
    if (t == '') t = '*';
    if (m[2] == '#') {
	var n = obj.getElementById(m[3]);
	if (n && (t == '*' || n.tagName == t)) a[0] = n;
    } else {
	var na = obj.getElementsByTagName(t);
	t = new RegExp(m[3] == '' ? '' : '(^|\\s)'+m[3]+'(\\s|$)', '');
	for (var i = 0, n; (n = na[i]); i++) if (t.test(n.className)) a.push(n);
    }
    return a;
};

// Cookie jar.
var cookie = window.cookie = {};
(function(){
    var cookies = document.cookie.split(/ *; */);
    while (cookies.length) {
	var vv = cookies.pop().split('=', 2);
	if (vv.length != 2) continue;
	cookie[vv[0]] = vv[1].substr(0,1) == '#' ?
	    eval(vv[1].substr(1)) : decodeURIComponent(vv[1]);
    }
    cookie.set = function(key, val, def) {
	this[key] = val;
	var exp = new Date();
	exp.setTime(val === def ? 0 : exp.getTime()+52*7*24*3600000);
	val = typeof(val) == 'string' ? encodeURIComponent(val) : '#'+val;
	document.cookie =
	    key+'='+val+';expires='+exp.toGMTString()+';path=/';
    };
    try {
	if (!localStorage.getItem || !window.JSON) return;
    } catch(e) {
	return;
    }
    for (var i = 0; i < localStorage.length; i++) {
	var key = localStorage.key(i);
	try { cookie[key] = JSON.parse(localStorage.getItem(key)) } catch(e) {}
    }
    cookie.set = function(key, val, def) {
	if ((this[key] = val) === def) {
	    localStorage.removeItem(key);
	} else {
	    localStorage.setItem(key, JSON.stringify(val));
	}
	document.cookie = key+'=;expires=Fri, 22 Aug 1986 03:00:00 GMT;path=/';
    };
})();

// onDOMLoaded: Use a DOM loaded handler if possible.  For IE try to scroll.
function DOMLoaded() {
    if (KKW.loaded) return;
    KKW.loaded = 1;
    if (window.onDOMLoaded) window.onDOMLoaded.call(window, {});
}
if (document.addEventListener) {
    document.addEventListener('DOMContentLoaded', DOMLoaded, false);
} else {
    IE && (function(){	// Idea by Diego Perini
	try {
	    document.documentElement.doScroll('left');
	} catch(e) {
	    setTimeout(arguments.callee, 50); return;
	}
	DOMLoaded();
    })();
}
window.onload = function(){
    DOMLoaded();
    KKW.loaded = 2;
    onload = null;
};

function addOnHandler(obj, type, handler)
{
    // Add to an "on" event handler of an object, making a list of them.
    // Handlers are to return undefined to disable themselves, false to disable
    // the default action and stay active, or any other value to stay active.
    // It's no problem if the same handler is registered twice.
    if (type == 'resize') {
	// Resize code may trigger resize events under IE and lock it up...
	if (!obj.onresize) {
	    (obj.onresize = function(){
		var self = arguments.callee;
		this.onresize = self.onresize;
		while (this.onresize && self.rate < 5) {
		    self.rate++;
		    setTimeout(function() { self.rate--; }, 100);
		    var e = {};
		    this.onresize.call(this, e);
		    if (!e.redo) break;
		}
		if (this.onresize) {
		    self.onresize = this.onresize;
		    this.onresize = self;
		}
	    }).onresize = null;
	    obj.onresize.rate = 0;
	}
	obj = obj.onresize;
    }
    type = 'on'+type;
    for (var h = obj[type]; h; h = h.old) if (h.handler == handler) return;
    var h = function(e) {
	e = e || window.event || {};
	if (!e.preventDefault) {
	    e.preventDefault = function() { this.returnValue = false };
	    e.stopPropagation = function() { this.cancelBubble = true };
	}
	var self = arguments.callee;
	if ((this[self.type] = self.old)) self.old.call(this, e);
	self.old = this[self.type];
	this[self.type] = self.handler;
	var r = self.handler.call(this, e);
	this[self.type] = r !== undefined && (self.handler = this[self.type]) ?
	    self : self.old;
	if (r === false) e.preventDefault();
    };
    h.type = type;
    h.handler = handler;
    h.old = obj[type];
    obj[type] = h;
}

function treewalk(obj, func)
{
    // Run a function on an object and all its descendants.
    func(obj);
    for (obj = obj.firstChild; obj; obj = obj.nextSibling) treewalk(obj, func);
}

function dataJS(obj)
{
    // Obtain JSON encoded data from the data-js attribute.
    var data = obj.getAttribute('data-js');
    return data && eval('('+data+')');
}

function loadJS(src, onload)
{
    // Dynamically load Javascript.
    var s = loadJS[src];
    if (!s) {
	loadJS[src] = s = document.createElement('SCRIPT');
	addOnHandler(s, 'load', onload || function(){});
	s.onreadystatechange = function(){
	    switch (this.readyState) {
	    case 'complete':
	    case 'loaded':
		if (this.onload) this.onload();
	    }
	};
	s.src = src;
	$('HEAD')[0].appendChild(s);
    } else {
	if (onload) {
	    if (s.onload) {
		addOnHandler(s, 'load', onload);
	    } else {
		onload();
	    }
	}
    }
}

function loadIMG(obj)
{
    // Misuse the useless longDesc attribute to late-load hidden images.
    if (obj.tagName != 'IMG') {
	$('IMG', obj).each(loadIMG);
    } else
    if (obj.longDesc) {
	obj.removeAttribute('src');
	obj.src = obj.longDesc;
	obj.removeAttribute('longDesc');
    }
}

function dynamicStyle()
{
    // Produce functions that dynamically add style snippets to a page.
    var ds = function(){
	var r = [];
	for (var i = 0; i < arguments.length; i++) r = r.concat(arguments[i]);
	r = r.join(' ');

	var self = arguments.callee;
	if (r == self.r) return;

	if (self.s.styleSheet) {		// IE
	    self.s.styleSheet.cssText = r;
	} else {				// W3C
	    self.s.replaceChild(document.createTextNode(r), self.s.firstChild);
	}
	self.r = r;
    };
    ds.s = document.createElement('STYLE');
    ds.s.type = 'text/css';
    if (!ds.s.styleSheet) ds.s.appendChild(document.createTextNode(''));
    $('HEAD')[0].appendChild(ds.s);
    ds.apply(this, arguments);
    return ds;
}

function styleSelect(name, value)
{
    // Set style selectors on the BODY element.
    if (!data.style) return;
    var old = data.style[name];
    if (arguments.length == 2) {
	if (!value) delete data.style[name]; else data.style[name] = value;
    }
    var s = [];
    for (var n in data.style) s.push(data.style[n]);
    s = s.join(' ');
    if (s != document.body.className) document.body.className = s;
    return old;
}

// Beef up IE.
IE && (function(){
    if (!window.XMLHttpRequest || location.protocol == 'file:') {
	window.XMLHttpRequest = function(){
	    return new ActiveXObject('Microsoft.XMLHTTP');
	};
    }
    if (!window.DOMParser) {
	window.DOMParser = function(){};
	window.DOMParser.prototype.parseFromString = function(str) {
	    var d = new ActiveXObject('Microsoft.XMLDOM');
	    d.async = false;
	    d.loadXML(str);
	    return d;
	};
    }
    var e = "ABBR ARTICLE ASIDE AUDIO CANVAS DATALIST DETAILS\
	FIGURE FOOTER HEADER HGROUP MARK MENU METER NAV OUTPUT\
	PROGRESS SECTION TIME VIDEO".split(/\s+/);
    while (e.length) document.createElement(e.pop());
})();

// Beef up the rest.
if (!navigator.browserLanguage) {
    navigator.browserLanguage = navigator.language || 'en';
}

function ajax(url, fresh, status, callback)
{
    // Use XMLHttpRequest to load some XML.  Fresh is true iff the data must be
    // validated at the server, for which we try to bypass the cache.  Status
    // is a text node for diagnostics.  Runs callback(responseXML) on success.
    var req;
    var cached;
    var twirl = 0;
    function change(e)
    {
	switch (req.readyState) {
	default:
	    status.nodeValue = 'Opening...';
	    return;
	case 3:
	    status.nodeValue = 'Loading... ' + "┌┐┘└".substr((twirl++)%4,1);
	    return;
	case 4:
	    status.nodeValue = req.status + ' ' + req.statusText;
	}
	if (req.status == 200 && fresh && !cached) {
	    // If what we got is from the cache then revalidate it.
	    var d = new Date(req.getResponseHeader('Date') || 0);
	    if (d.getTime() <= (new Date()).getTime() - 10*60*1000) {
		status.nodeValue = 'Initiating...';
		var lastmod = req.getResponseHeader('Last-Modified') ||
		    'Thu, 01 Jan 1970 00:00:00 GMT';
		cached = req;
		req = new XMLHttpRequest();
		req.onreadystatechange = change;
		req.open('GET', url, true);
		req.setRequestHeader('If-Modified-Since', lastmod);
		req.send(null);
		return;
	    }
	}
	if (req.status == 304) {
	    // The response from the cache is still good.
	    req = cached;
	}
	if (req.status == 200 || req.status == 0) {
	    var r = req.responseXML;
	    if (r) {
		if (!r.documentElement) r.load(req.responseStream);
		status.nodeValue = 'XML data loaded';
	    } else {
		r = req.responseText;
		status.nodeValue = 'Data loaded';
	    }
	    callback(r, req);
	}
    }
    if (!status) status = {};
    status.nodeValue = 'Initiating...';
    req = new XMLHttpRequest();
    req.onreadystatechange = change;
    req.open('GET', url, true);
    req.send(null);
}

// Keep images around that change dynamically to avoid interrupting a load.
function keepIMG(img)
{
    if (!keepIMG[img.src]) {
	(keepIMG[img.src] = document.createElement('IMG')).src = img.src;
    }
    return img;
}

// Call the resize handler when the DOM is changed.
function resize(now)
{
    if (window.onresize) {
	clearTimeout(resize.TID);
	if (now) {
	    window.onresize({});
	} else {
	    resize.TID = setTimeout(function(){ window.onresize({}) }, 1);
	}
    }
}

// Report the position of a node.
function position(n)
{
    var pos = [0, 0];
    do {
	pos[0] += n.offsetLeft;
	pos[1] += n.offsetTop;
    } while ((n = n.offsetParent));
    return pos;
}

// Compute the dimensions of the canvas.
function viewport()
{
    var d = [
	window.pageXOffset || document.body.scrollLeft ||
           document.documentElement.scrollLeft || 0,
	window.pageYOffset || document.body.scrollTop ||
           document.documentElement.scrollTop || 0,
	Math.min(document.documentElement.clientWidth,
	    document.body.clientWidth),
	Math.min(document.documentElement.clientHeight,
	    document.body.clientHeight)
    ];
    d[4] = d[2]+'x'+d[3];
    return d;
}

// Keep functions that initialize widgets.
var widget = {
    list: [],
    add: function(f) {
	this.list.push(f);
    },
    init: function(){
	for (var i = 0; i < this.list.length; i++) (this.list[i])();
    }
};

addOnHandler(window, 'DOMLoaded', function(){

if (!run) return;

// This script sits at the root of the site, and first in the <head>.
var base = KKW.base = $('SCRIPT')[0].src.replace(/[^\/]*$/,'');

// Mobile.
(function(){
    var w = Math.min(screen.width, screen.height);
    var s = navigator.userAgent.match(/\b(iPhone|iPad)\b/);
    s = !s ? (w < 600 ? 48 : 72) : (s[1] == 'iPhone' ? 57 : 72);
    s *= (window.devicePixelRatio||1);
    var l = document.createElement('LINK');
    l.rel = 'apple-touch-icon-precomposed';
    l.href = base+'miscellaneous/favicon'+s+'.png';
    $('HEAD')[0].appendChild(l);
})();

// Hide articles above a targeted element while loading so they don't push it
// down.  (Responsive images cause reflows when their sizes becomes known.)
(function(){
    var t = location.hash;
    if (!(t.length > 1 && (t = $(t)[0]))) return;
    while (t.tagName.toUpperCase() != 'ARTICLE') {
	if (!(t = t.parentNode)) return;
    }
    var a = t, ids = [];
    while ((a = a.previousSibling)) {
	if (a.nodeName.toUpperCase() == 'ARTICLE') {
	    if (!a.id) return;
	    ids.unshift('#'+a.id);
	}
    }
    if (ids.length == 0) return;
    var hide = dynamicStyle(ids.join(',') + '{display:none}');
    var w = document.createElement('ARTICLE');
    w.id = 'loading';
    w.innerHTML =
	'<button><div lang=ja class=toggle-ja>前の' +
	(document.body.id == 'shopping' ? '品物' : '記事') +
	'を表示</div><div lang=en class=toggle-en>View earlier article' +
	(ids.length == 1 ? '' : 's') + '</div></button>';
    t.parentNode.insertBefore(w, t);
    t.scrollIntoView(true);
    w.firstChild.onclick = function(){
	var d = position(t)[1] - viewport()[1];
	w.parentNode.removeChild(w);
	hide();
	resize(true);
	scrollTo(0, position(t)[1] - d);
	t.focus();
    };
})();

// Enable experimental CSS.
if (location.host != 'kitagawa-keiko.org') styleSelect('test', 'test');

widget.add(function(){
    for (var i = 0, name; (name = ['IMG','BUTTON','INPUT'][i]); i++) {
	$(name).each(function(btn) {
	    if (btn.onclick) return;
	    if (btn.id.search(/^toggle-(.*)-\d+$/) != -1) {
		// Toggle objects between open/seen and closed/unseen.  The
		// button images have a + or a - in their name to cause an open
		// or close.  Their ID attribute is id=toggle-<keyword>-<n> and
		// their title starts with "Show" or "Hide", or ends with
		// 開けます or 閉めます.  Objects to show or hide have
		// id=<keyword>-<n>, for <n> = 0, 1, ... or <n> = -1, -2, ...
		// if they should be hidden instead of shown.
		addOnHandler(btn, 'click', function(){
		    var key = this.id.replace(/^toggle-(.*)-\d+$/,'$1');
		    var show = this.src.indexOf('+') != -1;

		    for (var i = 0;; i++) {
			var btn = $('#toggle-'+key+'-'+i)[0];
			if (!btn) break;
			keepIMG(btn);
			btn.src = btn.src.replace(/[\-+](\.\w+)$/,
			    show ? '-$1' : '+$1');
			btn.title = btn.title.replace(/^(Show|Hide)/,
			    show ? 'Hide' : 'Show');
			btn.title = btn.title.replace(/(開けます|閉めます)$/,
			    show ? '閉めます' : '開けます');
		    }
		    for (var i = 0, obj; (obj = $('#'+key+'-'+i)[0]); i++) {
			obj.style.display = show ? '' : 'none';
			loadIMG(obj);
		    }
		    for (var i = -1, obj; (obj = $('#'+key+'-'+i)[0]); i--) {
			obj.style.display = show ? 'none' : '';
		    }
		    resize();
		    return false;
		});
	    } else
	    if (btn.className == 'expose-next-row') {
		// Expose the next row of a table if this button is clicked.
		addOnHandler(btn, 'click', function(){
		    var tr = this;
		    while ((tr = tr.parentNode) && tr.nodeName != 'TR') {}
		    if (!tr) return;
		    tr.className = 'hide';
		    while ((tr = tr.nextSibling) && tr.nodeName != 'TR') {}
		    if (!tr) return;
		    tr.className = '';
		    loadIMG(tr);
		    resize();
		    return false;
		});
	    }
	});
    }
});

// Time zone offset from UTC.
function zoff(d, z)
{
    switch (z) {
    case 'UTC':
	return 0;
    case 'JST':
	return 9*3600*1000;
    case 'CET':
	var y = d.getUTCFullYear();
	var f = Math.floor(5*y/4) - Math.floor(y/100) + Math.floor(y/400);
	var s = 20001 + (31 - ((f+5)%7)) * 100;
	var e = 90001 + (31 - ((f+2)%7)) * 100;
	var u = d.getUTCMonth()*10000 + d.getUTCDate()*100 + d.getUTCHours();
	return (u < s || u >= e ? 1 : 2)*3600*1000;
    }
}

// Convert <span class=time>##:## UTC/JST</span> to the viewer's time zone.
$('SPAN.time').each(function(span) {
    var text = span.firstChild;
    if (!text || text.nodeName != '#text') return;
    var time = text.nodeValue.match(
	/^((\d{4})-(\d{2})-(\d{2})\s+)?(\d{1,2}):(\d{2})\s+(UTC|JST|CET)$/
    );
    if (!time) return;

    var date = new Date();
    if (time[1]) date.setUTCFullYear(time[2], time[3]-1, time[4]);
    date.setUTCHours(time[5], time[6]);
    date.setTime(date.getTime() - zoff(date, time[7]));
    text.nodeValue = sprintf("%04d-%02d-%02d %02d:%02d",
	date.getFullYear(), date.getMonth()+1, date.getDay(),
	date.getHours(), date.getMinutes()).substr(time[1] ? 0 : 11);
    span.title = time[0] + ' converted to the time zone of your computer';
    span.style.borderBottom = '1px dotted';
});

// Running clocks.
(function(lc, tc, jc) {
    function tick()
    {
	var now = new Date();
	clearTimeout(tick.TID);
	tick.TID = setTimeout(tick,
	    60000 - (now.getSeconds() * 1000 + now.getMilliseconds()));

	function set(c, h, m) {
	    if (c) c.firstChild.nodeValue = sprintf("%02d:%02d", h, m);
	}
	set(lc, now.getHours(), now.getMinutes());
	var t = new Date(now.getTime() + zoff(now, 'CET'));
	set(tc, t.getUTCHours(), t.getUTCMinutes());
	var j = new Date(now.getTime() + 9*3600*1000);
	set(jc, j.getUTCHours(), j.getUTCMinutes());
	return true;
    }
    if (!(lc || tc || jc)) return;
    tick();
    addOnHandler(window, 'focus', tick);
})($('#localclock')[0], $('#transclock')[0], $('#japanclock')[0]);

// @media polyfill.
if (!window.matchMedia) loadJS(base + 'miscellaneous/respond.js');

// What language to show?
var langlist = navigator.browserLanguage.substr(0,2) == 'ja' ?
    ['ja','both','en'] : ['en','both','ja'];

// Language toggle button.
$('#lang-toggle').each(function(toggle) {
    var langidx = cookie.lang || 0;

    function show_language()
    {
	// Select the right language and update the button.
	styleSelect('lang', 'select-'+langlist[langidx]);
	keepIMG(toggle);
	toggle.src = toggle.src.replace(/[^\/]+$/, langlist[langidx]+'.png');
    }
    show_language();

    function realign()
    {
	// Make same-row elements line up by adjusting their top padding.
	for (var i = 0; i < 3; i++) {
	    var change = false;
	    for (var r = 0;; r++) {
		var obj = [];
		var base = [];
		var align = langidx == 1;
		for (var n = 0;; n++) {
		    var o = $('#align-' + r + '-' + n)[0];
		    if (!o) break;
		    if (o.style.paddingTop == '') o.style.paddingTop = '0px';
		    base[n] = o.parentNode.offsetTop;
		    if (base[n] != base[0]) align = false;
		    obj[n] = o;
		}
		if (n == 0) break;

		if (align) {
		    var maxTop = 0;
		    for (var c = 0; c < n; c++) {
			var t = obj[c].offsetTop;
			if (t > maxTop) maxTop = t;
		    }
		    for (var c = 0; c < n; c++) {
			var p = maxTop - obj[c].offsetTop;
			if (p <= 1) p = 0;
			if (obj[c].style.paddingTop != p+'px') {
			    obj[c].style.paddingTop = p+'px';
			    change = true;
			}
		    }
		} else {
		    for (var c = 0; c < n; c++) {
			obj[c].style.paddingTop = '0px';
		    }
		}
	    }
	    if (!change) break;
	}
	return true;
    }
    addOnHandler(window, 'resize', realign);

    addOnHandler(toggle, 'click', function(){
	// Change language preference and update.
	langidx = (langidx+1) % langlist.length;
	cookie.set('lang', langidx, 0);

	show_language();
	resize();
	return false;
    });

    addOnHandler(document, 'keydown', function(e) {
	if (e.keyCode != 76) return true;
	if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey) return true;
	var t = e.target || e.srcElement;
	if (t.nodeName == 'INPUT' || t.nodeName == 'TEXTAREA') return true;
	toggle.onclick();
	return false;
    });
});

// Automatic language choice.
if (!$('#lang-toggle')[0]) {
    function autolang()
    {
	styleSelect('lang', 'select-' + langlist[
	    data.bilingual && data.bilingual() ? 1 : 0
	]);
	return true;
    }
    addOnHandler(window, 'resize', autolang);
}

function parse(s)
{
    // Parse a search expression and return a function that tests it.
    function unit(s)
    {
	var m;
	var re = '';
	if ((m = s.match(/^\((.*)/))) {
	    var p = parse(m[1]);
	    if (!(m = p[1].match(/^\s*\)(.*)/))) {
		throw new SyntaxError("Missing ')'");
	    }
	    return [p[0],m[1]];
	}
	while ((m = s.match(/^([^\s　\"\|\&\(\)]+)(.*)/))
	    || (m = s.match(/^"([^"]*)"(.*)/))
	) {
	    re += m[1].
		replace(/([!-'\*-\/:-@`{}~\[\]])/, '\\$1').
		replace(/[\s　]+/, '[\\s　]+').
		replace(/oo|ou|o|ō/ig, 'Ō').
		replace(/uu|u|ū/ig, 'Ū').
		replace(/e|é|è/ig, 'È').
		replace(/Ō/g, '(oo|ou|o|ō)').
		replace(/Ū/g, '(uu|u|ū)').
		replace(/È/g, '(e|é|è)');
	    s = m[2];
	}
	var i;
	while ((i = re.search(/[！-～]/)) != -1) {
	    var c = String.fromCharCode(re.charCodeAt(i) - 65248);
	    c = c.replace(/([^\da-z])/i, '\\$1');
	    re = re.substr(0,i) + c + re.substr(i+1);
	}
	var f = re == '' ? function(s) { return 0; } : function(s) {
	    return arguments.callee.word.test(s) ? 1 :
		arguments.callee.any.test(s) ? 0 : -1;
	};
	f.any = new RegExp(re, 'i');
	f.word = new RegExp('\\b'+re+'\\b', 'i');
	return [f,s];
    }

    function term(s)
    {
	var u = unit(s);
	var m = u[1].match(/^[\s　]*[\s　\&][\s　]*(.*)/);
	if (!m) return u;
	var t = term(m[1]);
	var f = function(s) {
	    var a = arguments.callee.a(s);
	    var b = arguments.callee.b(s);
	    return a < 0 || b < 0 ? -1 : a+b;
	};
	f.a = u[0];
	f.b = t[0];
	return [f,t[1]];
    }

    var t = term(s.replace(/^[\s　]+/,''));
    var m = t[1].match(/^[\s　]*\|[\s　]*(.*)/);
    if (!m) return t;
    var p = parse(m[1]);
    var f = function(s) {
	var a = arguments.callee.a(s);
	var b = arguments.callee.b(s);
	return a < 0 ? b : b < 0 ? a : a+b;
    };
    f.a = t[0];
    f.b = p[0];
    return [f,p[1]];
}

var sdbfile = KKW.sdbfile = { 2006: 'kitagawakeiko2006/search.xml' };

// Search function.
widget.add(function(){
    var search = $('#search')[0];
    if (!search || search.onsubmit) return;
    var pattern = search.elements.pattern;
    var file = $('#search-file')[0].firstChild;
    var status = $('#search-status')[0].firstChild;
    var result = $('#index-search')[0];
    var template = result.rows[0];
    var bonus = $('#bonus')[0];

    var sdb = {};
    function search_entries()
    {
	// Show only the titles for entries matching the search pattern.
	var test;
	try {
	    if (pattern.value.substr(0,1) == '/') {
		test = function(s) {
		    return arguments.callee.re.test(s) ? 0 : -1;
		};
		test.re = new RegExp(pattern.value.substr(1), 'i');
	    } else {
		test = parse(pattern.value);
		if (test[1] != '') {
		    throw new SyntaxError("Can't parse past " + test[1]);
		}
		test = test[0];
	    }
	} catch(e) {
	    status.nodeValue = e.name + ': ' + e.message;
	    return;
	}

	status.nodeValue = ' ';
	if (pattern.value == '') { styleSelect('show-search', ''); return; }

	if (bonus) loadIMG(bonus);
	var tbody = result.cloneNode(false);
	result.parentNode.replaceChild(tbody, result);
	result = tbody;
	styleSelect('show-search', 'show-search');

	var nres = 0;
	for (var y = search.from.options[search.from.selectedIndex].text;
	    y <= search.to.options[search.to.selectedIndex].text; y++
	) {
	    if (!sdbfile[y] || !sdb[y]) continue;
	    var yref = base + sdbfile[y].replace(/\/.*/,'/');
	    var entries = sdb[y].getElementsByTagName('entry');
	    for (var i = 0, entry; (entry = entries[i]); i++) {
		entry.normalize();
		var text = entry.firstChild.nodeValue;
		var h = test(text);
		if (h < 0) continue;
		var date = entry.getAttribute('date');
		var orig = entry.getAttribute('orig');
		var titles = text.match(/\s*([^\n]+)\n([^\n]+)/);
		var tr = template.cloneNode(true);
		h = Math.round(255 * Math.exp(-0.125 * h));
		tr.style.backgroundColor = 'rgb('+h+',255,255)';
		var a = tr.getElementsByTagName('a');
		a[1].href = yref + date.substr(0,7) + '/' + date + '.html';
		a[0].href = orig || a[1].href;
		a[0].firstChild.nodeValue = titles[1];
		a[1].firstChild.nodeValue = titles[2];
		tr.getElementsByTagName('td')[1].firstChild.nodeValue =
		    date.substr(0,10)+' '+date.substr(11).replace(/-/g,':');
		tbody.appendChild(tr);
		nres++;
	    }
	}
	status.nodeValue = '結果 '+nres+' result'+(nres == 1 ? '' : 's');
	if (nres == 0) {
	    tbody.appendChild(template.cloneNode(true));
	    var div = $('DIV', tbody);
	    div[0].innerHTML = '結果はありません';
	    div[1].innerHTML = 'No results';
	}
    }

    function sched(delay)
    {
	// Schedule/postpone a search if a delay is given.
	if (delay) {
	    clearTimeout(sched.TID);
	    if (!sched.delay || delay < sched.delay) sched.delay = delay;
	    sched.TID = setTimeout(function(){
		sched.delay = 0; sched();
	    }, sched.delay);
	}

	// Load needed search databases.
	if (file.nodeValue.length > 1) return;
	for (var y = search.from.options[search.from.selectedIndex].text;
	    y <= search.to.options[search.to.selectedIndex].text; y++
	) {
	    if (y >= 2008) sdbfile[y] = 'star-studio/search/'+y+'.xml';
	    if (sdb[y] || !sdbfile[y] || pattern.value == '') continue;
	    file.nodeValue = sdbfile[y];
	    ajax(base + sdbfile[y], false, status, function(xml) {
		sdb[y] = xml;
		file.nodeValue = ' ';
		sched();
	    });
	    return;
	}

	// The databases are loaded and the delay has expired?
	if (!sched.delay) search_entries();
    }

    // Schedule a search on various inputs.
    if (!window.operamini) {
	search.from.onchange = search.to.onchange = function() {
	    while (search.from.options[search.from.selectedIndex].text >
		search.to.options[search.to.selectedIndex].text
	    ) {
		if (this == search.from) {
		    search.to.selectedIndex++;
		} else {
		    search.from.selectedIndex--;
		}
	    }
	    sched(2000);
	}
	pattern.onchange = pattern.onkeyup = function() { sched(2000); };
    }
    search.onsubmit = function() { sched(100); };
    var clears = document.getElementsByName('clear');
    for (var i = 0; clears[i]; i++) {
	clears[i].onclick = function() { pattern.value = ''; };
    }

    // Reload?
    if (pattern.value != '') sched(1000);
});

// Show nearest title attribute on touch.
if (document.elementFromPoint && document.body.getClientRects) (function(){
    var tid, tips = {}, mark = [], curtip;

    function settip(t, x, y, v)
    {
	// Hide or show a tooltip.
	if (t) {
	    t.style.left = x + 'px';
	    t.style.top = y + 'px';
	    t.style.visibility = v;
	}
	curtip = v != 'hidden' && t;
    }

    function cleartips()
    {
	// Remove titled element decorations and hide tooltip.
	while (mark.length) mark.pop().style.outline = '';
	settip(curtip, 0, 0, 'hidden');
    }

    function areaRects(a)
    {
	// Compute the rectangles covered by an AREA element.
	var rs = [];
	var c = a.coords.split(/,/);
	var imgs = $('[usemap="#'+a.parentNode.name+'"]');
	for (var i = 0, img; (img = imgs[i]); i++) {
	    var r = img.getBoundingClientRect();
	    if (r) rs.push({
		left: r.left + +c[0],
		top: r.top + +c[1],
		right: r.left + +c[2],
		bottom: r.top + +c[3]
	    });
	}
	return rs;
    }

    function dist(x, y, r)
    {
	// Distance from point (x,y) to rectangle r, doubled vertically.
	if (x >= r.left) x = x < r.right ? r.left : x - r.right + r.left;
	if (y >= r.top) y = y < r.bottom ? r.top : y - r.bottom + r.top;
	x -= r.left;
	y -= r.top;
	return x*x + 4*y*y;
    }

    document.ontouchstart = function(e) {
	// Compute distances to titled elements from the first touch,
	// mark those that are near, and show tooltip for the nearest.
	// All potential tooltips are set up and kept hidden, causing only
	// one costly reflow.  Changing outlines and (un)hide are fast.
	// (On iOS, clientX/Y and elementFromPoint are broken.)
	if (e.touches.length != 1) return;
	if ((e.target || e.srcElement).onclick) return;
	var t = e.touches[0];
	var v = viewport();
	var r = document.documentElement.getClientRects()[0];
	var iFix = t.clientX == t.pageX && t.clientY == t.pageY
	    && (r.left != 0 || r.top != 0) ? v : [0,0];
	var x = t.clientX - iFix[0], y = t.clientY - iFix[1];
	var near = [Infinity];
	var titled = $('[title]');
	for (var i = 0, t; (t = titled[i]); i++) {
	    if (!t.title) continue;
	    var rs = t.nodeName == 'AREA' ? areaRects(t) : t.getClientRects();
	    for (var j = 0, r; (r = rs[j]); j++) {
		if (r.left == r.right || r.top == r.bottom) continue;
		var d = dist(x, y, r);
		if (d > 500*500) continue;
		var p = document.elementFromPoint(
		    iFix[0]+(r.left+r.right)/2, iFix[1]+(r.top+r.bottom)/2);
		while (p && p != t) p = p.parentNode;
		if (!p && t.nodeName != 'AREA') continue;
		if (d < near[0]) near = [d, t, r];
		t.style.outline = '2px dotted #' + ['F83','088','83F'][i%3];
		mark.push(t);
	    }
	    if (!tips[t.title]) {
		var tip = tips[t.title] = document.createElement('DIV');
		tip.className = 'tooltip';
		tip.style.top = 0;
		tip.style.visibility = 'hidden';
		tip.appendChild(document.createTextNode(t.title));
		document.body.appendChild(tip);
	    }
	}
	if (near[1]) {
	    var d = near[0], t = near[1], r = near[2];
	    t.style.outline = '2px solid #F00';
	    var tip = tips[t.title];
	    x = v[0] + (r.left + r.right)/2;
	    x = x - tip.offsetWidth * (x / document.body.clientWidth);
	    y = v[1] + r.bottom + 8;
	    if (y + tip.offsetHeight > document.body.clientHeight) {
		y = v[1] + r.top - tip.offsetHeight - 8;
	    }
	    settip(curtip, 0, 0, 'hidden');
	    settip(tip, x, y, 'visible');
	    clearTimeout(tid);
	    tid = setTimeout(cleartips, 5000);
	}
    };

    document.ontouchend = function(e) {
	if (e.touches.length == 0) cleartips();
    };

    // Debugging on the desktop.
    if (location.host != 'kitagawa-keiko.org') {
	document.onmousemove = function(e) {
	    e = e || window.event;
	    if (e.shiftKey) {
		document.ontouchstart({touches:[e]});
	    } else {
		document.ontouchend({touches:[]});
	    }
	};
    }
})();

// Doll up the RSS feed.
$('#rss').each(function(rss) {
    var rows = $('TR',rss);
    var latest = 0;
    for (var i = 0, tr; (tr = rows[i]); i++) {
	var t = dataJS(tr);
	if (!t) continue;
	if (t > (cookie.rsslastseen || Infinity)) {
	    $('.title',tr)[0].className += ' new';
	}
	if (t > latest) latest = t;
    }
    if (latest != cookie.rsslastseen) cookie.set('rsslastseen', latest);

    if (location.host == 'kitagawa-keiko.org') {
	var url = base + 'miscellaneous/views.xml';
	if (window == top) url += '?viewport=' + viewport()[4];
	ajax(url, false, null, function(xml) {
	    var views = xml.getElementsByTagName('views');
	    var count = {};
	    for (var i = 0, v; (v= views[i]); i++) {
		count[v.getAttribute('entry')] = v.getAttribute('count');
	    }
	    for (var i = 0, tr; (tr = rows[i]); i++) {
		var td = document.createElement('TD');
		td.className = 'views';
		var t = tr.id.match(/^news-(.*)$/);
		if (t) t = count[t[1]];
		if (t) td.innerHTML = t;
		tr.appendChild(td);
	    }
	    var t = document.createElement('THEAD');
	    rss.insertBefore(t, rss.firstChild);
	    t = t.appendChild(document.createElement('TR'));
	    t.appendChild(document.createElement('TH')).colSpan = 2;
	    t = t.appendChild(document.createElement('TH'));
	    t.className = 'views';
	    t.title = "Estimated number of viewers";
	    resize();
	});
    }
});

$('#login').each(function(login) {
    var a = $('A', login)[0];
    a.href = base + 'maintenance/login.cgi' + location.pathname;
    var form = $('FORM', login)[0];
    if (form) {
	form.elements['path-info'].value = location.pathname;
	addOnHandler(a, 'click', function(){
	    form.style.display = form.style.display ? '' : 'block';
	    $('#name')[0].focus();
	    return false;
	});
    }
});
$('#yahoo-login').each(function(login) {
    // Blog♪.
    $('#ex-home')[0].innerHTML =
	    '<span title="(Blog♪ former home)">（Blog♪の前の家）</span>';
    login.innerHTML =
	'<span title="Kitagawa Keiko\'s Blog♪ archive">' +
	'北川景子のBlog♪Blog♪Blog♪の書庫</span> - ' +
	'<span title="Hello, guest">こんにちは、ゲストさん - ' +
	'<a href=X title="メンテナンスログイン ／ maintenance login">' +
	'ログイン</a></span>';
    $('A', login)[0].href = base + 'maintenance/login.cgi' + location.pathname;
});

if ($('#root')[0]) (function(){
    var t = document.createElement('SPAN');
    document.body.appendChild(t);
    t.innerHTML = 'ｹｲｺ';
    var h = t.offsetWidth;
    t.innerHTML = 'ケイコ';
    var f = t.offsetWidth;
    document.body.removeChild(t);
    if (h >= f) {
	t = document.createElement('DIV');
	t.style.color = '#F00';
	t.innerHTML = 'Note: This site looks a lot better with <a href="'
    +'http://en.wikipedia.org/wiki/Help:Multilingual_support_(East_Asian)'
	    +'">East Asian language support</a>.';
	h = $('HEADER')[0] || $('#tagline')[0];
	h.parentNode.insertBefore(t, h.nextSibling);
    }
})();

$('#gallery').each(function(gallery) {
    // Fold-out galleries have images encoded on links.
    $('A', gallery).each(function(a) {
	var entry = a.href.match(/^(.*\/\d\d\d\d(-\d\d){4})\.html$/);
	if (!entry) return;
	var data = dataJS(a);
	if (!data) return;
	entry = entry[1];
	a.title = a.removeChild(a.firstChild).nodeValue;
	for (;;) {
	    var img = document.createElement('IMG');
	    if (data.length != 0) {
		var swh = data.shift();
		img.longDesc = entry + '-' + swh[0] + '.jpg';
		img.width = swh[1];
		img.height = swh[2];
		var span = document.createElement('SPAN');
		span.className = 'image';
		span.appendChild(img);
		img = span;
	    } else {
		img.src = base + 'miscellaneous/no-picture.png';
		img.width = 57;
		img.height = 53;
	    }
	    a.appendChild(img);
	    if (data.length == 0) break;
	    a.appendChild(document.createTextNode(' '));
	}
    });

    // Shrink large images.
    function shrink(e)
    {
	var v = viewport();
	var ww = v[2] - 30;
	var wh = v[3];

	for (var i = 0, s; (s = shrink.data[i]); i++) {
	    var img = s.img;
	    img.className = img.className.replace(/ *\bzoom-\w+\b */,' ');
	    if (s.width <= ww && s.height <= wh) {
		img.width = s.width;
		img.height = s.height;
		s.max = false;
	    } else
	    if (s.max) {
		img.width = s.width;
		img.height = s.height;
		img.className += ' zoom-out';
	    } else {
		var w = ww / s.width;
		var h = wh / s.height;
		if (w < h) {
		    img.width = ww;
		    img.height = Math.round(s.height * w);
		} else {
		    img.width = Math.round(s.width * h);
		    img.height = wh;
		}
		img.className += ' zoom-in';
	    }
	}
	return true;
    }
    shrink.data = [];
    addOnHandler(window, 'resize', shrink);

    widget.add(function(){
	$('IMG.shrinkable', gallery).each(function(img) {
	    if (img.onclick) return;
	    var data = {
		img: img,
		width: parseInt(img.getAttribute('width')),
		height: parseInt(img.getAttribute('height'))
	    };
	    shrink.data.push(data);
	    img.onclick = function(){
		var self = arguments.callee;
		self.data.max = !self.data.max;
		resize(true);
		this.scrollIntoView(true);
	    };
	    img.onclick.data = data;
	});
    });
});

// Allow shrunk images to be enlarged and shrink images wider than the screen.
widget.add(function(){
    var popup = window.name.match(/^(\d+)x(\d+)$/);
    var w = popup ? popup[1] : (screen.availWidth || Infinity);
    w -= w <= 480 ? 50 : 70;
    $('IMG').each(function(img) {
	if (img.className.search(/\bshrunk\b/) != -1
	    || img.parentNode.className.search(/\bshrunk\b/) != -1
	) {
	    img.onclick = function(){
		var self = arguments.callee;
		this.className = this.className.replace(/ *\bzoom-\w+\b */,' ');
		if ((self.max = !self.max)) {
		    this.style.width = 'auto';
		    this.style.height = 'auto';
		    this.className += ' zoom-out';
		} else {
		    this.style.width = '';
		    this.style.height = '';
		    this.className += ' zoom-in';
		}
		resize(true);
		this.scrollIntoView(true);
	    };
	    img.className += ' zoom-in';
	}
	if (img.width > w) {
	    img.height = Math.round(img.height * w / img.width);
	    img.width = w;
	}
    });
    // Likewise videos.
    $('VIDEO').each(function(vid) {
	if (vid.width > w) {
	    vid.height = Math.round(vid.height * w / vid.width);
	    vid.width = w;
	}
	addOnHandler(vid, 'click', function(){
	    this.play();
	    this.onclick = null;
	    return false;
	});
	vid.onplay = function() { this.onclick = null; };
    });
});

// Activate video players if necessary.
if (IE || (navigator.plugins && navigator.plugins["Shockwave Flash"])) {
    widget.add(function(){
	$('A.flashplayer').each(function(a) {
	    a.className = '';
	    if (a.parentNode.play) return;
	    var f = function(){
		var a = arguments.callee.a;
		var img = $('IMG', a)[0];
		a.style.minWidth = img.width + 'px';
		a.style.minHeight = img.height + 'px';
		flowplayer(a, {
		    width: img.width,
		    height: img.height + 24,
		    src: base + 'miscellaneous/flowplayer.swf',
		    initialVolumePercentage: 100
		});
	    };
	    f.a = a;
	    loadJS(base + 'miscellaneous/flowplayer.js', f);
	});
    });
}

// Windows sans-serif has ugly arrows.
widget.add(function(){
    var arrow = [];
    treewalk(document.body, function(obj) {
	if (obj.nodeName != '#text') return;
	if (obj.parentNode.style.fontFamily == 'serif') return;
	var pos = obj.nodeValue.search(/[←↑→↓↔↕↨]/);
	if (pos == -1) return;
	if (pos > 0) { obj.splitText(pos); return; }
	if (obj.nodeValue.length > 1) obj.splitText(1);
	arrow.push(obj);
    });
    for (var i = 0, a; (a = arrow[i]); i++) {
	var p = a.parentNode;
	var span = document.createElement('SPAN');
	span.style.fontFamily = 'serif';
	span.style.fontSize = '100%';
	p.insertBefore(span, a);
	span.appendChild(a);
    }
});

// Comments.
function load_comments(doit)
{
    var cc = $('#comments-control')[0];
    cc.innerHTML = (doit ? 'Hide' : 'Show') + ' comments';
    while ((cc = cc.nextSibling)) cc.style.display = doit ? '' : 'none';
    if (doit) loadJS('http://keiko-writings.disqus.com/embed.js');
}
location.host == 'kitagawa-keiko.org' && window == top && widget.add(function(){
    if (!data.comments) return;
    window.disqus_shortname = 'keiko-writings';
    window.disqus_identifier = data.comments;
    data.comments = null;

    var c = $('#comments')[0];
    if (!c) {
	c = document.createElement('DIV');
	c.id = 'comments';
	c.lang = 'en';
	document.body.appendChild(c);
    }
    var cc = document.createElement('BUTTON');
    cc.id = 'comments-control';
    cc.onclick = function(){
	cookie.set('comments', !cookie.comments, false);
	load_comments(cookie.comments);
	if (cookie.comments) this.scrollIntoView(true);
    };
    var dq = document.createElement('DIV');
    dq.id = 'disqus_thread';
    c.appendChild(cc);
    c.appendChild(dq);
    var nav = $('TABLE.navigation');
    if (nav[0]) c.appendChild(nav[nav.length-1].cloneNode(true));
    if (location.hash.indexOf('#comment-') == 0) cookie.comments = true;
    load_comments(cookie.comments);
});

widget.init();
resize(true);

(function(h) {
    if (h.length > 1 && (h = $(h)[0])) h.scrollIntoView(true);
})(location.hash);

// Load maintenance code.
if (cookie.maintenance) loadJS(base + 'maintenance/javascript.js');

});//onDOMLoaded

if (window == top) {
    addOnHandler(window, 'load', function(){
	// If we came from the previous page then preload the next.
	var contents = $('LINK[rel~=contents]')[0],
	    prev = $('A[rel~=prev]')[0], next = $('A[rel~=next]')[0];
	if (contents && prev && next && prev.href == document.referrer) {
	    var iframe = document.createElement('IFRAME');
	    iframe.src = next.href+'#';
	    iframe.style.display = 'none';
	    document.body.appendChild(iframe);
	}
    });
}

return {
    // Init/run this package.
    init: function (d) { KKW.data = data = d; },
    run: function (d) { KKW.init(d); run = true; },
    loaded: 0,

    // Exports.
    $: $,
    cookie: cookie,
    treewalk: treewalk,
    addOnHandler: addOnHandler,
    dataJS: dataJS,
    loadJS: loadJS,
    loadIMG: loadIMG,
    dynamicStyle: dynamicStyle,
    styleSelect: styleSelect,
    ajax: ajax,
    keepIMG: keepIMG,
    resize: resize,
    widget: widget,
    position: position,
    viewport: viewport
}})();

