+This is javascript drawing on an HTML5 canvas in real time.
+
+
+
+
+
+
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..92df917
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,16 @@
+MIT No Attribution
+
+Copyright 2003-2024 PEW's Corner
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this
+software and associated documentation files (the "Software"), to deal in the Software
+without restriction, including without limitation the rights to use, copy, modify,
+merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/LTE/active_RS.png b/LTE/active_RS.png
new file mode 100644
index 0000000..f8513fa
Binary files /dev/null and b/LTE/active_RS.png differ
diff --git a/LTE/dci_decoder.html b/LTE/dci_decoder.html
new file mode 100644
index 0000000..c24deee
--- /dev/null
+++ b/LTE/dci_decoder.html
@@ -0,0 +1,1688 @@
+
+
+
+
+
+
+
+
+LTE DCI Decoder
+
+
+
+
+
+
+
+
+
+
LTE DCI Decoder
+
(Supports all Rel-8 DCI formats)
+
+
WARNING: If this text is still visible when the page is fully loaded, scripts are disabled in your browser and this page will not work properly!
+This shows parts of a single static PNG image.
+The parts shown depend on the current time.
+
+
+
+
+
+
\ No newline at end of file
diff --git a/anaclock.png b/anaclock.png
new file mode 100644
index 0000000..3cf2fea
Binary files /dev/null and b/anaclock.png differ
diff --git a/barcodes/code128_barcode_generator.html b/barcodes/code128_barcode_generator.html
new file mode 100644
index 0000000..81e6c39
--- /dev/null
+++ b/barcodes/code128_barcode_generator.html
@@ -0,0 +1,435 @@
+
+
+
+
+
+
+
+Code 128 Barcode Generator
+
+
+
+
+
+
+
+
+
Code 128 Barcode Generator
+
+
+
+Supported characters/codes:
+
+
+
Code set A: ASCII codes 0-95 (chars 0-9, A-Z, special, control), but only codes 32-63 are currently supported.
+
Code set B: ASCII codes 32-127 (chars 0-9, A-Z, a-z, special), but only codes 32-126 are currently supported.
+
Code set C: ASCII codes 48-57 (chars 0-9 in double density).
+
+
+(Codes for code set switching/shifting and FNC codes are currently not supported, except for auto-switching from code set C
+to A for the last digit when there's an odd number of digits).
+
In this document formulas are derived for the Doppler shift in
+various cases.
+
+
+
+
Stationary Transmitter, Moving Receiver
+
+
Assume we have a medium in which waves are transmitted with a
+frequency of f0 and propagate with velocity vw relative
+to the medium. If the wave transmitter is at rest relative to the medium, the
+wavelength everywhere in the medium will be:
+
+
Equation 1:
+
+
+
+
If the receiver is moving with velocity vRx
+relative to the medium and in the direction of the transmitter, then the receiver's
+velocity relative to the waves will be:
+
+
Equation 2:
+
+
+
+
Thus, the received waves will have the apparent frequency:
+
+
Equation 3:
+
+
+
+
By substituting Equation 1 and Equation 2 into Equation 3, we obtain the following expression for the apparent frequency:
+
+
Equation 4:
+
+
+
+
This can be rewritten to express the relative Doppler shift:
+
+
Equation 5:
+
+
+
+
Keep in mind that the velocities vw and vRx
+are relative to the medium.
+
+
+
+
Moving Transmitter, Stationary Receiver
+
+
Now let's assume that the transmitter is moving with
+velocity vTx relative to the medium and towards the receiver, which
+is at rest relative to the medium. The wavelength in front of the transmitter
+will be reduced by the distance it travels during one wave period T0
+(=1/f0), so the wavelength becomes:
+
+
Equation 6:
+
+
+
+
The received waves will therefore have the apparent
+frequency:
+
+
Equation 7:
+
+
+
+
By substituting Equation 6 into Equation 7, we obtain the following expression for the apparent frequency:
+
+
Equation 8:
+
+
+
+
And the relative Doppler shift becomes:
+
+
Equation 9:
+
+
+
+
When vTx is small compared to vw, Equation
+8 and Equation 9 become similar to the stationary transmitter case (Equation 4 and Equation 5) as shown below:
+
+
Equation 10:
+
+
+
+
Equation 11:
+
+
+
+
+
+
Moving Transmitter, Moving Receiver
+
+
If both the transmitter and the receiver are moving relative
+to the medium and towards each other with the velocities vTx and vRx,
+respectively, relative to the medium, then the wavelength λ will be
+described by Equation 6, and the receiver's velocity v relative to the waves will be described by Equation 2. For the receiver the waves will therefore have the apparent frequency:
+
+
Equation 12:
+
+
+
+
and the relative Doppler shift:
+
+
Equation 13:
+
+
+
+
When vRx and vTx are small compared to vw,
+these equations can be simplified to:
+
+
Equation 14:
+
+
+
+
Equation 15:
+
+
+
+
Note that vTx and vRx should have the
+same sign when they are pointed in opposite directions. vw should always
+be positive.
+
+
+
+
Electromagnetic Waves
+
+
For electromagnetic waves it is not necessary to relate the
+motion of the transmitter, receiver, and waves to a medium. Only the velocities
+relative to the receiver are important. If the velocity vTx of the
+transmitter relative to the receiver (and towards it) is much smaller than the velocity
+vw of the waves (which is 299,792,458 m/s in a vacuum), we can reuse Equation 10
+and Equation 11, regardless of whether the receiver is moving or not:
+
+
Equation 16:
+
+
+
+
Equation 17:
+
+
+
+
Remember that in this case vTx is the transmitter's velocity relative to the
+receiver - not relative to the medium. Equation 16 and Equation 17 could of course also
+be obtained by setting vRx=0 in Equation 14 and Equation 15, respectively.
+
+
If the transmitter velocity vTx is not small compared to the electromagnetic
+wave velocity vw, relativistic effects (according to
+Einstein's Theory of Relativity)
+will be non-negligible and need to be taken into account. In this case,
+time dilation
+will reduce the transmitted frequency f0 in Equation 8 by the Lorentz factor γ
+when observed from the receiver's frame of reference (before accounting for the Doppler shift).
+The Lorentz factor is determined by the transmitter's velocity vTx relative to the
+receiver as follows:
+
+
Equation 18:
+
+
+
+
Replacing f0 in Equation 8 with the time dilated value f0/γ,
+we get the combined result of time dilation and Doppler shift:
+
+
Equation 19:
+
+
+
+
Equation 20:
+
+
+
+
(vTx is the transmitter's velocity relative to the receiver and towards the receiver,
+and vw is the electromagnetic wave velocity).
Note: This is Q amplitude / I amplitude, converted to dB.
+
+
Phase mismatch
+
°
+
+
Note: This is Q phase - (I phase - 90°).
+
+
Resulting unwanted signal
+
dB
+
+
Note: This is magnitude of unwanted spectral component (at frequency -f) relative to wanted component (at frequency f), in dB.
+
+
+
+
+
+
+
+
I
+
Q
+
+
+
+
+
+
+
f
+
mag(dB)
+
+
+
+
+
+
+
+
+
+
+
diff --git a/iq_mismatch_theory/doc.html b/iq_mismatch_theory/doc.html
new file mode 100644
index 0000000..04ac12f
--- /dev/null
+++ b/iq_mismatch_theory/doc.html
@@ -0,0 +1,183 @@
+
+
+
+
+
+
+
+I/Q Signal Mismatch Theory
+
+
+
+
+
+
+
+
+
I/Q Signal Mismatch Theory
+
+
+
+
Introduction
+
+
This document describes the impact of amplitude and phase
+mismatch in an I/Q (in-phase/quadrature-phase) signal pair. The result of a mismatch is an
+unwanted spectral component at the negative signal frequency.
+
+
+
+
Theory
+
+
An ideal sinusoidal I/Q signal pair with no amplitude or
+phase mismatch can be represented by the following complex function:
+
+
Equation 1:
+
+
y(t) = A exp(i(ωt + φ)) =
+A cos(ωt + φ) + i A sin(ωt + φ)
+
+
The real part (the first term in the second expression) of
+y(t) is the I signal, and the imaginary part (the second term except the "i")
+is the Q signal. ω, A, and φ are the angular frequency, amplitude and
+phase, respectively.
+
+
If an amplitude and phase mismatch is introduced, the
+function becomes:
+
+
Equation 2:
+
+
y(t) = A cos(ωt + φ) +
+i α A sin(ωt + φ + ε)
+
+
Here, A and φ are the amplitude and phase of the I
+signal, α is the amplitude mismatch (the ratio of Q amplitude to I
+amplitude), and ε is the phase mismatch (the Q phase plus 90 degrees minus the I
+phase). For α=0 dB (α=1) and ε=0 degrees there is no mismatch.
+
+
By rewriting Equation 2 we can split the mismatched I/Q signal into its spectral components:
The first term is the wanted signal (including both I and Q
+parts) - a pure complex sinusoid at the frequency ω. The second term is an
+unwanted signal at the negative frequency -ω. The wanted signal is simply
+the ideal I/Q signal from Equation 1 multiplied by a complex constant, which modifies the amplitude and phase (here, the I and Q parts are modified equally).
+
+
From Equation 3 the ratio of the unwanted signal magnitude to the wanted signal magnitude can be found:
+
+
Equation 4:
+
+
+
+
+
+
Conclusion
+
+
It has been shown that the introduction of an amplitude
+and/or phase mismatch between the I and Q signals causes an unwanted spectral
+component to appear at the mirror frequency. Also, the amplitude and phase of
+the wanted spectral component will change. Equation 3 quantifies these effects.
+Pronounced like the first part of the "ay"-sound in "say", i.e. without the final "y"-sound.
+The "e" in "get" is also a fair approximation.
+The German "ä" and the Danish "æ" are better approximations.
+
+
+
ei/ee
+
+Pronounced as a Japanese "e" (see above), but twice as long in duration.
+Note that in hiragana text this long vowel often occurs when a syllable (e.g. "ke") is
+followed by an "i". In such cases the "i" is misleading and should not be pronounced as an "i" -
+it only serves to prolong the "e" sound.
+
+
+
i
+
+Pronounced like the "ee" in "feet", but short in duration.
+It is NOT pronounced like the "i" in "him".
+Often the "i" is not pronounced in the syllable "shi" when it is followed by another syllable.
+
+
+
o
+
+Pronounced like the first part of the "o"-sound in "old", i.e. without the final "u"-sound.
+The Danish "å" is also a good approximation.
+
+
+
ou/oo
+
+Pronounced as a Japanese "o" (see above), but twice as long in duration.
+Note that in hiragana text this long vowel often occurs when a syllable (e.g. "ko") is
+followed by a "u". In such cases the "u" is misleading and should not be pronounced as a "u" -
+it only serves to prolong the "o" sound.
+
+
+
g
+
+Pronounced like the "g" in "get" when it occurs at the beginning of words.
+Pronounced like the "ng" in "sing" in all other cases.
+Some people always pronounce it like the "g" in "get".
+
+
+
n
+
+Pronounced like "n" at the beginning of syllables,
+or when followed by any of the consonants ch, d, j, n, r, t, z, and sometimes s(h)
+(even when these consonants belong to the next word) -
+note that these consonants are all produced with the tip of the tongue (except s(h)).
+Pronounced like "m" when followed by m, b, or p (these consonants are all produced with the lips).
+Pronounced like the "ng" in "sing" in all other cases.
+
+
+
r
+
+Pronounced almost like the "rd" in "weirdo" (with an American-style "r").
+
+
+
z
+
+Pronounced like "dz" at the beginning of words.
+Pronounced like "z" in all other cases.
+
+
Octave defaults to same as for previous note (or rest) in same sequence (initially 4)
+
Relative duration: c1 c c/2 c c3/8 (whole, whole, half, half, 3/8)
+
Relative duration defaults to same as for previous note in same sequence (initially 1/4)
+
Absolute duration: c2500*1/4 c (2 quarter notes at 2500 ms per whole note)
+
Absolute duration defaults to same as for previous note in same sequence (initially 2000)
+
Rests: r1 r r/2 (whole, whole, and half rests)
+
Tracks: 1: c d e 2: g a h (track 1 plays c d e while track 2 plays g a h in parallel, default track is 0)
+
Tracks: 1: c d 2: g a h 1: e (same as above, except track 1 is defined in 2 parts)
+
Instruments: 1(2): c d (track 1 uses instrument 2, default instrument is 0)
+
Named sequences: hiho: g a 1: c1 1 hiho d (track 1 uses itself and hiho sequence and becomes: c1 c1 g/4 a/4 d1)
+
A reference to a named sequence is replaced by all notes in the sequence at the point of
+ reference, i.e. not including notes added to the sequence later
+
Volume: v0.5 c d v1 c d (notes c and d at half volume, then at full (auto-scaled) volume)
+
Comments: /* Multi-line comment */ c d // Single-line comment
+
+
+
+
diff --git a/page.js b/page.js
new file mode 100644
index 0000000..882cb90
--- /dev/null
+++ b/page.js
@@ -0,0 +1,550 @@
+"use strict";
+
+var genericPageSetup =
+{
+ debug: false,
+
+ script_version: '7',
+ style_version: undefined,
+
+ // A web page can add extra menu buttons to this array before the page load event
+ extra_menu_buttons: [],
+
+ // Page URL with and without the hash/query part
+ this_page_url: undefined,
+ this_page_noarg_url: undefined,
+
+ // Page header related variables
+ header_elem: undefined,
+ header_height: undefined,
+ has_scrolled: false,
+ last_scroll_pos: 0,
+ header_is_hidden: false,
+
+ // Page menu related variables
+ menu_group_elem: undefined,
+ menu_is_open: false,
+ pending_menu_action: undefined,
+ // Named history state values
+ menu_closed_history_state: null,
+ menu_open_history_state: 1,
+ // Menu button DOM elements and visibility states
+ update_pg_addr_menu_button_info:
+ {
+ id: 'update_pg_addr_button',
+ button_elem: undefined,
+ is_visible: false,
+ },
+ reset_pg_addr_menu_button_info:
+ {
+ id: 'reset_pg_addr_button',
+ button_elem: undefined,
+ is_visible: false,
+ },
+
+ // Relative URL path from the current page to the web site root (the location of this script is used as the root);
+ // includes a trailing '/' unless empty (meaning that the current page is in the root)
+ rel_path_to_root: (function pg_rel_path_to_root()
+ {
+ var scripts = document.getElementsByTagName('script');
+ var re = new RegExp('^(.*?[^/])(?:/(?!/)|$)(.*)');
+ var matches = re.exec(scripts[scripts.length - 1].src);
+ var this_script_url_parts = [matches[1]].concat(matches[2].split('/')).slice(0, -1);
+ matches = re.exec(location.href);
+ var this_page_url_parts = [matches[1]].concat(matches[2].split('/')).slice(0, -1);
+ var smallest_nbr_parts = Math.min(this_script_url_parts.length, this_page_url_parts.length);
+ for (var nbr_common_parts = 0;
+ nbr_common_parts < smallest_nbr_parts && this_script_url_parts[nbr_common_parts] == this_page_url_parts[nbr_common_parts];
+ nbr_common_parts++);
+ var nbr_backout_levels = (nbr_common_parts == 0 ? 0 : this_page_url_parts.length - nbr_common_parts);
+ var rel_path =
+ new Array(nbr_backout_levels + 1).join('../') +
+ this_script_url_parts.slice(nbr_common_parts).join('/');
+ if (rel_path.length > 0 && rel_path.slice(-1) != '/')
+ rel_path += '/';
+ return rel_path;
+ })(),
+
+ // Handles the page load event for the purpose of creating a page header and setting up related event handlers
+ init: function pg_init()
+ {
+ this.style_version = getComputedStyle(document.documentElement).getPropertyValue('--style-version').trim() || '?';
+ if (this.debug) console.log('Page loaded (script v' + this.script_version + ', style v' + this.style_version + ')');
+ this.refresh_page_info();
+ // Disable the browser's automatic restoring of the scroll position when navigating the history (sometimes it
+ // doesn't work, and sometimes it gets in the way) - we'll handle this ourselves
+ history.scrollRestoration = 'manual';
+ this.make_header();
+ var that = this;
+ window.addEventListener('scroll', function(){that.scroll_handler()}, false);
+ setInterval(function(){that.time_handler()}, 200);
+ var nav_event_handler = function(e){that.navigation_handler(e)};
+ // Register a handler for the pageshow event (comes after the load event - or without the load event - when
+ // navigating from a different page)
+ window.addEventListener('pageshow', nav_event_handler, false);
+ // Register a handler for the popstate event (comes when navigating from a different history entry for the same
+ // page - and for some older browsers it also comes after the pageshow event when navigating from a different
+ // page)
+ window.addEventListener('popstate', nav_event_handler, false);
+ },
+
+ refresh_page_info: function pg_refresh_page_info()
+ {
+ this.this_page_url = location.href;
+ this.this_page_noarg_url = this.this_page_url.split('#')[0].split('?')[0];
+ this.set_menu_button_visible(this.reset_pg_addr_menu_button_info,
+ this.this_page_url != this.this_page_noarg_url);
+ },
+
+ // Opens the page menu
+ open_menu: function pg_open_menu()
+ {
+ this.menu_is_open = true;
+ this.menu_group_elem.style.display = 'block';
+ if (this.debug) console.log('Adding and going to menu-open state');
+ // Create and go to the menu-open history state
+ history.pushState({state: this.menu_open_history_state, scroll: window.pageYOffset}, '');
+ },
+
+ // Closes the page menu if it is open, and ensures that all state reflects that it is closed
+ close_menu: function pg_close_menu()
+ {
+ this.menu_is_open = false;
+ this.menu_group_elem.style.display = 'none';
+ if (history.state.state == this.menu_open_history_state)
+ {
+ // We're in the menu-open history state, so go back to the menu-closed history state
+ if (this.debug) console.log('Going back');
+ history.back();
+ }
+ },
+
+ update_pg_addr_menu_button: [
+ 'update_pg_addr_button',
+ 'Update page address with setup',
+ 'genericPageSetup.update_page_addr_with_settings()'
+ ],
+
+ reset_pg_addr_menu_button: [
+ 'reset_pg_addr_button',
+ 'Reset page address',
+ 'genericPageSetup.reset_page_addr()'
+ ],
+
+ feedback_menu_button: [
+ 'feedback_button',
+ 'Feedback',
+ 'genericPageSetup.open_feedback_form()',
+ ],
+
+ // Performs the action for the 'Update page address with setup' menu button
+ update_page_addr_with_settings: function pg_update_page_addr_with_settings()
+ {
+ var url_with_settings = this.make_url_from_settings();
+ if (this.debug) console.log('update_pg_addr_with_settings: ' + url_with_settings);
+ history.replaceState(history.state, '', url_with_settings);
+ this.refresh_page_info();
+ },
+
+ // Performs the action for the 'Reset page address' menu button
+ reset_page_addr: function pg_reset_page_addr()
+ {
+ history.replaceState(history.state, '', this.this_page_noarg_url);
+ this.refresh_page_info();
+ },
+
+ // Performs the action for the 'Feedback' menu button
+ open_feedback_form: function pg_open_feedback_form()
+ {
+ var info = this.this_page_url + ' (modified ' + document.lastModified +
+ ', script v' + this.script_version +
+ ', style v' + this.style_version + ')\n';
+ window.open(this.rel_path_to_root + 'feedback.html#' + encodeURIComponent(info), '_blank');
+ },
+
+ // Makes the specified menu button visible or invisible as specified, or applies the previously set visibility if
+ // 'visible' is not true or false
+ set_menu_button_visible: function pg_set_menu_button_visible(button_info, visible)
+ {
+ if (this.debug) console.log('Requested visibility of ' + button_info.id + ': ' + visible);
+ if (visible === true || visible === false)
+ {
+ button_info.is_visible = visible;
+ }
+ if (button_info.button_elem)
+ {
+ if (this.debug) console.log('Setting visibility of ' + button_info.id + ' to ' + button_info.is_visible);
+ button_info.button_elem.style.display = button_info.is_visible ? 'block' : 'none';
+ }
+ },
+
+ // Handles click events on menu item buttons
+ menu_item_click_handler: function pg_menu_item_click_handler(event)
+ {
+ // Prepare the menu item action for execution; the sequence of events is:
+ // 1. The click event triggers this handler which prepares the action.
+ // 2. The same click event then triggers the click handler on the underlying menu_group_elem.
+ // 3. The menu_group_elem click handler closes the menu and goes back to the menu-closed history state.
+ // 4. This triggers the history popstate event which triggers the navigation_handler.
+ // 5. The navigation_handler executes the pending action prepared in step 1.
+ // This sequence ensures that the browser is back in the normal menu-closed state before the action is executed.
+ this.pending_menu_action = event.target.dataset.action;
+ },
+
+ // Handles key events when the menu is open, for the purpose of closing the menu when the Esc key is pressed
+ menu_key_down_handler: function pg_menu_key_down_handler(event)
+ {
+ // This handler is attached to the page body element and receives events in the capturing phase (not the usual
+ // bubbling phase which comes after the capturing phase), so it receives events before anything else on the page
+ if (this.menu_is_open && event.key == 'Escape')
+ {
+ if (this.debug) console.log('Esc pressed');
+ this.close_menu();
+ // Prevent the key event from reaching any other element on the page
+ event.stopPropagation();
+ }
+ },
+
+ // Handles navigation events for the purpose of closing the menu, executing a pending menu action if any, and
+ // generally avoiding the menu-open history state (which should never be entered on user navigation)
+ navigation_handler: function pg_navigation_handler(event)
+ {
+ // This navigation handler is called on every navigation event (pageshow or popstate) triggered by the user or
+ // by this script, but not on history.pushState which does not trigger any events
+ this.refresh_page_info();
+ if (history.state == null)
+ {
+ history.replaceState({state: this.menu_closed_history_state, scroll: window.pageYOffset}, '');
+ }
+ else if (history.state.state == this.menu_closed_history_state)
+ {
+ var that = this;
+ var old_scroll = history.state.scroll;
+ setTimeout(function(){that.scroll_and_hide_header(old_scroll)});
+ }
+ var pending_menu_action = this.pending_menu_action;
+ this.pending_menu_action = undefined;
+ if (this.debug) console.log('nav_hndlr from ' + event.type + ': (' + history.state.state + ', ' + history.state.scroll + ') ' +
+ pending_menu_action + ' ' + this.this_page_url);
+ // Ensure that the menu is closed in all cases where the navigation handler is called (the only case where we
+ // want the menu to stay open is when it is deliberately opened via history.pushState, and that will not trigger
+ // any call to the navigation handler)
+ this.close_menu();
+ if (pending_menu_action)
+ {
+ // There's a pending action from a menu item selected by the user, so execute that action now
+ setTimeout(function(){eval(pending_menu_action)}, 0);
+ }
+ },
+
+ // Handles scroll events for the purpose of showing or hiding the header depending on the scroll direction (most of
+ // this functionality is implemented in the timer_handler to avoid degrading the browser's scroll performance)
+ scroll_handler: function pg_scroll_handler()
+ {
+ this.has_scrolled = true;
+ },
+
+ // Handles regular timer ticks for the purpose of showing/hiding the header if scroll events have occurred since the
+ // last tick, and for storing the scroll position in the current history entry for later restoration when navigating
+ // back to this history entry
+ time_handler: function pg_time_handler()
+ {
+ if (this.has_scrolled)
+ {
+ this.has_scrolled = false;
+ var new_scroll_pos = window.pageYOffset;
+ history.replaceState({state: history.state.state, scroll: new_scroll_pos}, '');
+ if (!this.menu_is_open && !this.header_is_hidden &&
+ new_scroll_pos > this.last_scroll_pos && new_scroll_pos > this.header_height)
+ {
+ // The header is visible, the menu is closed, and the page has scrolled down beyond the height of the
+ // header, so hide the header
+ this.header_elem.className = 'hidden';
+ this.header_is_hidden = true;
+ } else if (this.header_is_hidden && new_scroll_pos <= this.last_scroll_pos)
+ {
+ // The header is hidden and the page has scrolled up (or "scrolled" to the same position, which will
+ // only happen when navigation to a page causes scroll_and_hide_header to "scroll" to the top and the
+ // scroll position is already at the top), so reveal the header
+ this.header_elem.className = '';
+ this.header_is_hidden = false;
+ }
+ this.last_scroll_pos = new_scroll_pos;
+ }
+ },
+
+ // Creates the page header and menu
+ make_header: function pg_make_header()
+ {
+ var body_elem = document.getElementsByTagName('body')[0];
+
+ var footer_elem = body_elem.appendChild(document.createElement('div'));
+ footer_elem.classList.add('footer');
+ footer_elem.innerHTML = 'Terms<\/a>';
+
+ this.header_elem = body_elem.appendChild(document.createElement('header'));
+
+ var hs = '