intranet-core 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +38 -0
  3. data/lib/core_extensions.rb +12 -0
  4. data/lib/core_extensions/string.rb +43 -0
  5. data/lib/core_extensions/tree.rb +84 -0
  6. data/lib/core_extensions/webrick/httpresponse.rb +22 -0
  7. data/lib/intranet/abstract_responder.rb +34 -0
  8. data/lib/intranet/core.rb +125 -0
  9. data/lib/intranet/core/builder.rb +98 -0
  10. data/lib/intranet/core/haml_wrapper.rb +60 -0
  11. data/lib/intranet/core/locales.rb +47 -0
  12. data/lib/intranet/core/servlet.rb +42 -0
  13. data/lib/intranet/core/version.rb +8 -0
  14. data/lib/intranet/logger.rb +38 -0
  15. data/lib/intranet/resources/haml/http_error.haml +27 -0
  16. data/lib/intranet/resources/haml/skeleton.haml +52 -0
  17. data/lib/intranet/resources/haml/title_and_breadcrumb.haml +8 -0
  18. data/lib/intranet/resources/locales/en.yml +46 -0
  19. data/lib/intranet/resources/locales/fr.yml +46 -0
  20. data/lib/intranet/resources/www/background.jpg +0 -0
  21. data/lib/intranet/resources/www/error.png +0 -0
  22. data/lib/intranet/resources/www/favicon.ico +0 -0
  23. data/lib/intranet/resources/www/fonts/SourceSansPro.woff2 +0 -0
  24. data/lib/intranet/resources/www/nav.js +25 -0
  25. data/lib/intranet/resources/www/style.css +306 -0
  26. data/spec/core_extensions/string_spec.rb +135 -0
  27. data/spec/core_extensions/tree_spec.rb +208 -0
  28. data/spec/core_extensions/webrick/httpresponse_spec.rb +43 -0
  29. data/spec/intranet/core/fr.yml +5 -0
  30. data/spec/intranet/core/haml_wrapper_spec.rb +70 -0
  31. data/spec/intranet/core/locales_spec.rb +74 -0
  32. data/spec/intranet/core_spec.rb +403 -0
  33. data/spec/intranet/logger_spec.rb +129 -0
  34. data/spec/spec_helper.rb +50 -0
  35. data/spec/test_responder/responder.rb +42 -0
  36. data/spec/test_responder/www/style.css +5 -0
  37. metadata +218 -0
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'haml'
4
+
5
+ module Intranet
6
+ class Core
7
+ # Wrapps the Haml engine from the Haml gem.
8
+ # This module has to be included in any class wanting to generate a HTML/XML
9
+ # from HAML templates.
10
+ module HamlWrapper
11
+ # @!visibility protected
12
+ # The default path to the HAML files.
13
+ DEFAULT_HAML_DIR = File.join(__dir__, '..', 'resources', 'haml')
14
+
15
+ class << self
16
+ # @!visibility protected
17
+ # Haml load path (not to be modified directly)
18
+ attr_accessor :load_path
19
+ end
20
+
21
+ # @!visibility protected
22
+ # Initializes HAML templates load path.
23
+ # @param default_haml_dir [String] The path to the directory containing default HAML
24
+ # templates.
25
+ def self.initialize(default_haml_dir = DEFAULT_HAML_DIR)
26
+ self.load_path = [default_haml_dir]
27
+ end
28
+
29
+ # @!visibility protected
30
+ # Adds a HAML templates directory.
31
+ # @param dir [String] The path to the directory to add.
32
+ def self.add_path(dir)
33
+ HamlWrapper.load_path.unshift(dir) # prepend load_path
34
+ end
35
+
36
+ # Generate the HTML/XML from a given HAML template.
37
+ # @param template [String] The name of the HAML template to use.
38
+ # @param content [Hash] The additional content required to generate the HTML/XML.
39
+ # @return [String] The HTML/XML content.
40
+ def to_markup(template, content = {})
41
+ fd = File.read(find_template(template))
42
+ Haml::Engine.new(fd, escape_html: false).render(self, content)
43
+ rescue Errno::ENOENT # template file does not exists
44
+ ''
45
+ end
46
+
47
+ private
48
+
49
+ # Look for an HAML template in the registered paths.
50
+ # @return [String] The path to the template if it exists, an empty string otherwise.
51
+ def find_template(template)
52
+ HamlWrapper.load_path.each do |dir|
53
+ candidate = File.join(dir, template + '.haml')
54
+ return candidate if File.exist?(candidate)
55
+ end
56
+ ''
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'i18n'
4
+
5
+ module Intranet
6
+ class Core
7
+ # @!visibility protected
8
+ # Wrapps the initialisation of the i18n internationalization gem.
9
+ module Locales
10
+ # The default path to the locales files.
11
+ DEFAULT_LOCALES_DIR = File.join(__dir__, '..', 'resources', 'locales')
12
+
13
+ # Initializes translation units.
14
+ # @param default_locales_dir [String] The path to the directory containing default translation
15
+ # units.
16
+ def self.initialize(default_locales_dir = DEFAULT_LOCALES_DIR)
17
+ I18n.load_path = translation_files(default_locales_dir)
18
+ I18n.available_locales = available_locales(default_locales_dir)
19
+ begin
20
+ I18n.default_locale = ENV['LANG'].split('.')[0].split('_')[0].to_sym
21
+ rescue NoMethodError, I18n::InvalidLocale
22
+ I18n.default_locale = :en
23
+ end
24
+ end
25
+
26
+ # Adds a translation units directory.
27
+ # @param dir [String] The path to the directory to add.
28
+ def self.add_path(dir)
29
+ I18n.load_path = Dir.glob(File.join(dir, '*.yml')) + I18n.load_path # prepend load_path
30
+ end
31
+
32
+ # private
33
+
34
+ def self.translation_files(dir)
35
+ Dir.glob(File.join(dir, '*.yml'))
36
+ end
37
+ private_class_method :translation_files
38
+
39
+ def self.available_locales(dir)
40
+ translation_files(dir).map do |file|
41
+ File.basename(file, File.extname(file)).to_sym
42
+ end
43
+ end
44
+ private_class_method :available_locales
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+ require 'webrick'
5
+ require 'webrick/httpstatus'
6
+
7
+ module Intranet
8
+ class Core
9
+ # @!visibility protected
10
+ # WEBrick servlet for the Intranet.
11
+ class Servlet < WEBrick::HTTPServlet::AbstractServlet
12
+ # Initializes a new Servlet instance.
13
+ # @param server [WEBrick::HTTPServer] The web server.
14
+ # @param builder [Intranet::Core::Builder] The web pages builder.
15
+ def initialize(server, builder)
16
+ super(server)
17
+ @builder = builder
18
+ end
19
+
20
+ # Processes a GET request issued to the web server.
21
+ # @param request [WEBrick::HTTPRequest] The GET request.
22
+ # @param response [WEBrick::HTTPResponse] Fields +status+, +content_type+ and +body+ are
23
+ # filled with the server response.
24
+ def do_GET(request, response) # rubocop:disable Naming/MethodName
25
+ # See https://github.com/nahi/webrick/blob/master/lib/webrick/httprequest.rb
26
+ # We could use request.request_uri (which is a URI object) but its path is not normalized
27
+ # (it may contain '../' or event start with '..'). Hence we use request.past which has been
28
+ # normalized with HTTPUtils::normalize_path, and request.query for the URL parameters.
29
+ path = request.path
30
+ path += 'index.html' if path[-1] == '/' # path is normalized (ie. does not contain '../')
31
+
32
+ status, content_type, body = @builder.do_get(path, request.query)
33
+
34
+ raise WEBrick::HTTPStatus[status] if WEBrick::HTTPStatus.error?(status)
35
+
36
+ response.status = status
37
+ response.content_type = content_type
38
+ response.body = body
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Intranet
4
+ class Core
5
+ # The version of the Intranet core, according to semantic versionning.
6
+ VERSION = '1.0.0'
7
+ end
8
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'webrick'
4
+
5
+ module Intranet
6
+ # The default logger for the Intranet. It is based on +WEBrick::BasicLog+ but adds timestamp and
7
+ # colors to the messages.
8
+ class Logger < WEBrick::BasicLog
9
+ COLOR = {
10
+ FATAL => "\033[1;37;41m",
11
+ ERROR => "\033[0;31m",
12
+ WARN => "\033[0;33m",
13
+ INFO => "\033[0;36m",
14
+ DEBUG => "\033[0;35m"
15
+ }.freeze
16
+ private_constant :COLOR
17
+
18
+ # Initializes a new logger for $stderr that outputs messages at +level+ or higher.
19
+ # +level+ can be modified later (attribute accessor).
20
+ # @param level The initial log level.
21
+ def initialize(level = INFO)
22
+ super($stderr, level)
23
+ end
24
+
25
+ # Logs a message at a given level if it is above the current log level.
26
+ # @param level The level of the message
27
+ # @param msg [String] The message
28
+ def log(level, msg)
29
+ super(level, COLOR[level] + Time.now.strftime('[%Y-%m-%d %H:%M:%S] ') + msg + "\033[0m")
30
+ end
31
+
32
+ # Logs an object that responds to +to_s+ at level INFO.
33
+ # @param obj [Object] The object to log.
34
+ def <<(obj)
35
+ info(obj.to_s.chomp)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,27 @@
1
+ !!! 5
2
+ %html
3
+ %head
4
+ %title
5
+ = HTMLEntities.new.encode(I18n.t('nav.error.http'), :named) + ' ' + error.to_s + ' | '
6
+ = HTMLEntities.new.encode(I18n.t('nav.error.http_' + error.to_s + '.short'), :named)
7
+ %meta{charset: 'utf-8'}
8
+ %link{rel: 'icon', type: 'image/x-icon', href: '/design/favicon.ico'}
9
+ %link{rel: 'stylesheet', type: 'text/css', href: '/design/style.css'}
10
+ %body
11
+ %header
12
+ %h1
13
+ %a{href: '/index.html'}= Socket.gethostname.capitalize
14
+ %main
15
+ %section#error
16
+ %h2
17
+ = HTMLEntities.new.encode(I18n.t('nav.error.http'), :named) + ' ' + error.to_s
18
+ %br
19
+ = HTMLEntities.new.encode(I18n.t('nav.error.http_' + error.to_s + '.short'), :named)
20
+ %p
21
+ %img{src: '/design/error.png', alt: ''}
22
+ %br
23
+ = HTMLEntities.new.encode(I18n.t('nav.error.http_' + error.to_s + '.full'), :named)
24
+ %br
25
+ %br
26
+ %a{href: '/index.html'}= HTMLEntities.new.encode(I18n.t('nav.back.home'), :named)
27
+ %footer
@@ -0,0 +1,52 @@
1
+ !!! 5
2
+ %html
3
+ %head
4
+ %title= Socket.gethostname.capitalize + ' | ' + body[:title]
5
+ %meta{charset: 'utf-8'}
6
+ %link{rel: 'icon', type: 'image/x-icon', href: '/design/favicon.ico'}
7
+ - css.each do |url|
8
+ %link{rel: 'stylesheet', type: 'text/css', href: url}
9
+ - js.each do |url|
10
+ %script{src: url, defer: 'defer'}
11
+ %body
12
+ %header
13
+ %a{id: 'openmenu', onclick: 'openNavMenu();'}= '&#9776;'
14
+ %h1
15
+ %a{href: '/index.html', title: I18n.t('nav.back.home')}= Socket.gethostname.capitalize
16
+ %nav
17
+ %a{id: 'closemenu', onclick: 'closeNavMenu();'}= '&times;'
18
+ %ul
19
+ - responders.children_nodes.each do |child_id, child_node|
20
+ %li
21
+ - if child_node.children? # Create a dropdown menu entry
22
+ %a= I18n.t(child_id + '.menu')
23
+ %ul
24
+ - child_node.children_nodes.each do |child_url, node|
25
+ %li
26
+ %a{href: '/' + child_id + '/' + child_url + '/index.html'}
27
+ = I18n.t(child_id + '.' + child_url + '.menu')
28
+ - else # Create a regular menu entry
29
+ %a{href: '/' + child_id + '/index.html'}= I18n.t(child_id + '.menu')
30
+
31
+ - if is_home
32
+ %aside
33
+
34
+ %main
35
+ = body[:content]
36
+
37
+ %footer
38
+ %p
39
+ = I18n.t('nav.generated.page') + ' ' + I18n.t('nav.generated.on_host') + ' '
40
+ %em= Socket.gethostname
41
+ = ' ' + I18n.t('nav.generated.on_date') + ' ' + Time.now.strftime(I18n.t('date_format'))
42
+ = ' ' + I18n.t('date_time_separator') + Time.now.strftime(I18n.t('time_format'))
43
+ %br
44
+ %a{onclick: 'openModal();'}= I18n.t('nav.about')
45
+ %aside#modal
46
+ %div#modal-content
47
+ %h2= 'Intranet'
48
+ %h3
49
+ = 'core' + '&nbsp;&nbsp;'
50
+ %em= Intranet::Core::VERSION
51
+ %p
52
+ %a{href: 'https://bitbucket.org/linuxtools/intranet.git', target: '_blank'}= 'https://bitbucket.org/linuxtools/intranet.git'
@@ -0,0 +1,8 @@
1
+ %h2= title
2
+ %ul.breadcrumb
3
+ - nav.each do |name, url|
4
+ - if url.nil?
5
+ %li= name
6
+ - else
7
+ %li
8
+ %a{ href: url }= name
@@ -0,0 +1,46 @@
1
+ # en.yml - Translation file for the IntraNet in en-GB/en-US
2
+ ---
3
+
4
+ en:
5
+ # Date and time format, as inputs to strftime
6
+ date_format: '%Y-%m-%d'
7
+ time_format: '%H:%M'
8
+ date_time_separator: ''
9
+
10
+ # Translations for navigation elements
11
+ nav:
12
+ home: 'Home'
13
+ about: 'About'
14
+ back:
15
+ home: 'Back to the homepage'
16
+ generated:
17
+ page: 'Page generated'
18
+ on_host: 'on'
19
+ on_date: 'on'
20
+ error:
21
+ http: 'Error'
22
+ http_400: {short: 'Bad Request', full: 'The request could not be understood by the server.'}
23
+ http_401: {short: 'Unauthorized', full: 'The requested page requires user authentication.'}
24
+ http_402: {short: 'Payment Required', full: 'The requested page requires payment.'}
25
+ http_403: {short: 'Forbidden', full: 'Access to the requested page is forbidden.'}
26
+ http_404: {short: 'Not Found', full: 'The requested page cannot be found.'}
27
+ http_405: {short: 'Method Not Allowed', full: 'The method specified in the request is not allowed for this resource.'}
28
+ http_406: {short: 'Not Acceptable', full: 'The request could not be accepted by the server.'}
29
+ http_407: {short: 'Proxy Authentication Required', full: 'The requested page requires user authentication with the proxy.'}
30
+ http_408: {short: 'Request Timeout', full: 'The server did not receive the request in the expected delay.'}
31
+ http_409: {short: 'Conflict', full: 'The request could not be completed due to a conflict with the current state of the resource.'}
32
+ http_410: {short: 'Gone', full: 'The requested resource is no longer available at the server and no forwarding address is known.'}
33
+ http_411: {short: 'Length Required', full: 'The request could not be understood by the server.'}
34
+ http_412: {short: 'Precondition Failed', full: 'The request could not be completed by the server.'}
35
+ http_413: {short: 'Request Entity Too Large', full: 'The request could not be completed by the server.'}
36
+ http_414: {short: 'Request-URI Too Long', full: 'The request could not be completed by the server.'}
37
+ http_415: {short: 'Unsupported Media Type', full: 'The request could not be completed by the server.'}
38
+ http_416: {short: 'Requested Range Not Satisfiable', full: 'The request could not be completed by the server.'}
39
+ http_417: {short: 'Expectation Failed', full: 'The request could not be completed by the server.'}
40
+ http_500: {short: 'Internal Server Error', full: 'The server encountered an unexpected error and is unable to fulfill the request.'}
41
+ http_501: {short: 'Not Implemented', full: 'The server does not support the functionality required to fulfill the request.'}
42
+ http_502: {short: 'Bad Gateway', full: 'The server received an invalid response from the upstream server.'}
43
+ http_503: {short: 'Service Unavailable', full: 'The server is temporary unable to handle the request. Please try again later.'}
44
+ http_504: {short: 'Gateway Timeout', full: 'The server did not receive a response from the upstream server in the expected delay.'}
45
+ http_505: {short: 'HTTP Version Not Supported', full: 'The server does not support the HTTP protocol version that was used in the request message.'}
46
+ load: 'Load error!'
@@ -0,0 +1,46 @@
1
+ # fr.yml - Translation file for the IntraNet in fr-FR
2
+ ---
3
+
4
+ fr:
5
+ # Date and time format, as inputs to strftime
6
+ date_format: '%d/%m/%Y'
7
+ time_format: '%Hh%M'
8
+ date_time_separator: 'à '
9
+
10
+ # Translations for navigation elements
11
+ nav:
12
+ home: 'Accueil'
13
+ about: 'À propos'
14
+ back:
15
+ home: 'Retour à l''accueil'
16
+ generated:
17
+ page: 'Page générée'
18
+ on_host: 'sur'
19
+ on_date: 'le'
20
+ error:
21
+ http: 'Erreur'
22
+ http_400: {short: 'Requête malformée', full: 'La syntaxe de la requête est erronée.'}
23
+ http_401: {short: 'Authentification requise', full: 'La page demandée requiert une authentification.'}
24
+ http_402: {short: 'Paiement requis', full: 'La page demandée requiert un paiement.'}
25
+ http_403: {short: 'Accès interdit', full: 'L''accès à la page demandée est interdit.'}
26
+ http_404: {short: 'Page non trouvée', full: 'La page demandée n''existe pas.'}
27
+ http_405: {short: 'Méthode non autorisée', full: 'La méthode de requête n''est pas autorisée.'}
28
+ http_406: {short: 'Page non disponible', full: 'La page demandée n''est pas disponible.'}
29
+ http_407: {short: 'Authentification avec le proxy requise', full: 'La page demandée requiert une authentification avec le proxy.'}
30
+ http_408: {short: 'Requête expirée', full: 'Le client n''a pas produit de requête dans le délai que le serveur était prêt à attendre.'}
31
+ http_409: {short: 'Conflit', full: 'La page demandée n''est pas disponible.'}
32
+ http_410: {short: 'Page déplacée', full: 'La page demandée n''est plus disponible et aucune adresse de redirection n''est connue.'}
33
+ http_411: {short: 'Requête malformée', full: 'La longueur de la requête n''a pas été précisée.'}
34
+ http_412: {short: 'Précondition non vérifiée', full: 'La requête n''a pas pu être traitée par le server.'}
35
+ http_413: {short: 'Requête trop grande', full: 'La requête n''a pas pu être traitée par le server.'}
36
+ http_414: {short: 'URI trop longue', full: 'La requête n''a pas pu être traitée par le server.'}
37
+ http_415: {short: 'Format de requête non supporté', full: 'La requête n''a pas pu être traitée par le server.'}
38
+ http_416: {short: 'Requête incorrecte', full: 'La requête n''a pas pu être traitée par le server.'}
39
+ http_417: {short: 'Requête incorrecte', full: 'La requête n''a pas pu être traitée par le server.'}
40
+ http_500: {short: 'Erreur interne du serveur', full: 'Le serveur a rencontré une erreur et est incapable de poursuivre le traitement de la requête.'}
41
+ http_501: {short: 'Fonction non supportée', full: 'La fonctionnalité requise n''est pas supportée par le serveur.'}
42
+ http_502: {short: 'Réponse invalide', full: 'Le serveur a reçu une réponse invalide depuis le serveur distant.'}
43
+ http_503: {short: 'Service indisponible', full: 'Le serveur est temporairement indisponible.'}
44
+ http_504: {short: 'Délai d''attente de la réponse dépassé', full: 'Le serveur distant n''a pas répondu dans le délai attendu.'}
45
+ http_505: {short: 'Version du protocole HTTP non supportée', full: 'Le serveur ne gère pas la version demandée du protocole HTTP.'}
46
+ load: 'Erreur de chargement !'
@@ -0,0 +1,25 @@
1
+ /**
2
+ * nav.js
3
+ * JavaScript functions for the main navigation menu (used on small screens)
4
+ * and for the modal box.
5
+ */
6
+
7
+
8
+ function openNavMenu() {
9
+ document.querySelectorAll('header nav')[0].style.width = 'auto';
10
+ }
11
+
12
+ function closeNavMenu() {
13
+ document.querySelectorAll('header nav')[0].style.width = '0';
14
+ }
15
+
16
+ function openModal() {
17
+ document.getElementById('modal').style.display = 'block';
18
+ }
19
+
20
+ window.onclick = function(event) {
21
+ if (event.target == document.getElementById('modal')) {
22
+ document.getElementById('modal').style.display = 'none';
23
+ }
24
+ }
25
+
@@ -0,0 +1,306 @@
1
+ /**
2
+ * style.css
3
+ * Design for the IntraNet core.
4
+ */
5
+
6
+ /******************************* External Fonts *******************************/
7
+
8
+ @font-face {
9
+ font-family: "Source Sans Pro";
10
+ font-style: normal;
11
+ font-weight: 300;
12
+ src: url("fonts/SourceSansPro.woff2") format("woff2");
13
+ unicode-range: U+0-FF, U+131, U+152-153, U+2C6, U+2DA, U+2DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
14
+ }
15
+
16
+ /******************************* General design *******************************/
17
+
18
+ body, html {
19
+ padding: 0px;
20
+ margin: 0px;
21
+ height: 100%;
22
+ }
23
+ body {
24
+ background: #1e262b url('background.jpg') fixed top right no-repeat;
25
+ font-family: "Source Sans Pro", Cantarell, sans-serif;
26
+ color: black;
27
+ }
28
+ /* Mobile devices only */
29
+ @media only screen and (max-width: 600px), only screen and (max-device-width: 600px) {
30
+ body {
31
+ font-size: 180%;
32
+ }
33
+ }
34
+
35
+ a {
36
+ color: inherit;
37
+ text-decoration: inherit;
38
+ cursor: pointer;
39
+ }
40
+
41
+ /********************************* Navigation *********************************/
42
+
43
+ header {
44
+ position: fixed;
45
+ top: 0px;
46
+ width: 100%;
47
+ margin: auto;
48
+ height: 57px;
49
+ background-color: #1e262b;
50
+ color: white;
51
+ z-index: 2;
52
+ }
53
+ header h1 {
54
+ float: left;
55
+ font-size: 225%;
56
+ font-weight: 500;
57
+ margin: 0px;
58
+ padding: 5px 20px 8px; /* top sides bottom */
59
+ }
60
+ header nav {
61
+ float: right;
62
+ padding: 0px 20px;
63
+ }
64
+ header nav ul {
65
+ margin: 0px;
66
+ padding: 0px;
67
+ }
68
+ header nav > ul > li {
69
+ display: inline-block;
70
+ }
71
+ header nav > ul > li a, header a#openmenu, header a#closemenu {
72
+ display: block;
73
+ padding: 16px 20px;
74
+ margin: 0px;
75
+ font-size: 125%;
76
+ color: rgba(255, 255, 255, 0.7);
77
+ }
78
+ header nav > ul > li a:hover {
79
+ color: rgba(255, 255, 255, 1.0);
80
+ }
81
+
82
+ /* dropdown menus */
83
+ header nav > ul > li > ul {
84
+ display: none;
85
+ background-color: #1e262b;
86
+ position: absolute;
87
+ }
88
+ header nav > ul > li > ul > li {
89
+ display: block;
90
+ }
91
+ header nav > ul > li > ul > li a {
92
+ padding: 8px 20px;
93
+ }
94
+ header nav > ul > li:hover > ul, header nav > ul > li:active > ul {
95
+ display: block;
96
+ }
97
+
98
+ header a#openmenu, header a#closemenu {
99
+ display: none;
100
+ }
101
+
102
+ /* Mobile devices only */
103
+ @media only screen and (max-width: 600px), only screen and (max-device-width: 600px) {
104
+ header {
105
+ height: 100px;
106
+ }
107
+ header a#openmenu {
108
+ display: block;
109
+ position: absolute;
110
+ top: 0;
111
+ left: 0;
112
+ font-size: 190%;
113
+ font-weight: bold;
114
+ color: white;
115
+ }
116
+ header nav a#closemenu {
117
+ display: block;
118
+ font-size: 250%;
119
+ font-weight: bold;
120
+ color: white;
121
+ text-align: right;
122
+ margin-right: 0.5em;
123
+ }
124
+ header h1 {
125
+ float: none;
126
+ text-align: center;
127
+ }
128
+ header nav {
129
+ position: fixed;
130
+ top: 0;
131
+ left: 0;
132
+ height: 100%;
133
+ padding: 0;
134
+ z-index: 1;
135
+ background: inherit;
136
+ font-size: 125%;
137
+ overflow-y: scroll;
138
+ width: 0;
139
+ max-width: 100%;
140
+ }
141
+ header nav > ul {
142
+ height: auto;
143
+ margin: 0px 0px 50px; /* top sides bottom */
144
+ }
145
+ header nav > ul > li {
146
+ display: block;
147
+ }
148
+ header nav > ul > li a {
149
+ color: rgba(255, 255, 255, 1.0);
150
+ margin-top: 35px;
151
+ padding: 10px 150px 10px 40px; /* top right bottom left */
152
+ }
153
+ header nav > ul > li > ul {
154
+ display: block;
155
+ position: static; /* reset absolute positionning */
156
+ }
157
+ header nav > ul > li > ul > li a {
158
+ color: rgba(255, 255, 255, 0.7);
159
+ margin: 0px;
160
+ padding: 10px 100px 10px 90px; /* top right bottom left */
161
+ }
162
+ header nav li a:hover {
163
+ color: inherit;
164
+ background-color: #c2c2c2;
165
+ }
166
+ }
167
+
168
+ /* breadcrump navigation menu */
169
+ ul.breadcrumb {
170
+ list-style: none;
171
+ padding: 0px;
172
+ font-size: 90%;
173
+ margin-bottom: 50px;
174
+ }
175
+ ul.breadcrumb li {
176
+ display: inline;
177
+ }
178
+ ul.breadcrumb li+li:before {
179
+ padding: 4px;
180
+ content: "/\00a0";
181
+ }
182
+ ul.breadcrumb li a, main article a {
183
+ color: #748ea3;
184
+ }
185
+
186
+ /* Mobile devices only */
187
+ @media only screen and (max-width: 600px), only screen and (max-device-width: 600px) {
188
+ ul.breadcrumb {
189
+ display: none;
190
+ }
191
+ }
192
+
193
+ /******************************** Main content *******************************/
194
+
195
+ body > aside {
196
+ height: 45%;
197
+ min-height: 350px;
198
+ max-height: 500px; /* less than height of background.jpg */
199
+ }
200
+
201
+ body > main {
202
+ background: #f2f2f2;
203
+ padding: 57px 0px; /* height of the navigation bar */
204
+ min-height: 500px; /* lower or equal to height of background.jpg (with padding) */
205
+ }
206
+
207
+ body > main section, body > main article, body > main hr, body > footer p {
208
+ width: 75%;
209
+ max-width: 1200px;
210
+ margin: auto;
211
+ }
212
+ body > main hr {
213
+ height: 2px;
214
+ border: 0px;
215
+ background: #1e262b;
216
+ margin-top: 3em;
217
+ margin-bottom: 3em;
218
+ }
219
+
220
+ /* Mobile devices only */
221
+ @media only screen and (max-width: 600px), only screen and (max-device-width: 600px) {
222
+ body > main {
223
+ padding: 100px 0px;
224
+ }
225
+ body > main section, main hr, footer p {
226
+ width: 90%;
227
+ }
228
+ }
229
+
230
+ body > main section h2 {
231
+ font-size: 200%;
232
+ text-align: center;
233
+ margin: 50px 0px 2em; /* top sides bottom */
234
+ }
235
+ body > main article h2 {
236
+ margin-bottom: 40px;
237
+ }
238
+ body > main section h3 {
239
+ margin: 3em 0px 1em; /* top sides bottom */
240
+ }
241
+
242
+ body > main div.loading {
243
+ width: 100px;
244
+ height: 100px;
245
+ margin: auto;
246
+ border: 16px solid #eaeaea;
247
+ border-radius: 50%;
248
+ border-top: 16px solid #a0c5e2;
249
+ animation: spin 2s linear infinite;
250
+ }
251
+ @keyframes spin { /* animation definition */
252
+ 0% { transform: rotate(0deg); }
253
+ 100% { transform: rotate(360deg); }
254
+ }
255
+
256
+ body > footer {
257
+ background: #1e262b;
258
+ text-align: center;
259
+ color: white;
260
+ padding: 15px 0px; /* top sides */
261
+ font-size: 10pt;
262
+ }
263
+ body > footer aside#modal { /* modal box container */
264
+ display: none;
265
+ position: fixed;
266
+ z-index: 10;
267
+ left: 0;
268
+ top: 0;
269
+ width: 100%;
270
+ height: 100%;
271
+ overflow: auto;
272
+ color: black;
273
+ background-color: rgba(0, 0, 0, 0.8);
274
+ }
275
+ body > footer aside#modal #modal-content { /* modal box */
276
+ background-color: #f2f2f2;
277
+ margin: 15% auto;
278
+ padding: 20px 25px 40px; /* top sides bottom */
279
+ border: 1px solid #1e262b;
280
+ width: 400px;
281
+ font-size: 125%;
282
+ }
283
+ /* Mobile devices only */
284
+ @media only screen and (max-width: 600px), only screen and (max-device-width: 600px) {
285
+ body > footer {
286
+ font-size: 13pt;
287
+ }
288
+ }
289
+
290
+
291
+ /********************************* Error pages *********************************/
292
+
293
+ section#error h2 {
294
+ color: #ff3333;
295
+ }
296
+ section#error p {
297
+ text-align: center;
298
+ }
299
+ section#error p img {
300
+ width: 25%;
301
+ margin-bottom: 3em;
302
+ }
303
+ section#error a {
304
+ color: #748ea3;
305
+ }
306
+