--- /dev/null
+package SCSite;
+
+use IO::All;
+use SCSite::PageSet;
+use Web::Simple;
+
+{
+ package SCSite::Link; use Moo; has $_ => (is => 'ro') for qw(title to);
+}
+
+has _topbar => (is => 'ro', default => sub { [
+ map SCSite::Link->new(title => $_->[0], to => $_->[1] ),
+ map [ /^(.*?)\s+(\S+)$/ ],
+ split("\n", <<'ENDBAR')
+Home /
+About /about
+News /news
+Catalyst /catalyst
+Servers /servers
+Professional Services /services
+Blogs /blog
+ENDBAR
+ ] }
+);
+
+has pages => (is => 'lazy');
+
+has _layout_zoom => (is => 'lazy');
+
+sub default_config {
+ (
+ pages_dir => 'share/content',
+ template_dir => 'share/skin',
+ )
+}
+
+sub _build_pages {
+ my ($self) = @_;
+ SCSite::PageSet->new(base_dir => $self->config->{pages_dir})
+}
+
+sub dispatch_request {
+ my $self = shift;
+ sub (/feed/**/) {
+ $self->_http_response(500 => 'text/plain' => 'Not implemented');
+ },
+ sub (/) {
+ $self->_page_http_response(200 => $self->_find_page('index'));
+ },
+ sub (/**) {
+ [ 302, [ 'Location' => "/$_[1]/" ], [] ]
+ },
+ sub (/**/) {
+ my ($code, $page) = map {
+ $_ ? (200, $_) : (404, $self->_error_page(404))
+ } $self->_find_page($_[1]);
+ $self->_page_http_response($code => $page);
+ }
+}
+
+sub _error_page {
+ my ($self, $code) = @_;
+ $self->_find_page("error_${code}");
+}
+
+sub _find_page {
+ my ($self, $path) = @_;
+ $self->pages->get({ path => $path });
+}
+
+sub _http_response {
+ my ($self, $code, $type, $content) = @_;
+ [ $code, [ 'Content-type' => $type ], [ $content ] ];
+}
+
+sub _page_http_response {
+ my ($self, $code, $page) = @_;
+ [ $code, [ 'Content-type' => 'text/html' ], $self->_render_page($page) ];
+}
+
+sub _render_page {
+ my ($self, $page) = @_;
+ my $zoom = $self->_layout_zoom;
+ $zoom->select('.title')->replace_content($page->title)
+ ->select('meta[name=description]')->replace_content($page->description)
+ ->select('meta[name=keywords]')->replace_content($page->keywords)
+ ->select('.topbar_entries')->repeat_content([
+ map { my $e = $_; sub {
+ $_->select('.entry')->replace_content($e->title)
+ ->then->set_attribute(href => $e->to)
+ } } @{$self->_topbar}
+ ])
+ ->select('.main')->replace_content(\$page->body)
+ ->to_fh
+}
+
+sub _build__layout_zoom {
+ my ($self) = @_;
+ HTML::Zoom->from_file(
+ io->dir($self->config->{template_dir})->catfile('layout.html')
+ )->memoize;
+}
+
+sub run_if_script {
+ return $_[0]->to_psgi_app if caller(1);
+ my $class = shift;
+ my @config_keys = keys %{{$class->default_config}};
+ require Getopt::Long;
+ my %config;
+ Getopt::Long::GetOptions(
+ map +("$_=s" => \$config{$_}), @config_keys
+ );
+ delete $config{$_} for grep !defined($config{$_}), keys %config;
+ my $new = $class->new(config => \%config);
+ $new->run(@_)
+}
+
+__PACKAGE__->run_if_script;