zine 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/zine/page.rb CHANGED
@@ -12,7 +12,7 @@ module Zine
12
12
  # A page on the site where the content comes from a file's markdown, and the
13
13
  # destination's location mirrors its own
14
14
  class Page
15
- attr_reader :formatted_data
15
+ attr_reader :dest_path, :formatted_data, :source_file, :template_bundle
16
16
  # the meta data, passed formatted to the template
17
17
  class FormattedData
18
18
  include ERB::Util
@@ -28,6 +28,7 @@ module Zine
28
28
  @page = { date_rfc3339: front_matter['date'],
29
29
  date_us: parse_date(front_matter['date']),
30
30
  github_name: site_opt['options']['github_name'],
31
+ links_array: site_opt['links'],
31
32
  num_items_on_home: site_opt['options']['num_items_on_home'],
32
33
  site_author: site_opt['options']['site_author'],
33
34
  site_description: site_opt['options']['site_description'],
@@ -59,8 +60,10 @@ module Zine
59
60
  :pageDateUS)
60
61
 
61
62
  def initialize(md_file_name, dest, templates, site_options)
63
+ @source_file = md_file_name
62
64
  file_parts = File.open(md_file_name, 'r').read.split('---')
63
- @formatted_data = FormattedData.new(parse_yaml(file_parts[1]),
65
+ @formatted_data = FormattedData.new(parse_yaml(file_parts[1],
66
+ md_file_name),
64
67
  site_options)
65
68
  @dest_path = dest
66
69
  @raw_text = file_parts[2]
@@ -71,6 +74,7 @@ module Zine
71
74
  @header_partial = templates.header
72
75
  @footer_partial = templates.footer
73
76
  @template = templates.body
77
+ @template_bundle = templates
74
78
  end
75
79
 
76
80
  def parse_markdown
@@ -81,9 +85,10 @@ module Zine
81
85
  smart_quotes: %w(apos apos quot quot),
82
86
  syntax_highlighter: 'rouge'
83
87
  ).to_html
88
+ @raw_text = nil
84
89
  end
85
90
 
86
- def parse_yaml(text)
91
+ def parse_yaml(text, md_file_name)
87
92
  YAML.safe_load text
88
93
  rescue Psych::Exception
89
94
  puts Rainbow("Could not parse front matter for: #{md_file_name}").red
data/lib/zine/post.rb CHANGED
@@ -4,8 +4,10 @@ module Zine
4
4
  # A post - content comes from the markdown, and the destination from the date
5
5
  class Post < Page
6
6
  def initialize(md_file_name, templates, site_options)
7
+ @source_file = md_file_name
7
8
  file_parts = File.open(md_file_name, 'r').read.split('---')
8
- @formatted_data = FormattedData.new(parse_yaml(file_parts[1]),
9
+ @formatted_data = FormattedData.new(parse_yaml(file_parts[1],
10
+ md_file_name),
9
11
  site_options)
10
12
  @raw_text = file_parts[2]
11
13
  init_templates(templates)
@@ -17,16 +19,25 @@ module Zine
17
19
  def make_path_from_date(build_dir)
18
20
  page_data = @formatted_data.page
19
21
  date = DateTime.parse(page_data[:date_rfc3339])
20
- dest_dir = File.join(build_dir,
21
- date.strftime('%Y'),
22
- date.strftime('%-m'))
23
- FileUtils.mkdir_p dest_dir
22
+ @dest_dir = File.join(build_dir,
23
+ date.strftime('%Y'),
24
+ date.strftime('%-m'))
24
25
  slg = Zine::Page.slug(page_data[:title]) + '.html'
25
- @dest_path = File.join(dest_dir, slg)
26
+ @dest_path = File.join(@dest_dir, slg)
26
27
  end
27
28
 
28
29
  def process
30
+ FileUtils.mkdir_p @dest_dir
29
31
  super
32
+ tag_and_uri_subprocess
33
+ end
34
+
35
+ def process_without_writing
36
+ parse_markdown
37
+ tag_and_uri_subprocess
38
+ end
39
+
40
+ def tag_and_uri_subprocess
30
41
  page_data = @formatted_data.page
31
42
  file_path = rel_path_from_build_dir(@dest_path).to_s
32
43
  @formatted_data.uri = URI.join(page_data[:site_URL], file_path).to_s
@@ -0,0 +1,202 @@
1
+ require 'zine/data_page'
2
+ require 'zine/post'
3
+ require 'zine/tag'
4
+
5
+ module Zine
6
+ # The blog posts and their associate headline files (the home page, an
7
+ # articles index, and RSS feed)
8
+ class PostsAndHeadlines
9
+ def initialize(site, options)
10
+ @options = options
11
+ @post_array = []
12
+ @site = site
13
+ @tags_by_post = []
14
+ dir = @options['directories']
15
+ @guard = Zine::Watcher.new self, dir['build'], dir['source']
16
+ @guard.start
17
+ read_post_markdown_files
18
+ sort_posts_by_date
19
+ end
20
+
21
+ def find_page_from_path(file_path)
22
+ post_to_rebuild = @post_array.detect do |post|
23
+ File.expand_path(post.source_file) == file_path
24
+ end
25
+ index = @post_array.find_index post_to_rebuild
26
+ { post: post_to_rebuild, index: index }
27
+ end
28
+
29
+ # The headlines pages - the home page, an articles index, and RSS feed
30
+ def headline_pages
31
+ dir = @options['directories']['build']
32
+ options = @options['options']
33
+ templates = @options['templates']
34
+ [{ build_dir: dir, name: 'articles', number: @post_array.size,
35
+ suffix: '.html', template_name: templates['articles'],
36
+ title: 'Articles' },
37
+ { build_dir: dir, name: 'index',
38
+ number: options['num_items_on_home'], suffix: '.html',
39
+ template_name: templates['home'], title: 'Home' },
40
+ { build_dir: dir, name: 'rss',
41
+ number: options['number_items_in_RSS'], suffix: '.xml',
42
+ template_name: templates['rss'], title: '' }]
43
+ end
44
+
45
+ def one_new_post(source_file)
46
+ post_name = @options['templates']['post']
47
+ @post_array << Zine::Post.new(source_file,
48
+ @site.make_template_bundle(post_name),
49
+ @options)
50
+ @tags_by_post << @post_array.last.process
51
+ # TODO: may need to reorder posts by date, and therefor redo tags
52
+ write_tags_and_headlines
53
+ end
54
+
55
+ # get build file from post or location, delete build, remove form post_array
56
+ def preview_delete(file_path)
57
+ page = find_page_from_path file_path
58
+ if page[:index].nil?
59
+ directories = @options['directories']
60
+ full = Pathname(file_path)
61
+ relative = full.relative_path_from(
62
+ Pathname(File.absolute_path(directories['source']))
63
+ )
64
+ relative_path = File.dirname(relative)
65
+ file = File.basename(relative, '.md') + '.html'
66
+ File.delete(File.join(directories['build'], relative_path, file))
67
+ else
68
+ File.delete(page[:post].dest_path)
69
+ @post_array.delete_at(page[:index])
70
+ end
71
+ end
72
+
73
+ def preview_rebuild(file_path)
74
+ page = find_page_from_path file_path
75
+ if page[:index].nil?
76
+ if File.dirname(file_path) ==
77
+ File.absolute_path(@options['directories']['posts'])
78
+ one_new_post file_path
79
+ else
80
+ rebuild_page file_path
81
+ end
82
+ else
83
+ rebuild_post page[:post], page[:index]
84
+ end
85
+ end
86
+
87
+ # the build folder equivalent of a non Markdown file in the source tree
88
+ # TODO: move from posts & headlines
89
+ def preview_relative_equivalent(file)
90
+ directories = @options['directories']
91
+ source_dir = Pathname(File.absolute_path(directories['source']))
92
+ build_dir = Pathname(File.absolute_path(directories['build']))
93
+ file_dir = Pathname(File.dirname(file))
94
+ File.join build_dir, file_dir.relative_path_from(source_dir)
95
+ end
96
+
97
+ # copy a non Markdown file, TODO: move form posts & headlines
98
+ def preview_straight_copy(file)
99
+ FileUtils.cp(file, preview_relative_equivalent(file))
100
+ end
101
+
102
+ # delete a non Markdown file, TODO: move form posts & headlines
103
+ def preview_straight_delete(file)
104
+ FileUtils.rm(File.join(
105
+ preview_relative_equivalent(file), File.basename(file)
106
+ ))
107
+ end
108
+
109
+ # rebuild a page that's not a post - doesn't create the file structure for a
110
+ # new file with new parent folders
111
+ def rebuild_page(file)
112
+ @site.write_markdown(@options['templates']['default'],
113
+ File.expand_path(@options['directories']['source']),
114
+ file)
115
+ end
116
+
117
+ # inserts the new post into the @post_array, builds the file, calls
118
+ # write_tags_and_headlines to rewrites the headline pages & tags
119
+ # RSS & home page will be redundant re-builds if not a recent page
120
+ def rebuild_post(post, index)
121
+ @post_array[index] = Zine::Post.new post.source_file,
122
+ post.template_bundle, @options
123
+ @tags_by_post[index] = @post_array[index].process
124
+ write_tags_and_headlines
125
+ # TODO: may need to reorder posts by date... means re-doing tags
126
+ end
127
+
128
+ # Read markdown files in the posts folder into an array of Posts
129
+ def read_post_markdown_files
130
+ file_name_array = Dir[File.join(@options['directories']['posts'], '*.md')]
131
+ post_name = @options['templates']['post']
132
+ file_name_array.each do |file|
133
+ @post_array << Zine::Post.new(file,
134
+ @site.make_template_bundle(post_name),
135
+ @options)
136
+ end
137
+ end
138
+
139
+ # Sort the Posts array into date order, newest first
140
+ def sort_posts_by_date
141
+ @post_array.sort_by! do |post|
142
+ post.formatted_data.page[:date_rfc3339]
143
+ end.reverse!
144
+ # TODO: .freeze -- currently modified during preview
145
+ end
146
+
147
+ # Process each of the headlines pages
148
+ def wrangle_headlines
149
+ headline_pages.each do |page|
150
+ write_headline page
151
+ end
152
+ end
153
+
154
+ # Write out the Posts, calls write_tags_and_headlines
155
+ def write
156
+ @tags_by_post = []
157
+ @post_array.each do |post|
158
+ @tags_by_post << post.process
159
+ end
160
+ write_tags_and_headlines
161
+
162
+ # end point
163
+ { posts: @post_array, guard: @guard }
164
+ end
165
+
166
+ # Generate data without writing files (for incremnetal builds & uploads)
167
+ def writeless
168
+ @tags_by_post = []
169
+ @post_array.each do |post|
170
+ @tags_by_post << post.process_without_writing
171
+ end
172
+
173
+ # end point
174
+ { posts: @post_array, guard: @guard }
175
+ end
176
+
177
+ # Pass headline data to the DataPage class to write the files
178
+ def write_headline(page)
179
+ data = page
180
+ data[:post_array] = []
181
+ @post_array.first(page[:number]).each do |post|
182
+ post_data = post.formatted_data
183
+ data[:post_array] << { page: post_data.page, html: post_data.html,
184
+ uri: post_data.uri }
185
+ end
186
+ data_page = DataPage.new(data,
187
+ @site.make_template_bundle(data[:template_name]),
188
+ @options, data[:suffix])
189
+ data_page.write
190
+ end
191
+
192
+ # Write out the tags and headline files
193
+ def write_tags_and_headlines
194
+ tag_name = @options['templates']['tag']
195
+ tag_index_name = @options['templates']['tag_index']
196
+ tags = Zine::Tag.new @tags_by_post, @site.make_template_bundle(tag_name),
197
+ @site.make_template_bundle(tag_index_name), @options
198
+ tags.write_tags
199
+ wrangle_headlines
200
+ end
201
+ end
202
+ end
data/lib/zine/server.rb CHANGED
@@ -1,18 +1,25 @@
1
+ require 'highline'
1
2
  require 'rainbow'
2
3
  require 'rack'
3
4
  require 'thin'
5
+ require 'zine/upload'
6
+ require 'zine/watcher'
4
7
 
5
8
  module Zine
6
9
  # Local preview web server
7
10
  class Server
8
- def initialize(root)
11
+ # def initialize(_posts, rel_path_build, _rel_path_source, upload_options,
12
+ def initialize(rel_path_build, upload_options, delete_array, upload_array)
13
+ @delete_array = delete_array
14
+ @upload_array = upload_array
15
+ root = File.absolute_path(rel_path_build)
9
16
  motd
10
- Thin::Server.start('127.0.0.1', 8080) do
17
+
18
+ @thin = Thin::Server.new('127.0.0.1', 8080) do
11
19
  use Rack::Static,
12
20
  urls: ['/'],
13
21
  index: 'index.html',
14
22
  root: root
15
-
16
23
  now = Time.now
17
24
  a_long_time = 100**4
18
25
  run lambda { |_env|
@@ -26,9 +33,34 @@ module Zine
26
33
  'Pragma' => 'no-cache',
27
34
  'Expires' => now - a_long_time
28
35
  },
29
- File.open(File.join(root, 'index.html'), File::RDONLY)]
36
+ [File.open(File.join(root, 'index.html'), File::RDONLY)]]
30
37
  }
31
38
  end
39
+ @thin.start
40
+
41
+ return if upload_options['method'] == 'none' ||
42
+ (@delete_array.empty? && @upload_array.empty?)
43
+ cli = HighLine.new
44
+ answer = cli.ask('Upload files? (Y/n)') { |q| q.default = 'Y' }
45
+ return if answer != 'Y'
46
+ file_upload rel_path_build, upload_options
47
+ end
48
+
49
+ # deploy
50
+ def file_upload(rel_path_build, upload_options)
51
+ puts Rainbow('Connecting...').green
52
+ begin
53
+ upload = Zine::Upload.new rel_path_build, upload_options,
54
+ # @guard.delete_array, @guard.upload_array
55
+ @delete_array, @upload_array
56
+ upload.delete
57
+ upload.deploy
58
+ rescue Errno::ENETUNREACH
59
+ puts Rainbow("Unable to connect to #{upload_options['host']}").red
60
+ rescue Net::SSH::AuthenticationFailed
61
+ puts Rainbow("Authentication failed for #{upload_options['host']}").red
62
+ puts 'Check your credential file, and maybe run ssh-add?'
63
+ end
32
64
  end
33
65
 
34
66
  def motd
@@ -0,0 +1,18 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
3
+ <svg version="1.1" id="Icon" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 48 48" enable-background="new 0 0 48 48" xml:space="preserve">
4
+ <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="23.9995" y1="0" x2="23.9995" y2="48.0005">
5
+ <stop offset="0" style="stop-color:#F2A833"/>
6
+ <stop offset="1" style="stop-color:#E8621D"/>
7
+ </linearGradient>
8
+ <path fill-rule="evenodd" clip-rule="evenodd" fill="url(#SVGID_1_)" d="M48,42c0,3.313-2.687,6-6,6H6c-3.313,0-6-2.687-6-6V6
9
+ c0-3.313,2.687-6,6-6h36c3.313,0,6,2.687,6,6V42z"/>
10
+ <path fill="#E8621D" d="M10.016,8.494c0,0,27.276-1.343,29.426,27.41h-5.509c0,0,1.076-20.959-23.917-22.841V8.494z"/>
11
+ <path fill="#E8621D" d="M10.016,18.034c0,0,15.99-0.538,19.08,17.869h-5.508c0,0-0.671-10.883-13.572-13.302V18.034z"/>
12
+ <path fill="#E8621D" d="M13.01,30.021c1.928,0,3.494,1.563,3.494,3.494c0,1.928-1.566,3.493-3.494,3.493
13
+ c-1.93,0-3.494-1.564-3.494-3.493C9.516,31.585,11.08,30.021,13.01,30.021z"/>
14
+ <path fill="#FFFFFF" d="M10.016,9.597c0,0,27.276-1.343,29.426,27.41h-5.509c0,0,1.076-20.96-23.917-22.842V9.597z"/>
15
+ <path fill="#FFFFFF" d="M10.016,19.138c0,0,15.99-0.539,19.08,17.869h-5.508c0,0-0.671-10.882-13.572-13.304V19.138z"/>
16
+ <path fill="#FFFFFF" d="M13.01,31.125c1.928,0,3.494,1.562,3.494,3.494c0,1.928-1.566,3.493-3.494,3.493
17
+ c-1.93,0-3.494-1.564-3.494-3.493C9.516,32.688,11.08,31.125,13.01,31.125z"/>
18
+ </svg>
@@ -0,0 +1,24 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
3
+ <svg version="1.1" id="Icon" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 48 48" enable-background="new 0 0 48 48" xml:space="preserve">
4
+ <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="23.9995" y1="0" x2="23.9995" y2="48.0005">
5
+ <stop offset="0" style="stop-color:#4BD0EF"/>
6
+ <stop offset="1" style="stop-color:#29AAE1"/>
7
+ </linearGradient>
8
+ <path fill-rule="evenodd" clip-rule="evenodd" fill="url(#SVGID_1_)" d="M48,42c0,3.313-2.687,6-6,6H6c-3.313,0-6-2.687-6-6V6
9
+ c0-3.313,2.687-6,6-6h36c3.313,0,6,2.687,6,6V42z"/>
10
+ <path fill="#29AAE1" d="M40.231,13.413c-1.12,0.497-2.323,0.833-3.588,0.984c1.291-0.774,2.28-1.998,2.747-3.457
11
+ c-1.206,0.716-2.543,1.236-3.968,1.516c-1.139-1.214-2.763-1.972-4.56-1.972c-3.449,0-6.246,2.796-6.246,6.247
12
+ c0,0.49,0.055,0.966,0.161,1.424c-5.192-0.261-9.795-2.749-12.876-6.528c-0.538,0.923-0.846,1.996-0.846,3.141
13
+ c0,2.167,1.103,4.08,2.779,5.199c-1.024-0.032-1.987-0.313-2.83-0.781c0,0.026,0,0.053,0,0.079c0,3.026,2.153,5.551,5.011,6.125
14
+ c-0.525,0.143-1.076,0.219-1.646,0.219c-0.403,0-0.794-0.038-1.176-0.11c0.795,2.48,3.102,4.287,5.835,4.338
15
+ c-2.138,1.675-4.832,2.675-7.758,2.675c-0.504,0-1.002-0.03-1.491-0.089c2.765,1.773,6.048,2.808,9.576,2.808
16
+ c11.49,0,17.774-9.519,17.774-17.774c0-0.271-0.006-0.54-0.019-0.809C38.334,15.766,39.394,14.666,40.231,13.413z"/>
17
+ <path fill="#FFFFFF" d="M40.231,14.739c-1.12,0.497-2.323,0.833-3.588,0.984c1.291-0.773,2.28-1.998,2.747-3.456
18
+ c-1.206,0.716-2.543,1.236-3.968,1.516c-1.139-1.214-2.763-1.972-4.56-1.972c-3.449,0-6.246,2.796-6.246,6.247
19
+ c0,0.489,0.055,0.966,0.161,1.424c-5.192-0.261-9.795-2.748-12.876-6.527c-0.538,0.923-0.846,1.996-0.846,3.141
20
+ c0,2.167,1.103,4.079,2.779,5.199c-1.024-0.032-1.987-0.313-2.83-0.781c0,0.026,0,0.052,0,0.079c0,3.027,2.153,5.551,5.011,6.125
21
+ c-0.525,0.144-1.076,0.219-1.646,0.219c-0.403,0-0.794-0.038-1.176-0.11c0.795,2.481,3.102,4.287,5.835,4.338
22
+ c-2.138,1.676-4.832,2.675-7.758,2.675c-0.504,0-1.002-0.03-1.491-0.089c2.765,1.773,6.048,2.808,9.576,2.808
23
+ c11.49,0,17.774-9.519,17.774-17.774c0-0.271-0.006-0.54-0.019-0.808C38.334,17.092,39.394,15.992,40.231,14.739z"/>
24
+ </svg>
@@ -1 +1 @@
1
- *{margin:0;padding:0}html,body{height:100%}body{background-color:#fff;font-family:GillSansRegular,'Gill Sans MT','Gill Sans','Century Gothic',Calibri,'Trebuchet MS',sans-serif;line-height:1.618;color:#333;text-align:center;font-weight:300}#skiptocontent{height:1px;width:1px;position:absolute;overflow:hidden;top:-10px}h1,h2,h3,h4,h5,h6{color:#333;letter-spacing:.1em}h1 a,h2 a,h3 a,h4 a,h5 a,h6 a{color:#414f7c;text-decoration:none}h1 a:hover,h2 a:hover,h3 a:hover,h4 a:hover,h5 a:hover,h6 a:hover{color:#212721}h1{font-weight:300;font-size:2.618em;margin:1.809em 0 .809em}h2{font-weight:300;font-size:1.618em;margin:1.809em 0 .809em}h3,h4,h5,h6{font-weight:400;font-size:1em;margin:1.809em 0 .809em}p{margin:1em 0}a{color:#414f7c}a:hover{color:#212721}section{margin-bottom:1.618em}section>section{margin-bottom:3.236em}body>header{width:43em;text-align:center;margin:0 auto 0}body>header a{text-decoration:none;margin-left:.5em;margin-right:.5em}body>header,body>main,body>footer{display:block}body>header a{color:#414f7c}body>header a:hover{color:#212721}body>header a.extra{color:#414f7c;margin-left:1em}body>header a.extra:hover{color:#212721}body>header nav ul li{display:inline;list-style:none}.button{width:30px;height:30px;display:inline-block;background-size:100%;text-indent:-999em;text-align:left;margin:20px}.twitter{background:url('/assets/webicon-twitter-m.png');background-image:url('/assets/webicon-twitter.svg'),none}.rss{background:url('/assets/webicon-rss-m.png');background-image:url('/assets/webicon-rss.svg'),none}main{text-align:left;width:43em;margin:3em auto 2em}main li{margin-left:2.618em}.meta{color:#667}footer{width:43em;color:#667;border-top:4px solid #ddd;margin:3em auto 2em;overflow:hidden}footer .contact{float:left;margin-right:3em}footer .contact a,.tags a{color:#414f7c;text-decoration:none}footer .contact a:hover,.tags a:hover{color:#212721;text-decoration:none}.tags ul li{list-style:none;display:inline;font-variant:small-caps;font-size:1.2em}.archive a{text-decoration:none}ul.archive,ul.archive ul{margin-left:0}ul.archive li,ul.archive ul li{list-style:none;margin-left:0}.post pre{border:1px solid #ddd;background-color:#fff;padding:0 .4em}p.date{color:#667}pre{background-color:#eee;padding:1em;white-space:pre-wrap;white-space:-moz-pre-wrap;white-space:-pre-wrap;white-space:-o-pre-wrap;word-wrap:break-word}code{font-family:Consolas,Menlo,Monaco,'Lucida Console','Courier New',monospace,serif;font-size:.8em}blockquote{margin:2em 2em 2em 1em;padding:0 .75em 0 1.25em;border-left:2px solid #ddd;border-right:0 solid #ddd}@media all and (max-width:736px){body>header,main,footer{width:86%;margin:0 auto 0;padding:12px 24px 12px}p{margin-bottom:2em}.button{width:50px;height:50px;margin:20px}}
1
+ *{margin:0;padding:0}html,body{height:100%}body{background-color:#fff;font-family:GillSansRegular,"Gill Sans MT","Gill Sans","Century Gothic",Calibri,"Trebuchet MS",sans-serif;line-height:1.618;color:#333;text-align:center;font-weight:300}#skiptocontent{height:1px;width:1px;position:absolute;overflow:hidden;top:-10px}h1,h2,h3,h4,h5,h6{color:#333;letter-spacing:0.1em}h1 a,h2 a,h3 a,h4 a,h5 a,h6 a{color:#414f7c;text-decoration:none}h1 a:hover,h2 a:hover,h3 a:hover,h4 a:hover,h5 a:hover,h6 a:hover{color:#212721}h1{font-weight:300;font-size:2.618em;margin:1.809em 0 0.809em}h2{font-weight:300;font-size:1.618em;margin:1.809em 0 0.809em}h3,h4,h5,h6{font-weight:400;font-size:1em;margin:1.809em 0 0.809em}p,li{margin:1em 0}ul{margin:0}a{color:#414f7c}a:hover{color:#212721}section{margin-bottom:1.618em}section>section{margin-bottom:3.236em}body>header{width:43em;text-align:center;margin:0 auto 0}body>header a{text-decoration:none;margin-left:0.5em;margin-right:0.5em}body>header,body>main,body>footer{display:block}body>header a{color:#414f7c}body>header a:hover{color:#212721}body>header a.extra{color:#414f7c;margin-left:1em}body>header a.extra:hover{color:#212721}body>header nav ul li{display:inline;list-style:none}.button{width:30px;height:30px;display:inline-block;background-size:100%;text-indent:-999em;text-align:left;margin:20px}.twitter{background:url("/assets/webicon-twitter-m.png");background-image:url("/assets/webicon-twitter.svg"),none}.rss{background:url("/assets/webicon-rss-m.png");background-image:url("/assets/webicon-rss.svg"),none}main{text-align:left;width:43em;margin:3em auto 2em}main li{margin-left:2.618em}.meta{color:#667}footer{width:43em;color:#667;border-top:4px solid #ddd;margin:3em auto 2em;overflow:hidden}footer .column{float:left;width:33%;text-align:left}footer .column ul{list-style:none}footer .column a,.tags a{color:#414f7c;text-decoration:none}footer .column a:hover,.tags a:hover{color:#212721;text-decoration:none}.tags ul li{list-style:none;display:inline;font-variant:small-caps;font-size:1.2em}.archive a{text-decoration:none}ul.archive,ul.archive ul{margin-left:0}ul.archive li,ul.archive ul li{list-style:none;margin-left:0}.post pre{border:1px solid #ddd;background-color:#fff;padding:0 .4em}p.date{color:#667}pre{background-color:#eee;padding:1em;white-space:pre-wrap;white-space:-moz-pre-wrap;white-space:-pre-wrap;white-space:-o-pre-wrap;word-wrap:break-word}code{font-family:Consolas,Menlo,Monaco,"Lucida Console","Courier New",monospace,serif;font-size:0.8em}blockquote{margin:2em 2em 2em 1em;padding:0 .75em 0 1.25em;border-left:2px solid #ddd;border-right:0px solid #ddd}@media all and (max-width: 736px){body>header,main,footer{width:86%;margin:0 auto 0;padding:12px 24px 12px}p{margin-bottom:2em}.button{width:50px;height:50px;margin:20px}footer .column{width:100%}}
@@ -0,0 +1,291 @@
1
+ $blue: #414f7c;
2
+ $darkGrey: #333;
3
+ $mediumGrey: #667;
4
+ $lightGrey: #ddd;
5
+ $black: #212721;
6
+ $white: #fff;
7
+ $offWhite: #eee;
8
+
9
+ $backgroundColour: $white;
10
+ $codeBackgroundColour: $offWhite;
11
+ $headingColour: $darkGrey;
12
+ $textColour: $darkGrey;
13
+ $linkColour: $blue;
14
+ $hoverColour: $black;
15
+ $metaAndFooterColour: $mediumGrey;
16
+ $borderColour: $lightGrey;
17
+
18
+ $desktopWidth: 43em;
19
+ $lightWeight: 300;
20
+ $heavyWeight: 400;
21
+
22
+ $regularFont: GillSansRegular, 'Gill Sans MT', 'Gill Sans', 'Century Gothic', Calibri, 'Trebuchet MS', sans-serif;
23
+ $monoFont: Consolas, Menlo, Monaco, 'Lucida Console', 'Courier New', monospace, serif;
24
+
25
+ * {
26
+ margin: 0;
27
+ padding: 0;
28
+ }
29
+
30
+ html, body { height: 100%; }
31
+
32
+ body {
33
+ background-color: $backgroundColour;
34
+ font-family: $regularFont;
35
+ line-height: 1.618;
36
+ color: $textColour;
37
+ text-align: center;
38
+ font-weight: $lightWeight;
39
+ }
40
+
41
+ #skiptocontent {
42
+ height: 1px;
43
+ width: 1px;
44
+ position: absolute;
45
+ overflow: hidden;
46
+ top: -10px;
47
+ }
48
+
49
+ h1,h2,h3,h4,h5,h6 {
50
+ color: $headingColour;
51
+ letter-spacing: 0.1em;
52
+ a {
53
+ color: $linkColour;
54
+ text-decoration: none;
55
+ }
56
+ a:hover {
57
+ color: $hoverColour;
58
+ }
59
+ }
60
+
61
+ h1 {
62
+ font-weight: $lightWeight;
63
+ font-size: 2.618em;
64
+ margin: 1.809em 0 0.809em;
65
+ }
66
+
67
+ h2 {
68
+ font-weight: $lightWeight;
69
+ font-size: 1.618em;
70
+ margin: 1.809em 0 0.809em;
71
+ }
72
+
73
+ h3, h4, h5, h6 {
74
+ font-weight: $heavyWeight;
75
+ font-size: 1em;
76
+ margin: 1.809em 0 0.809em;
77
+ }
78
+
79
+ p, li {
80
+ margin: 1em 0;
81
+ }
82
+
83
+ ul {
84
+ margin: 0;
85
+ }
86
+
87
+ a { color:$linkColour; }
88
+ a:hover { color: $hoverColour; }
89
+
90
+ /* Home */
91
+ section {
92
+ margin-bottom: 1.618em;
93
+ }
94
+
95
+ section > section {
96
+ margin-bottom: 3.236em;
97
+ }
98
+
99
+ /* Site */
100
+ body > header {
101
+ width: $desktopWidth;
102
+ text-align: center;
103
+ margin: 0 auto 0;
104
+ }
105
+
106
+ body > header a {
107
+ text-decoration: none;
108
+ margin-left: 0.5em;
109
+ margin-right: 0.5em;
110
+ }
111
+
112
+ body > header, body > main, body > footer {
113
+ display: block;
114
+ }
115
+
116
+ body > header a {
117
+ color: $linkColour;
118
+ }
119
+
120
+ body > header a:hover {
121
+ color: $hoverColour;
122
+ }
123
+
124
+ body > header a.extra {
125
+ color: $linkColour;
126
+ margin-left: 1em;
127
+ }
128
+
129
+ body > header a.extra:hover {
130
+ color: $hoverColour;
131
+ }
132
+
133
+ body > header nav ul li {
134
+ display: inline;
135
+ list-style: none;
136
+ }
137
+
138
+ .button {
139
+ width: 30px;
140
+ height: 30px;
141
+ display: inline-block;
142
+ background-size: 100%;
143
+ text-indent: -999em;
144
+ text-align: left;
145
+ margin: 20px;
146
+ }
147
+
148
+ .twitter {
149
+ background: url('/assets/webicon-twitter-m.png');
150
+ background-image: url('/assets/webicon-twitter.svg'), none;
151
+ }
152
+
153
+ .rss {
154
+ background: url('/assets/webicon-rss-m.png');
155
+ background-image: url('/assets/webicon-rss.svg'), none;
156
+ }
157
+
158
+ main {
159
+ text-align: left;
160
+ width: $desktopWidth;
161
+ margin: 3em auto 2em;
162
+ }
163
+
164
+ main li {
165
+ margin-left: 2.618em;
166
+ }
167
+
168
+ .meta {
169
+ color: $metaAndFooterColour;
170
+ }
171
+
172
+ footer {
173
+ width: $desktopWidth;
174
+ color: $metaAndFooterColour;
175
+ border-top: 4px solid $borderColour;
176
+ margin: 3em auto 2em;
177
+ overflow: hidden;
178
+ }
179
+
180
+ footer .column {
181
+ float: left;
182
+ width: 33%;
183
+ text-align: left;
184
+ }
185
+
186
+ footer .column ul { list-style: none; }
187
+ footer .column a, .tags a { color:$linkColour; text-decoration: none; }
188
+ footer .column a:hover, .tags a:hover { color:$hoverColour; text-decoration: none; }
189
+
190
+ .tags ul li {
191
+ list-style: none;
192
+ display: inline;
193
+ font-variant: small-caps;
194
+ font-size:1.2em;
195
+ }
196
+
197
+ .archive a {
198
+ text-decoration: none;
199
+ }
200
+
201
+ ul.archive, ul.archive ul {
202
+ margin-left: 0;
203
+ }
204
+
205
+ ul.archive li, ul.archive ul li {
206
+ list-style: none;
207
+ margin-left: 0;
208
+ }
209
+
210
+ /*footer .rss {
211
+ margin-top: 1.1em;
212
+ margin-right: -.2em;
213
+ float: right;
214
+ }
215
+
216
+ footer .rss img {
217
+ border: 0;
218
+ }*/
219
+
220
+ /* Posts */
221
+
222
+ /* standard */
223
+ .post pre {
224
+ border: 1px solid $borderColour;
225
+ background-color: $backgroundColour;
226
+ padding: 0 .4em;
227
+ }
228
+
229
+
230
+ p.date {
231
+ color: $mediumGrey;
232
+ }
233
+
234
+ /*.post ul, .post ol {
235
+ margin-left: 1.35em;
236
+ }*/
237
+
238
+ pre {
239
+ background-color: $codeBackgroundColour;
240
+ padding: 1em;
241
+ white-space: pre-wrap;
242
+ /* why necessary */
243
+ white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
244
+ white-space: -pre-wrap; /* Opera 4-6 */
245
+ white-space: -o-pre-wrap; /* Opera 7 */
246
+ word-wrap: break-word;
247
+ }
248
+
249
+ code {
250
+ font-family: $monoFont;
251
+ font-size: 0.8em;
252
+ }
253
+
254
+ /* terminal
255
+ .post pre.terminal {
256
+ border: 1px solid $borderColour;
257
+ background-color: $codeBackgroundColour;
258
+ color: $textColour;
259
+ }
260
+
261
+ .post pre.terminal code {
262
+ background-color: $codeBackgroundColour;
263
+ }*/
264
+
265
+ /* quotes */
266
+ blockquote {
267
+ margin: 2em 2em 2em 1em;
268
+ padding: 0 .75em 0 1.25em;
269
+ border-left: 2px solid $borderColour;
270
+ border-right: 0px solid $borderColour;
271
+ }
272
+
273
+ @media all and (max-width: 736px) {
274
+ /* was max-device-width: 1242px */
275
+ body > header, main, footer {
276
+ width:86%;
277
+ margin: 0 auto 0;
278
+ padding: 12px 24px 12px;
279
+ }
280
+ p {
281
+ margin-bottom:2em;
282
+ }
283
+ .button {
284
+ width: 50px;
285
+ height: 50px;
286
+ margin: 20px;
287
+ }
288
+ footer .column {
289
+ width: 100%;
290
+ }
291
+ }