├── deck ├── __init__.py ├── settings │ ├── __init__.py │ ├── production.py │ ├── dev.py │ └── base.py ├── react │ ├── app │ │ ├── Slide │ │ │ └── components │ │ │ │ ├── HeadingField.jsx │ │ │ │ ├── EmbedField.jsx │ │ │ │ ├── ParagraphField.jsx │ │ │ │ ├── Title.jsx │ │ │ │ ├── ImageField.jsx │ │ │ │ ├── FlexGroupField.jsx │ │ │ │ ├── CodeField.jsx │ │ │ │ └── Slide.jsx │ │ ├── Weaver │ │ │ └── components │ │ │ │ └── Weaver.jsx │ │ ├── Loader │ │ │ ├── components │ │ │ │ └── Loader.jsx │ │ │ └── styles │ │ │ │ └── styles.scss │ │ ├── index.jsx │ │ ├── styles.scss │ │ └── App │ │ │ └── components │ │ │ └── App.jsx │ ├── package.json │ ├── .eslintrc.js │ └── webpack.config.js ├── templates │ ├── 404.html │ ├── app.html │ ├── 500.html │ └── base.html ├── wsgi.py ├── views.py ├── urls.py └── static │ └── img │ └── logo.svg ├── home ├── __init__.py ├── migrations │ ├── __init__.py │ ├── 0001_initial.py │ └── 0002_create_homepage.py ├── models.py └── templates │ └── home │ └── home_page.html ├── slides ├── __init__.py ├── migrations │ ├── __init__.py │ ├── 0004_slide_ordering.py │ ├── 0002_auto_20160513_1659.py │ ├── 0003_auto_20160513_1702.py │ ├── 0005_auto_20160514_0407.py │ ├── 0006_auto_20160514_0437.py │ ├── 0001_initial.py │ └── 0007_auto_20160529_0352.py ├── apps.py ├── image.py └── models.py ├── .gitignore ├── requirements.txt ├── manage.py └── README.md /deck/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /home/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /slides/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /home/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /slides/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /deck/settings/__init__.py: -------------------------------------------------------------------------------- 1 | from .dev import * 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite3 2 | static 3 | media 4 | *.pyc 5 | node_modules 6 | npm-debug.log 7 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django>=1.9,<1.10 2 | wagtail==1.5.2 3 | django-webpack-loader==0.3.0 4 | -------------------------------------------------------------------------------- /slides/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class SlidesConfig(AppConfig): 5 | name = 'slides' 6 | -------------------------------------------------------------------------------- /deck/settings/production.py: -------------------------------------------------------------------------------- 1 | from .base import * 2 | 3 | 4 | DEBUG = False 5 | 6 | try: 7 | from .local import * 8 | except ImportError: 9 | pass 10 | -------------------------------------------------------------------------------- /deck/react/app/Slide/components/HeadingField.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | 4 | export default function Heading({ value }) { 5 | return

{value}

6 | } 7 | -------------------------------------------------------------------------------- /slides/image.py: -------------------------------------------------------------------------------- 1 | # Expose Image fields to the API 2 | 3 | from wagtail.wagtailimages.models import get_image_model 4 | 5 | model = get_image_model() 6 | model.api_fields = model.admin_form_fields 7 | -------------------------------------------------------------------------------- /home/models.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from django.db import models 4 | 5 | from wagtail.wagtailcore.models import Page 6 | 7 | 8 | class HomePage(Page): 9 | pass 10 | -------------------------------------------------------------------------------- /deck/react/app/Slide/components/EmbedField.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | 4 | export default function EmbedField({ value }) { 5 | return
6 | } 7 | -------------------------------------------------------------------------------- /deck/react/app/Slide/components/ParagraphField.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | 4 | export default function ParagraphField({ value }) { 5 | return
6 | } 7 | -------------------------------------------------------------------------------- /deck/templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block body_class %}template-404{% endblock %} 4 | 5 | {% block content %} 6 |

Page not found

7 | 8 |

Sorry, this page could not be found.

9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "deck.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /deck/react/app/Slide/components/Title.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | 4 | export default function Title({ display_title, title }) { 5 | if (!display_title) { 6 | return null 7 | } 8 | 9 | return ( 10 |
11 |

{title}

12 |
13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /deck/react/app/Weaver/components/Weaver.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function Weaver({ display_weaver }) { 4 | if (!display_weaver) { 5 | return null 6 | } 7 | 8 | return ( 9 | 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /deck/react/app/Slide/components/ImageField.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import Loader from '~/Loader/components/Loader' 4 | 5 | 6 | export default function ImageField({ value, images }) { 7 | const image = images.find(image => image.id === value) 8 | if (!image) { 9 | return 10 | } 11 | 12 | return ( 13 | 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /deck/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for deck project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.9/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "deck.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /deck/react/app/Slide/components/FlexGroupField.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | 4 | export default function FlexGroupField({ value, images, getField }) { 5 | return ( 6 |
7 | {value.map((field, index) => 8 | 9 | {getField(field.type)({value: field.value, images})} 10 | 11 | )} 12 |
13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /deck/react/app/Slide/components/CodeField.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { highlightAuto } from 'highlight.js' 4 | 5 | import 'highlight.js/styles/github-gist.css' 6 | 7 | 8 | const languages = ['python', 'jsx', 'bash', 'json', 'html'] 9 | 10 | 11 | export default function CodeField({ value }) { 12 | const html = highlightAuto(value, languages) 13 | return ( 14 |
15 |             
16 |         
17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /deck/react/app/Loader/components/Loader.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import '~/Loader/styles/styles.scss' 4 | 5 | 6 | export default function Loader({ loading }) { 7 | const className = loading ? "loader" : "loader fade-out" 8 | 9 | return ( 10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /deck/templates/app.html: -------------------------------------------------------------------------------- 1 | {% load render_bundle from webpack_loader %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | Wagtail CMS + React 9 | 10 | 11 | 12 | 13 | 14 | {% render_bundle 'main' %} 15 | 16 | 17 | -------------------------------------------------------------------------------- /deck/settings/dev.py: -------------------------------------------------------------------------------- 1 | from .base import * 2 | 3 | 4 | # SECURITY WARNING: don't run with debug turned on in production! 5 | DEBUG = True 6 | 7 | for template_engine in TEMPLATES: 8 | template_engine['OPTIONS']['debug'] = True 9 | 10 | # SECURITY WARNING: keep the secret key used in production secret! 11 | SECRET_KEY = 'nkc65egdike&i3cghykteb&8gk$%ub^&!kvn-v4#4*wk((nzqq' 12 | 13 | 14 | EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' 15 | 16 | 17 | try: 18 | from .local import * 19 | except ImportError: 20 | pass 21 | -------------------------------------------------------------------------------- /home/templates/home/home_page.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block body_class %}template-homepage{% endblock %} 4 | 5 | {% block content %} 6 |

Welcome to your new Wagtail site!

7 | 8 |

You can access the admin interface here (make sure you have run "./manage.py createsuperuser" in the console first). 9 | 10 |

If you haven't already given the documentation a read, head over to http://docs.wagtail.io to start building on Wagtail

11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /slides/migrations/0004_slide_ordering.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-05-13 17:10 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('slides', '0003_auto_20160513_1702'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='slide', 17 | name='ordering', 18 | field=models.IntegerField(default=1), 19 | preserve_default=False, 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /slides/migrations/0002_auto_20160513_1659.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-05-13 16:59 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | import wagtail.wagtailcore.fields 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('slides', '0001_initial'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='slide', 18 | name='speaker_notes', 19 | field=wagtail.wagtailcore.fields.RichTextField(blank=True, null=True), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /home/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('wagtailcore', '__latest__'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='HomePage', 16 | fields=[ 17 | ('page_ptr', models.OneToOneField(on_delete=models.CASCADE, parent_link=True, auto_created=True, primary_key=True, serialize=False, to='wagtailcore.Page')), 18 | ], 19 | options={ 20 | 'abstract': False, 21 | }, 22 | bases=('wagtailcore.page',), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /deck/templates/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Internal server error 10 | 11 | 12 | 13 |

Internal server error

14 | 15 |

Sorry, there seems to be an error. Please try again soon.

16 | 17 | 18 | -------------------------------------------------------------------------------- /slides/migrations/0003_auto_20160513_1702.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-05-13 17:02 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('slides', '0002_auto_20160513_1659'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='slide', 17 | name='centered_slide', 18 | field=models.BooleanField(default=False), 19 | ), 20 | migrations.AddField( 21 | model_name='slide', 22 | name='display_title', 23 | field=models.BooleanField(default=False), 24 | ), 25 | migrations.AddField( 26 | model_name='slide', 27 | name='display_weaver', 28 | field=models.BooleanField(default=False), 29 | ), 30 | ] 31 | -------------------------------------------------------------------------------- /deck/react/app/index.jsx: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill' 2 | 3 | window.onunhandledrejection = (e) => { 4 | if ('reason' in e) { 5 | throw e.reason 6 | } 7 | } 8 | 9 | import React, { Component } from 'react' 10 | import { render } from 'react-dom' 11 | import { Router, IndexRedirect, Route, browserHistory } from 'react-router' 12 | 13 | import App from './App/components/App' 14 | import Slide from '~/Slide/components/Slide' 15 | 16 | import './styles.scss' 17 | 18 | class NotFound extends Component { 19 | render() { 20 | return

404: Error Not Found

21 | } 22 | } 23 | 24 | render( 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | , document.getElementById('app')) 35 | -------------------------------------------------------------------------------- /slides/migrations/0005_auto_20160514_0407.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-05-14 04:07 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | import wagtail.wagtailcore.blocks 7 | import wagtail.wagtailcore.fields 8 | import wagtail.wagtailimages.blocks 9 | 10 | 11 | class Migration(migrations.Migration): 12 | 13 | dependencies = [ 14 | ('slides', '0004_slide_ordering'), 15 | ] 16 | 17 | operations = [ 18 | migrations.AlterField( 19 | model_name='slide', 20 | name='contents', 21 | field=wagtail.wagtailcore.fields.StreamField((('heading', wagtail.wagtailcore.blocks.CharBlock(classname='heading', icon='title')), ('paragraph', wagtail.wagtailcore.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.wagtailimages.blocks.ImageChooserBlock(icon='image')), ('embed', wagtail.wagtailcore.blocks.RawHTMLBlock(icon='code')))), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /slides/migrations/0006_auto_20160514_0437.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-05-14 04:37 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | import wagtail.wagtailcore.blocks 7 | import wagtail.wagtailcore.fields 8 | import wagtail.wagtailimages.blocks 9 | 10 | 11 | class Migration(migrations.Migration): 12 | 13 | dependencies = [ 14 | ('slides', '0005_auto_20160514_0407'), 15 | ] 16 | 17 | operations = [ 18 | migrations.AlterField( 19 | model_name='slide', 20 | name='contents', 21 | field=wagtail.wagtailcore.fields.StreamField((('heading', wagtail.wagtailcore.blocks.CharBlock(classname='heading', icon='title')), ('paragraph', wagtail.wagtailcore.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.wagtailimages.blocks.ImageChooserBlock(icon='image')), ('embed', wagtail.wagtailcore.blocks.RawHTMLBlock(icon='code')), ('code', wagtail.wagtailcore.blocks.RawHTMLBlock(icon='code')))), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /deck/views.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponse, JsonResponse 2 | 3 | 4 | # Server holds the presentation. 5 | template = ''' 6 | Clicks: {cur_count}. 7 | 8 | Increment to {next_count} 9 | 10 | ''' 11 | 12 | 13 | def counter(request, cur_count="1"): 14 | # Server deals with application logic. 15 | next_count = int(cur_count) + 1 16 | 17 | # Server responds with everything the browser needs. 18 | response = template.format( 19 | cur_count=cur_count, next_count=next_count) 20 | 21 | return HttpResponse(response) 22 | 23 | 24 | # Front-end deals with presentation separately. 25 | def decoupled_counter(request, cur_count="1"): 26 | # Server deals with application logic. 27 | next_count = int(cur_count) + 1 28 | 29 | response = { 30 | 'cur_count': int(cur_count), 31 | 'next_count': next_count, 32 | 'next_link': '/counter/{}/'.format(next_count), 33 | } 34 | 35 | # Server just responds with data. 36 | return JsonResponse(response) 37 | -------------------------------------------------------------------------------- /slides/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-05-13 14:12 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | import wagtail.wagtailcore.blocks 8 | import wagtail.wagtailcore.fields 9 | import wagtail.wagtailimages.blocks 10 | 11 | 12 | class Migration(migrations.Migration): 13 | 14 | initial = True 15 | 16 | dependencies = [ 17 | ('wagtailcore', '0028_merge'), 18 | ] 19 | 20 | operations = [ 21 | migrations.CreateModel( 22 | name='Slide', 23 | fields=[ 24 | ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')), 25 | ('contents', wagtail.wagtailcore.fields.StreamField((('heading', wagtail.wagtailcore.blocks.CharBlock(classname='heading', icon='title')), ('paragraph', wagtail.wagtailcore.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.wagtailimages.blocks.ImageChooserBlock(icon='image'))))), 26 | ('speaker_notes', wagtail.wagtailcore.fields.RichTextField()), 27 | ], 28 | options={ 29 | 'abstract': False, 30 | }, 31 | bases=('wagtailcore.page',), 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /home/migrations/0002_create_homepage.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations 5 | 6 | 7 | def create_homepage(apps, schema_editor): 8 | # Get models 9 | ContentType = apps.get_model('contenttypes.ContentType') 10 | Page = apps.get_model('wagtailcore.Page') 11 | Site = apps.get_model('wagtailcore.Site') 12 | HomePage = apps.get_model('home.HomePage') 13 | 14 | # Delete the default homepage 15 | Page.objects.get(id=2).delete() 16 | 17 | # Create content type for homepage model 18 | homepage_content_type, created = ContentType.objects.get_or_create( 19 | model='homepage', app_label='home') 20 | 21 | # Create a new homepage 22 | homepage = HomePage.objects.create( 23 | title="Homepage", 24 | slug='home', 25 | content_type=homepage_content_type, 26 | path='00010001', 27 | depth=2, 28 | numchild=0, 29 | url_path='/home/', 30 | ) 31 | 32 | # Create a site with the new homepage set as the root 33 | Site.objects.create( 34 | hostname='localhost', root_page=homepage, is_default_site=True) 35 | 36 | 37 | class Migration(migrations.Migration): 38 | 39 | dependencies = [ 40 | ('home', '0001_initial'), 41 | ] 42 | 43 | operations = [ 44 | migrations.RunPython(create_homepage), 45 | ] 46 | -------------------------------------------------------------------------------- /deck/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import include, url 2 | from django.conf import settings 3 | from django.contrib import admin 4 | from django.views.generic import TemplateView 5 | 6 | from wagtail.wagtailadmin import urls as wagtailadmin_urls 7 | from wagtail.wagtaildocs import urls as wagtaildocs_urls 8 | from wagtail.wagtailcore import urls as wagtail_urls 9 | from wagtail.wagtailimages import urls as wagtailimage_urls 10 | from wagtail.contrib.wagtailapi import urls as wagtailapi_urls 11 | 12 | from .views import decoupled_counter, counter 13 | 14 | 15 | urlpatterns = [ 16 | url(r'^django-admin/', include(admin.site.urls)), 17 | 18 | url(r'^admin/', include(wagtailadmin_urls)), 19 | url(r'^documents/', include(wagtaildocs_urls)), 20 | 21 | url(r'^api/', include(wagtailapi_urls)), 22 | url(r'^wagtail/', include(wagtail_urls)), 23 | url(r'^images/', include(wagtailimage_urls)), 24 | 25 | url(r'^counter/$', counter), 26 | url(r'^counter/(?P[0-9]+)/$', counter), 27 | ] 28 | 29 | 30 | if settings.DEBUG: 31 | from django.conf.urls.static import static 32 | from django.contrib.staticfiles.urls import staticfiles_urlpatterns 33 | from django.views.generic import TemplateView 34 | 35 | # Serve static and media files from development server 36 | urlpatterns += staticfiles_urlpatterns() 37 | urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 38 | 39 | urlpatterns += [url('', TemplateView.as_view(template_name="app.html"))] 40 | -------------------------------------------------------------------------------- /deck/templates/base.html: -------------------------------------------------------------------------------- 1 | {% load static wagtailuserbar %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {% block title %}{% if self.seo_title %}{{ self.seo_title }}{% else %}{{ self.title }}{% endif %}{% endblock %}{% block title_suffix %}{% endblock %} 12 | 13 | 14 | 15 | {# Global stylesheets #} 16 | 17 | 18 | {% block extra_css %} 19 | {# Override this in templates to add extra stylesheets #} 20 | {% endblock %} 21 | 22 | 23 | 24 | {% wagtailuserbar %} 25 | 26 | {% block content %}{% endblock %} 27 | 28 | {# Global javascript #} 29 | 30 | 31 | {% block extra_js %} 32 | {# Override this in templates to add extra javascript #} 33 | {% endblock %} 34 | 35 | 36 | -------------------------------------------------------------------------------- /slides/migrations/0007_auto_20160529_0352.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-05-29 03:52 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | import wagtail.wagtailcore.blocks 7 | import wagtail.wagtailcore.fields 8 | import wagtail.wagtailimages.blocks 9 | 10 | 11 | class Migration(migrations.Migration): 12 | 13 | dependencies = [ 14 | ('slides', '0006_auto_20160514_0437'), 15 | ] 16 | 17 | operations = [ 18 | migrations.AlterField( 19 | model_name='slide', 20 | name='contents', 21 | field=wagtail.wagtailcore.fields.StreamField((('heading', wagtail.wagtailcore.blocks.CharBlock(classname='heading', icon='title')), ('paragraph', wagtail.wagtailcore.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.wagtailimages.blocks.ImageChooserBlock(icon='image')), ('embed', wagtail.wagtailcore.blocks.RawHTMLBlock(icon='site', label='Raw HTML')), ('code', wagtail.wagtailcore.blocks.RawHTMLBlock(icon='code', label='Highlighted Code')), ('flex_group', wagtail.wagtailcore.blocks.StreamBlock((('heading', wagtail.wagtailcore.blocks.CharBlock(classname='heading', icon='title')), ('paragraph', wagtail.wagtailcore.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.wagtailimages.blocks.ImageChooserBlock(icon='image')), ('embed', wagtail.wagtailcore.blocks.RawHTMLBlock(icon='site', label='Raw HTML')), ('code', wagtail.wagtailcore.blocks.RawHTMLBlock(icon='code', label='Highlighted Code'))), icon='doc-empty', label='Flex Group')))), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /deck/react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wagtail-deck", 3 | "version": "0.0.0", 4 | "private": true, 5 | "description": "", 6 | "main": "webpack.config.js", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "build": "webpack -p", 10 | "start": "webpack -w -d --display-error-details", 11 | "lint": "eslint app/**/*.jsx" 12 | }, 13 | "author": "Emily Horsman ", 14 | "devDependencies": { 15 | "autoprefixer": "^6.3.6", 16 | "babel-core": "^6.8.0", 17 | "babel-eslint": "^6.0.4", 18 | "babel-loader": "^6.2.4", 19 | "babel-plugin-add-module-exports": "^0.2.1", 20 | "babel-polyfill": "^6.8.0", 21 | "babel-preset-es2015": "^6.6.0", 22 | "babel-preset-react": "^6.5.0", 23 | "babel-preset-stage-0": "^6.5.0", 24 | "babel-preset-stage-1": "^6.5.0", 25 | "babel-preset-stage-2": "^6.5.0", 26 | "css-loader": "^0.23.1", 27 | "eslint": "^2.9.0", 28 | "eslint-plugin-react": "^5.1.1", 29 | "exports-loader": "^0.6.3", 30 | "imports-loader": "^0.6.5", 31 | "json-loader": "^0.5.4", 32 | "node-sass": "^3.7.0", 33 | "postcss-loader": "^0.9.1", 34 | "sass-loader": "^3.2.0", 35 | "style-loader": "^0.13.1", 36 | "webpack": "^2.1.0-beta.7", 37 | "webpack-bundle-tracker": "0.0.93", 38 | "webpack-dev-server": "^1.14.1", 39 | "webpack-merge": "^0.12.0" 40 | }, 41 | "dependencies": { 42 | "axios": "^0.11.0", 43 | "highlight.js": "^9.3.0", 44 | "react": "^15.0.2", 45 | "react-dom": "^15.0.2", 46 | "react-icons": "^2.0.1", 47 | "react-router": "^2.4.0" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /slides/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | from wagtail.wagtailcore.models import Page 4 | from wagtail.wagtailcore.fields import StreamField, RichTextField 5 | from wagtail.wagtailcore import blocks 6 | from wagtail.wagtailadmin.edit_handlers import FieldPanel, StreamFieldPanel 7 | from wagtail.wagtailimages.blocks import ImageChooserBlock 8 | 9 | from . import image 10 | 11 | 12 | base_stream_blocks = [ 13 | ('heading', blocks.CharBlock(icon='title', classname="heading")), 14 | ('paragraph', blocks.RichTextBlock(icon='pilcrow')), 15 | ('image', ImageChooserBlock(icon='image')), 16 | ('embed', blocks.RawHTMLBlock(icon='site', label='Raw HTML')), 17 | ('code', blocks.RawHTMLBlock(icon='code', label='Highlighted Code')), 18 | ] 19 | 20 | 21 | class Slide(Page): 22 | contents = StreamField(base_stream_blocks + [ 23 | ('flex_group', blocks.StreamBlock(base_stream_blocks, 24 | icon='doc-empty', 25 | label='Flex Group')), 26 | ]) 27 | 28 | speaker_notes = RichTextField(blank=True, null=True) 29 | 30 | ordering = models.IntegerField() 31 | display_weaver = models.BooleanField(default=False) 32 | display_title = models.BooleanField(default=False) 33 | centered_slide = models.BooleanField(default=False) 34 | 35 | content_panels = Page.content_panels + [ 36 | FieldPanel('ordering'), 37 | FieldPanel('display_weaver'), 38 | FieldPanel('display_title'), 39 | FieldPanel('centered_slide'), 40 | FieldPanel('speaker_notes'), 41 | StreamFieldPanel('contents'), 42 | ] 43 | 44 | api_fields = ['speaker_notes', 'contents', 'display_weaver', 'display_title', 'centered_slide', 'ordering',] 45 | -------------------------------------------------------------------------------- /deck/react/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * You should probably check out the Airbnb React/JSX style guide. 3 | * https://github.com/airbnb/javascript/tree/master/react 4 | */ 5 | 6 | module.exports = { 7 | "rules": { 8 | "quotes": [ 9 | 2, 10 | "single" 11 | ], 12 | "linebreak-style": [ 13 | 2, 14 | "unix" 15 | ], 16 | "semi": [ 17 | 2, 18 | "never" 19 | ], 20 | "comma-dangle": 0, 21 | "jsx-quotes": 1, 22 | "react/no-danger": 1, 23 | "react/no-unknown-property": 1, 24 | "react/no-direct-mutation-state": 1, 25 | "react/jsx-sort-props": 1, 26 | "react/no-is-mounted": 1, 27 | "react/sort-comp": 1, 28 | "react/jsx-no-bind": 1, 29 | "react/self-closing-comp": 1, 30 | "react/wrap-multilines": 1, 31 | "react/jsx-boolean-value": 1, 32 | "react/jsx-closing-bracket-location": 1, 33 | "react/jsx-pascal-case": 1, 34 | "react/prefer-es6-class": 1, 35 | "react/no-multi-comp": 1, 36 | "react/jsx-uses-react": 1, 37 | "react/react-in-jsx-scope": 1, 38 | "react/jsx-max-props-per-line": [1, {"maximum": 2}] 39 | }, 40 | "env": { 41 | "es6": true, 42 | "browser": true 43 | }, 44 | "extends": "eslint:recommended", 45 | "ecmaFeatures": { 46 | "jsx": true, 47 | "experimentalObjectRestSpread": true, 48 | "modules": true, 49 | "spread": true 50 | }, 51 | "parser": "babel-eslint", 52 | "parserOptions": { 53 | "sourceType": "module" 54 | }, 55 | "plugins": [ 56 | "react" 57 | ], 58 | "settings": { 59 | "react": { 60 | "pragma": "React" 61 | } 62 | } 63 | }; 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A slide deck with React and Wagtail 2 | 3 | This is a proof-of-concept library demonstrating how to 4 | build a slide presentation CMS using React and Wagtail. 5 | 6 | ## Getting Started 7 | 8 | ``` 9 | $ mkvirtualenv --python=python3 wagtail-react-deck 10 | $ git clone https://github.com/littleweaver/wagtail-react-deck.git 11 | $ cd wagtail-react-deck 12 | $ pip install -r requirements.txt 13 | $ ./manage.py migrate 14 | $ ./manage.py runserver 15 | ``` 16 | 17 | ## Running webpack 18 | 19 | `django-webpack-loader` and `webpack-stats` are used for the marriage of 20 | Django and webpack. `webpack-stats` outputs a JSON file each time a webpack 21 | bundle is built. `django-webpack-loader` polls this file to determine which 22 | bundle to serve to visitors. 23 | 24 | Make sure you are running node `5.x` or `6.x`. (We recommend 25 | managing your node versions with nvm.) 26 | 27 | ### Development 28 | 29 | ``` 30 | $ cd deck/react 31 | $ npm install 32 | $ npm run start 33 | ``` 34 | 35 | ### Production 36 | 37 | ``` 38 | $ cd deck/react 39 | $ npm install 40 | $ NODE_ENV=production npm run build 41 | ``` 42 | 43 | ## Presentation 44 | 45 | Use the left and right arrow keys to change your slides. 46 | Use the `o` key to open a deck with speaker notes in a new tab. Slide 47 | transitions will sync between the presentation and speaker copy (i.e., hitting 48 | the left arrow in the speaker deck with do the same in the presentation deck). 49 | 50 | ## Slide/Content Management 51 | 52 | You’ll need to create a superuser, 53 | 54 | ``` 55 | $ ./manage.py createsuperuser 56 | ``` 57 | 58 | Wagtail's admin lives at `/admin/` (that trailing slash is important). 59 | 60 | All requests not matched by Django will fall through to the client-side router. 61 | 62 | ## FAQ 63 | 64 | ### Isn’t this totally unnecessary? 65 | 66 | Yes. 67 | -------------------------------------------------------------------------------- /deck/react/app/Loader/styles/styles.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016 Connor Atherton 3 | * 4 | * All animations must live in their own file 5 | * in the animations directory and be included 6 | * here. 7 | * 8 | */ 9 | /** 10 | * Styles shared by multiple animations 11 | */ 12 | /** 13 | * Dots 14 | */ 15 | @-webkit-keyframes scale { 16 | 0% { 17 | -webkit-transform: scale(1); 18 | transform: scale(1); 19 | opacity: 1; } 20 | 45% { 21 | -webkit-transform: scale(0.1); 22 | transform: scale(0.1); 23 | opacity: 0.7; } 24 | 80% { 25 | -webkit-transform: scale(1); 26 | transform: scale(1); 27 | opacity: 1; } } 28 | @keyframes scale { 29 | 0% { 30 | -webkit-transform: scale(1); 31 | transform: scale(1); 32 | opacity: 1; } 33 | 45% { 34 | -webkit-transform: scale(0.1); 35 | transform: scale(0.1); 36 | opacity: 0.7; } 37 | 80% { 38 | -webkit-transform: scale(1); 39 | transform: scale(1); 40 | opacity: 1; } } 41 | 42 | .ball-pulse > div:nth-child(1) { 43 | -webkit-animation: scale 0.75s -0.24s infinite cubic-bezier(0.2, 0.68, 0.18, 1.08); 44 | animation: scale 0.75s -0.24s infinite cubic-bezier(0.2, 0.68, 0.18, 1.08); } 45 | 46 | .ball-pulse > div:nth-child(2) { 47 | -webkit-animation: scale 0.75s -0.12s infinite cubic-bezier(0.2, 0.68, 0.18, 1.08); 48 | animation: scale 0.75s -0.12s infinite cubic-bezier(0.2, 0.68, 0.18, 1.08); } 49 | 50 | .ball-pulse > div:nth-child(3) { 51 | -webkit-animation: scale 0.75s 0s infinite cubic-bezier(0.2, 0.68, 0.18, 1.08); 52 | animation: scale 0.75s 0s infinite cubic-bezier(0.2, 0.68, 0.18, 1.08); } 53 | 54 | .ball-pulse > div { 55 | background-color: #fff; 56 | width: 15px; 57 | height: 15px; 58 | border-radius: 100%; 59 | margin: 2px; 60 | -webkit-animation-fill-mode: both; 61 | animation-fill-mode: both; 62 | display: inline-block; } 63 | -------------------------------------------------------------------------------- /deck/react/app/styles.scss: -------------------------------------------------------------------------------- 1 | $font-size: 3vw; 2 | 3 | * { 4 | box-sizing: border-box; 5 | } 6 | 7 | *:before, 8 | *:after { 9 | box-sizing: inherit; 10 | } 11 | 12 | html { 13 | font-family: 'Raleway', serif; 14 | font-size: $font-size; 15 | } 16 | 17 | body { 18 | margin: 0; 19 | } 20 | 21 | header, 22 | header h1, 23 | h2, 24 | h3 { 25 | margin-top: 0; 26 | margin-bottom: 0.15em; 27 | 28 | font-family: 'Lato'; 29 | } 30 | 31 | header { 32 | border-bottom: 0.15rem solid lighten(#171512, 20%); 33 | margin: 4vh 5vw; 34 | } 35 | 36 | header h1 { 37 | font-size: 5vw; 38 | } 39 | 40 | pre { 41 | font-size: 0.8rem; 42 | line-height: 1; 43 | } 44 | 45 | .slide--has-weaver header { 46 | margin-right: 20vw; 47 | } 48 | 49 | .slide--centered header { 50 | position: absolute; 51 | top: 0; 52 | left: 0; 53 | right: 0; 54 | } 55 | 56 | a { 57 | color: #3f51b5; 58 | 59 | text-decoration: none; 60 | 61 | transition: all 0.25s ease; 62 | 63 | &:hover { 64 | color: #283593; 65 | } 66 | } 67 | 68 | img { 69 | width: 50vw; 70 | max-width: 75vw; 71 | height: auto; 72 | max-height: 75vh; 73 | } 74 | 75 | li:not(:last-child) { 76 | margin-bottom: 0.5em; 77 | } 78 | 79 | .ball-pulse > div { 80 | background-color: rgba(#283593, 0.8); 81 | } 82 | 83 | .loader { 84 | letter-spacing: 1px; 85 | } 86 | 87 | .loader-inner { 88 | display: inline-block; 89 | } 90 | 91 | .weaver { 92 | position: absolute; 93 | top: 0; 94 | right: 1rem; 95 | 96 | width: 15vw; 97 | height: auto; 98 | } 99 | 100 | .slide-contents { 101 | margin-left: 10vw; 102 | margin-right: 10vw; 103 | } 104 | 105 | .slide-contents--centered { 106 | display: flex; 107 | height: 100vh; 108 | 109 | flex-direction: column; 110 | justify-content: center; 111 | align-items: center; 112 | 113 | h1 { 114 | text-align: center; 115 | } 116 | } 117 | 118 | p { 119 | margin: 0.5rem 0; 120 | } 121 | 122 | .slide-contents--centered svg { 123 | height: 60vh; 124 | width: auto; 125 | max-width: 75vw; 126 | } 127 | 128 | .flex svg { 129 | height: 40vh; 130 | max-width: 50vw; 131 | } 132 | 133 | .flex { 134 | display: flex; 135 | 136 | justify-content: center; 137 | } 138 | 139 | .slide-contents--centered .flex { 140 | align-items: center; 141 | } 142 | 143 | .speaker-notes { 144 | position: fixed; 145 | bottom: 0; 146 | left: 0; 147 | right: 0; 148 | padding: 10px; 149 | border-top: 1px solid black; 150 | 151 | background: white; 152 | 153 | font-size: 0.75rem; 154 | 155 | &:hover { 156 | background: rgba(white, 0.6); 157 | } 158 | } 159 | 160 | .counter { 161 | position: fixed; 162 | bottom: 0; 163 | right: 0; 164 | padding: 0.5rem; 165 | 166 | color: #ccc; 167 | 168 | font-size: 2vw; 169 | } 170 | -------------------------------------------------------------------------------- /deck/react/webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var merge = require('webpack-merge'); 3 | var BundleTracker = require('webpack-bundle-tracker'); 4 | var autoprefixer = require('autoprefixer'); 5 | 6 | var TARGET = process.env.npm_lifecycle_event; 7 | process.env.BABEL_ENV = TARGET; 8 | 9 | var target = __dirname + '/../static/js/bundles'; 10 | 11 | var common = { 12 | entry: __dirname + '/app/index.jsx', 13 | 14 | output: { 15 | path: target, 16 | filename: '[name]-[hash].js' 17 | }, 18 | 19 | resolve: { 20 | alias: { '~': __dirname + '/app' }, 21 | extensions: ['', '.js', '.jsx', '.json', '.scss'], 22 | modulesDirectories: ['node_modules'] 23 | }, 24 | 25 | module: { 26 | loaders: [ 27 | { 28 | test: /\.jsx?$/, 29 | loader: 'babel', 30 | query: { 31 | presets: ['react', 'es2015', 'stage-0', 'stage-1', 'stage-2'], 32 | plugins: ['add-module-exports'] 33 | }, 34 | include: [ 35 | __dirname + '/app', 36 | __dirname + '/node_modules/react-icons', 37 | __dirname + '/node_modules/react-icon-base', 38 | ], 39 | }, 40 | 41 | { 42 | test: /\.json$/, 43 | loader: 'json', 44 | }, 45 | 46 | { 47 | test: /\.scss$/, 48 | loader: 'style!css!postcss!sass', 49 | include: __dirname + '/app' 50 | }, 51 | 52 | { 53 | test: /\.css$/, 54 | loader: 'style!css!postcss' 55 | } 56 | ] 57 | }, 58 | 59 | sassLoader: { 60 | includePaths: [__dirname + '/node_modules'] 61 | }, 62 | 63 | postcss: function() { 64 | return { 65 | defaults: [autoprefixer], 66 | cleaner: [autoprefixer({ browsers: [ 67 | 'Chrome >= 35', 68 | 'Firefox >= 31', 69 | 'Edge >= 12', 70 | 'Explorer >= 9', 71 | 'iOS >= 8', 72 | 'Safari >= 8', 73 | 'Android 2.3', 74 | 'Android >= 4', 75 | 'Opera >= 12' 76 | ]})] 77 | } 78 | }, 79 | 80 | plugins: [ 81 | new BundleTracker({ 82 | path: target, 83 | filename: './webpack-stats.json' 84 | }) 85 | ] 86 | }; 87 | 88 | if (TARGET === 'build') { 89 | module.exports = merge(common, { 90 | plugins: [ 91 | new webpack.DefinePlugin({ 92 | 'process.env': { 'NODE_ENV': JSON.stringify('production') } 93 | }), 94 | 95 | new webpack.optimize.UglifyJsPlugin() 96 | ] 97 | }); 98 | } 99 | 100 | if (TARGET === 'start') { 101 | module.exports = merge(common, { 102 | devtool: 'eval-source-map', 103 | devServer: { 104 | contentBase: target, 105 | progress: true, 106 | } 107 | }); 108 | } 109 | -------------------------------------------------------------------------------- /deck/react/app/Slide/components/Slide.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | import Loader from '~/Loader/components/Loader' 4 | import CodeField from '~/Slide/components/CodeField' 5 | import EmbedField from '~/Slide/components/EmbedField' 6 | import FlexGroupField from '~/Slide/components/FlexGroupField' 7 | import HeadingField from '~/Slide/components/HeadingField' 8 | import ImageField from '~/Slide/components/ImageField' 9 | import ParagraphField from '~/Slide/components/ParagraphField' 10 | import Title from '~/Slide/components/Title' 11 | import Weaver from '~/Weaver/components/Weaver' 12 | 13 | 14 | const FIELDS = { 15 | code: CodeField, 16 | embed: EmbedField, 17 | flex_group: FlexGroupField, 18 | heading: HeadingField, 19 | image: ImageField, 20 | paragraph: ParagraphField, 21 | } 22 | 23 | 24 | function getField(fieldType) { 25 | if (!FIELDS.hasOwnProperty(fieldType)) 26 | throw 'Unknown fieldType: ' + fieldType 27 | return FIELDS[fieldType] 28 | } 29 | 30 | 31 | function getSlideClassName(base, slide) { 32 | const classNames = [base] 33 | if (slide.centered_slide) { 34 | classNames.push(`${base}--centered`) 35 | } 36 | 37 | if (slide.display_weaver) { 38 | classNames.push(`${base}--has-weaver`) 39 | } 40 | 41 | return classNames.join(' ') 42 | } 43 | 44 | 45 | class Slide extends Component { 46 | constructor(props) { 47 | super(props) 48 | this.state = {} 49 | } 50 | 51 | componentWillReceiveProps(nextProps) { 52 | const slide = nextProps.pages.find(page => page.ordering >= parseInt(nextProps.params.ordering)) 53 | if (!slide) { 54 | return 55 | } 56 | 57 | this.setState({ 58 | slide, 59 | }) 60 | 61 | document.title = `${slide.ordering} | ${slide.title}` 62 | } 63 | 64 | render() { 65 | const slide = this.state.slide 66 | if (!slide) { 67 | return 68 | } 69 | 70 | const speakerNotes = window.opener && 71 |
75 | 76 | return ( 77 |
78 | {speakerNotes} 79 | 80 | 81 | 82 | <div className={getSlideClassName('slide-contents', slide)}> 83 | {slide.contents.map((field, index) => 84 | <span key={index} className="field"> 85 | {getField(field.type)({ 86 | value: field.value, 87 | images: this.props.images, 88 | getField, 89 | })} 90 | </span> 91 | )} 92 | </div> 93 | 94 | <div className="counter"> 95 | {this.props.params.ordering} of {this.props.pages.length} 96 | </div> 97 | </div> 98 | ) 99 | } 100 | } 101 | 102 | export default Slide 103 | -------------------------------------------------------------------------------- /deck/settings/base.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for deck project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.9.6. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.9/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.9/ref/settings/ 11 | """ 12 | 13 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 14 | import os 15 | 16 | PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | BASE_DIR = os.path.dirname(PROJECT_DIR) 18 | 19 | 20 | # Quick-start development settings - unsuitable for production 21 | # See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/ 22 | 23 | 24 | # Application definition 25 | 26 | INSTALLED_APPS = [ 27 | 'home', 28 | 'slides', 29 | 30 | 'wagtail.wagtailforms', 31 | 'wagtail.wagtailredirects', 32 | 'wagtail.wagtailembeds', 33 | 'wagtail.wagtailsites', 34 | 'wagtail.wagtailusers', 35 | 'wagtail.wagtailsnippets', 36 | 'wagtail.wagtaildocs', 37 | 'wagtail.wagtailimages', 38 | 'wagtail.wagtailsearch', 39 | 'wagtail.wagtailadmin', 40 | 'wagtail.wagtailcore', 41 | 42 | 'wagtail.contrib.wagtailapi', 43 | 'rest_framework', 44 | 45 | 'webpack_loader', 46 | 47 | 'modelcluster', 48 | 'taggit', 49 | 50 | 'django.contrib.admin', 51 | 'django.contrib.auth', 52 | 'django.contrib.contenttypes', 53 | 'django.contrib.sessions', 54 | 'django.contrib.messages', 55 | 'django.contrib.staticfiles', 56 | ] 57 | 58 | MIDDLEWARE_CLASSES = [ 59 | 'django.contrib.sessions.middleware.SessionMiddleware', 60 | 'django.middleware.common.CommonMiddleware', 61 | 'django.middleware.csrf.CsrfViewMiddleware', 62 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 63 | 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 64 | 'django.contrib.messages.middleware.MessageMiddleware', 65 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 66 | 'django.middleware.security.SecurityMiddleware', 67 | 68 | 'wagtail.wagtailcore.middleware.SiteMiddleware', 69 | 'wagtail.wagtailredirects.middleware.RedirectMiddleware', 70 | ] 71 | 72 | ROOT_URLCONF = 'deck.urls' 73 | 74 | TEMPLATES = [ 75 | { 76 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 77 | 'DIRS': [ 78 | os.path.join(PROJECT_DIR, 'templates'), 79 | ], 80 | 'APP_DIRS': True, 81 | 'OPTIONS': { 82 | 'context_processors': [ 83 | 'django.template.context_processors.debug', 84 | 'django.template.context_processors.request', 85 | 'django.contrib.auth.context_processors.auth', 86 | 'django.contrib.messages.context_processors.messages', 87 | ], 88 | }, 89 | }, 90 | ] 91 | 92 | WSGI_APPLICATION = 'deck.wsgi.application' 93 | 94 | 95 | # Database 96 | # https://docs.djangoproject.com/en/1.9/ref/settings/#databases 97 | 98 | DATABASES = { 99 | 'default': { 100 | 'ENGINE': 'django.db.backends.sqlite3', 101 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 102 | } 103 | } 104 | 105 | 106 | # Internationalization 107 | # https://docs.djangoproject.com/en/1.9/topics/i18n/ 108 | 109 | LANGUAGE_CODE = 'en-us' 110 | 111 | TIME_ZONE = 'UTC' 112 | 113 | USE_I18N = True 114 | 115 | USE_L10N = True 116 | 117 | USE_TZ = True 118 | 119 | 120 | # Static files (CSS, JavaScript, Images) 121 | # https://docs.djangoproject.com/en/1.9/howto/static-files/ 122 | 123 | STATICFILES_FINDERS = [ 124 | 'django.contrib.staticfiles.finders.FileSystemFinder', 125 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 126 | ] 127 | 128 | STATICFILES_DIRS = [ 129 | os.path.join(PROJECT_DIR, 'static'), 130 | ] 131 | 132 | STATIC_ROOT = os.path.join(BASE_DIR, 'static') 133 | STATIC_URL = '/static/' 134 | 135 | MEDIA_ROOT = os.path.join(BASE_DIR, 'media') 136 | MEDIA_URL = '/media/' 137 | 138 | 139 | # Wagtail settings 140 | 141 | WAGTAIL_SITE_NAME = "deck" 142 | WAGTAILAPI_BASE_URL = os.environ.get('WAGTAILAPI_BASE_URL', 'http://localhost') 143 | WAGTAILAPI_LIMIT_MAX = 1000 144 | 145 | 146 | # Webpack Loader Configuration 147 | 148 | WEBPACK_LOADER = { 149 | 'DEFAULT': { 150 | 'STATS_FILE': os.path.join(PROJECT_DIR, 'static', 'js', 'bundles', 'webpack-stats.json'), 151 | 'BUNDLE_DIR_NAME': 'js/bundles/', 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /deck/react/app/App/components/App.jsx: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import React, { Component, cloneElement } from 'react' 3 | import { Link, browserHistory } from 'react-router' 4 | 5 | 6 | const pageFields = [ 7 | 'title', 8 | 'speaker_notes', 9 | 'contents', 10 | 'display_weaver', 11 | 'display_title', 12 | 'centered_slide', 13 | 'ordering', 14 | ] 15 | 16 | const sortPages = (a, b) => a.ordering - b.ordering 17 | 18 | const leftKeyCode = 37 19 | const rightKeyCode = 39 20 | const oKeyCode = 79 21 | 22 | // Preload an image, hidden from the user. This speeds up transitions 23 | // between slides. 24 | function preloadImage(url) { 25 | const img = new Image() 26 | img.src = url 27 | } 28 | 29 | class App extends Component { 30 | constructor(props) { 31 | super(props) 32 | 33 | this.state = { 34 | loading: 0, 35 | pages: [], 36 | images: [], 37 | } 38 | } 39 | 40 | componentDidMount() { 41 | axios.get('/api/v1/pages/', { 42 | params: { 43 | type: 'slides.Slide', 44 | fields: pageFields.join(','), 45 | order: 'ordering', 46 | limit: 1000, 47 | } 48 | }) 49 | .then(this.handlePagesIndex.bind(this)) 50 | 51 | document.addEventListener('keydown', this.handleKeydown.bind(this)) 52 | window.addEventListener('message', this.handleMessage.bind(this)) 53 | } 54 | 55 | handleMessage(event) { 56 | if (event.data.nextSlide) { 57 | browserHistory.push(`/slide/${event.data.nextSlide}/`) 58 | } 59 | } 60 | 61 | handleKeydown(event) { 62 | if (event.keyCode === rightKeyCode) { 63 | this.changeSlide(1) 64 | } else if (event.keyCode === leftKeyCode) { 65 | this.changeSlide(-1) 66 | } else if (event.keyCode === oKeyCode) { 67 | this.openSpeakerNotes() 68 | } 69 | } 70 | 71 | openSpeakerNotes() { 72 | this.speakerWindow = window.open(window.location.href) 73 | } 74 | 75 | changeSlide(offset) { 76 | const currentSlide = parseInt(this.props.params.ordering) 77 | const orderings = this.state.pages.map(page => page.ordering) 78 | const maxOrdering = Math.max.apply(null, orderings) 79 | const minOrdering = Math.min.apply(null, orderings) 80 | 81 | const nextSlide = Math.max(Math.min(currentSlide + offset, maxOrdering), minOrdering) 82 | browserHistory.push(`/slide/${nextSlide}/`) 83 | 84 | const target = window.opener || this.speakerWindow 85 | if (target) { 86 | target.postMessage({ nextSlide, }, '*') 87 | } 88 | } 89 | 90 | handlePagesIndex(response) { 91 | const fetchedPages = response.data.pages 92 | const existingPages = this.state.pages 93 | const pages = fetchedPages.filter(fetchedPage => { 94 | return !existingPages.some(page => page.id === fetchedPage.id) 95 | }) 96 | 97 | const imageFields = pages.reduce((acc, page) => { 98 | return acc.concat(page.contents.filter(field => field.type === 'image')) 99 | }, []) 100 | 101 | this.fireImageRequests(imageFields) 102 | 103 | this.setState({ 104 | loading: this.state.loading - 1, 105 | pages: this.state.pages.concat(pages).sort(sortPages), 106 | }) 107 | } 108 | 109 | handlePagesDetail(response) { 110 | const fetchedPage = response.data 111 | if (this.state.pages.find(page => page.id === fetchedPage.id)) { 112 | return 113 | } 114 | 115 | const imageFields = fetchedPage.contents.filter(field => field.type === 'image') 116 | 117 | this.fireImageRequests(imageFields) 118 | 119 | this.setState({ 120 | loading: this.state.loading - 1, 121 | pages: this.state.pages.concat(fetchedPage).sort(sortPages), 122 | }) 123 | } 124 | 125 | handleImagesIndex(response) { 126 | this.setState({ 127 | loading: this.state.loading - 1, 128 | images: this.state.images.concat(response.data.images), 129 | }) 130 | } 131 | 132 | handleImagesDetail(response) { 133 | this.setState({ 134 | loading: this.state.loading - 1, 135 | images: this.state.images.concat(response.data), 136 | }) 137 | 138 | preloadImage(response.data.original_url) 139 | } 140 | 141 | fireImageRequests(imageFields) { 142 | const images = this.state.images 143 | const unfetchedImages = imageFields.filter(field => !images.some(image => image.id === field.value)) 144 | 145 | if (unfetchedImages.length === 0) { 146 | return 147 | } 148 | 149 | this.incrementLoading(unfetchedImages.length) 150 | for (const field of unfetchedImages) { 151 | axios.get(`/api/v1/images/${field.value}/`) 152 | .then(this.handleImagesDetail.bind(this)) 153 | } 154 | } 155 | 156 | incrementLoading(count = 1) { 157 | this.setState({ 158 | loading: this.state.loading + count, 159 | }) 160 | } 161 | 162 | render() { 163 | const { children } = this.props 164 | const childProps = { 165 | loading: this.state.loading, 166 | handlePagesIndex: this.handlePagesIndex.bind(this), 167 | handlePagesDetail: this.handlePagesDetail.bind(this), 168 | handleImagesIndex: this.handleImagesIndex.bind(this), 169 | handleImagesDetail: this.handleImagesDetail.bind(this), 170 | incrementLoading: this.incrementLoading.bind(this), 171 | pages: this.state.pages, 172 | images: this.state.images, 173 | } 174 | 175 | return ( 176 | <div> 177 | {React.Children.map(children, child => cloneElement(child, childProps))} 178 | </div> 179 | ) 180 | } 181 | } 182 | 183 | export default App 184 | -------------------------------------------------------------------------------- /deck/static/img/logo.svg: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <!-- Generator: Adobe Illustrator 15.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> 3 | <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> 4 | <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="50px" 5 | height="100px" viewBox="0 0 50 100" enable-background="new 0 0 50 100" xml:space="preserve"> 6 | <g class="nest"> 7 | <path class="nest_base" fill-rule="evenodd" clip-rule="evenodd" fill="#A4C16F" d="M46.661,32.522c0,0.851-0.638,1.531-1.263,2.231 8 | c0.078-0.481,0.138-0.957,0.169-1.422c0.155-2.262-0.003-4.486-0.381-6.672c0.693-1.695,1.18-3.488,0.696-5.229 9 | c-0.198-0.206-0.396-0.413-0.596-0.62c-0.186-0.617-0.752-2.544-1.585-2.544c1.166,2.431,1.286,4.308,1.08,6.381 10 | c-0.687-2.904-1.742-5.732-2.97-8.476c0.046-1.037,0.092-2.073,0.137-3.109c-0.016-0.094-0.033-0.186-0.049-0.279 11 | c-0.054,0.004-0.108,0.008-0.162,0.011c-0.395,0.235-0.669,0.771-0.833,1.429c-0.496-1.029-1.008-2.048-1.53-3.051 12 | c-0.419-0.765-0.837-1.529-1.255-2.294c-0.732-1.14-1.466-2.278-2.199-3.417c-0.225-0.4-0.44-0.99-0.691-1.57 13 | c-0.121-1.469-0.561-2.992-1.446-3.287h-0.04c0,0.475,0.024,0.963,0.064,1.451c-0.357-0.109-0.779-0.023-1.289,0.369 14 | c-0.366,0.404-0.732,0.811-1.099,1.215c0.296,0.941,0.592,1.883,0.888,2.823c0.481,1.462,1.051,3.507,0.969,5.34 15 | c-1.28,1.214-3.768,1.523-3.768,2.501c1.078,0,2.512-1.502,3.058-1.502v0.045c-0.128,0.383-1.479,1.484-1.186,1.871 16 | c0.034,0.006,0.068,0.012,0.103,0.018c0.445-0.261,0.951-0.641,1.418-1.092c-0.33,0.804-0.892,1.482-1.803,1.92 17 | c-0.968,0.466-2.088,0.5-3.098,0.129c-1.731-0.636-2.445-2.32-2.61-4.255c0.439-0.476,0.944-0.89,1.09-1.512 18 | c0.012-0.178,0.023-0.356,0.034-0.534c-0.179,0.041-0.355,0.082-0.534,0.123c-0.047-0.252,1.274-3.724,1.274-4.93 19 | c-0.229,0.588-0.457,1.178-0.685,1.766c-0.253,0.625-0.657,1.258-1.042,1.906c0.222-1.516,0.619-2.93,0.986-3.877 20 | c0.272-0.702,1.734-3.838,0.726-4.289c-1.142-0.381-2.433,0.532-3.252,1.146l0.236-0.643c-1.357,2.173-2.597,4.449-3.714,6.791 21 | c-1.133,1.006-2.376,1.971-2.228,2.758c0.017,0.021,0.034,0.045,0.051,0.067c0.025-0.001,0.051-0.003,0.077-0.004 22 | c0.119-0.12,0.237-0.241,0.355-0.362c-0.078,0.198-0.157,0.396-0.235,0.594c-0.431,0.994-0.496,1.631-0.322,2.379 23 | c-1.062,2.861-1.923,5.789-2.525,8.746c-0.096,0.471-0.197,0.938-0.296,1.402c0.137-0.965,0.273-1.931,0.411-2.896 24 | c0.145-0.763-0.4-1.105-0.49-1.738c0.083-0.697,0.166-1.396,0.25-2.094c-0.296,0.846-0.592,1.691-0.888,2.537 25 | c-0.094-0.041-0.187-0.08-0.281-0.119c0.159,0.6,0.319,1.201,0.479,1.802c0.082,1.713-0.489,3.729-0.01,5.399 26 | c-0.111,0.859-0.166,1.73-0.119,2.625c0.012,0.223,0.03,0.444,0.048,0.667c-0.663,1.7-1.122,4.61-0.311,5.422 27 | c0.098-0.366,0.196-0.733,0.294-1.101c0.379,0.84,0.759,1.68,1.139,2.52c-0.161-0.953-0.322-1.906-0.483-2.859 28 | c0.604,2.486,1.581,4.888,2.897,7.016c0.258,1.828,0.711,4.225,1.737,4.225c-0.393,0-0.473-1.59-0.458-1.844 29 | c0.01-0.164,0.012-0.342,0.014-0.518c0.88,1.125,1.874,2.131,2.969,2.992c0,0,0,0,0.001,0.001 30 | c0.818,0.677,1.637,1.354,2.454,2.03c0.33,0.002,0.659,0.006,0.988,0.008c2.305,0.961,4.922,1.329,7.817,0.885 31 | c0.567-0.087,1.041-0.301,1.504-0.542c0.527-0.077,0.973-0.258,1.364-0.688c0.69-0.302,1.381-0.604,2.072-0.906 32 | c-0.21,0.254-0.415,0.518-0.597,0.809c-0.116,0.184-0.939,1.825-0.398,1.825c0.188-0.784,0.715-1.366,1.406-1.772 33 | c0.023-0.002,0.046-0.003,0.068-0.004c0.027,0.263,0.054,0.525,0.081,0.789h0.023c0.784-1.241,1.858-2.569,2.197-4.051 34 | c1.328-1.594,2.365-3.545,3.056-5.551c0.652-0.493,1.117-2.113,1.279-2.531c0.248-0.256,0.496-0.514,0.743-0.77 35 | c0.01-0.669,0.02-1.338,0.029-2.007C46.668,32.523,46.664,32.523,46.661,32.522z"/> 36 | <path class="nest_inside_base" fill-rule="evenodd" clip-rule="evenodd" fill="#677944" d="M37.782,39.122c-0.26-0.985-0.8-1.948-1.303-2.692 37 | c-0.083-0.123-0.166-0.245-0.249-0.367c-0.062-0.093-0.123-0.186-0.185-0.273c-0.688-1.014-1.419-2.006-2.447-2.707 38 | c-3.457-2.355-11.964-5.438-14.564-0.179c-0.732,1.481,0.351,2.702,0.73,4.069c0.594,2.14,1.022,2.438,2.304,4.021l1.271,1.936 39 | c0.933,0.123,1.994,0.586,2.958,0.997c0.683,0.187,1.365,0.374,2.048,0.56c1.533-0.008,3.068-0.016,4.602-0.021 40 | c1.02-0.74,2.117-1.361,3.328-0.43c0.209-0.02,0.423-0.039,0.625-0.027c0.023-0.027,0.057-0.041,0.079-0.071 41 | c0.104-0.046,0.211-0.091,0.292-0.14C38.601,43,38.48,41.07,37.782,39.122z"/> 42 | <path class="nest_inside_shadow" fill-rule="evenodd" clip-rule="evenodd" fill="#4E5C34" d="M37.006,36.549c-0.81-1.633-2.006-3.123-3.317-4.236 43 | c-1.361-1.156-3.096-1.812-4.813-2.219c-1.304-0.196-2.608-0.393-3.912-0.588c-0.931,0.006-1.86,0.012-2.79,0.018 44 | c-3.253,0.582-4.015,4.232-3.079,6.947c0.177,0.514,0.457,1.428,0.927,1.881c0.16,0.33,0.377,0.712,0.722,1.143 45 | c0.284,0.003,0.626-0.123,1.035-0.249c0.294-0.618,0.407-0.503,1.084-0.75c1.215-0.53,0.725-0.637,1.776-1.405 46 | c1.544-1.129,4.167-1.314,5.943-0.707c2.649,0.906,2.551,3.578,4.73,4.926c0.552,0.365,0.691,0.35,1.487,0.678 47 | c0.62,0.366,0.528,1.547,1.409,0.652C39.246,41.404,38.262,38.652,37.006,36.549z"/> 48 | <path class="nest_outside_shadow" fill-rule="evenodd" clip-rule="evenodd" fill="#93AD64" d="M20.407,40.551c-0.027-0.271-0.054-0.545-0.081-0.816 49 | c-1.348-2.709-2.058-4.607-2.058-7.689c-0.236,0.293-0.471,0.586-0.707,0.879c-0.327-0.327,1.187-6.62,1.187-6.62 50 | c-0.535,0.354-1.07,0.71-1.604,1.063c0.085-0.336,0.169-0.674,0.253-1.01c-0.36,0.299-0.721,0.6-1.081,0.899 51 | c-0.032-0.003-0.063-0.007-0.095-0.011c-0.615-0.799,0.342-2.562,0.342-3.453c-0.518,0.217-1.917,6.854-1.917,7.805 52 | c0.343-0.464,0.686-0.928,1.028-1.392c0.02-0.002,0.04-0.005,0.06-0.007c0.068,0.723,0.137,1.444,0.206,2.166 53 | c0.357,1.191,0.713,2.384,1.07,3.575c0.342,0.981,1.163,1.708,1.5,2.568c0.242,0.934,0.484,1.868,0.726,2.802 54 | c0.147-0.066,0.294-0.133,0.44-0.199c0.554,0.597,1.107,1.193,1.66,1.791l0.287-0.572c0.047,0.065,0.095,0.132,0.143,0.197 55 | c-0.31-0.687-0.619-1.374-0.928-2.062C20.694,40.494,20.551,40.522,20.407,40.551z M33.361,45.211 56 | c-0.299-0.012-0.598-0.023-0.896-0.034l0.067-0.688c-0.052-1.107-2.317-1.339-2.974-0.959v0.039 57 | c0.122,0.204,1.264,0.58,2.122,1.055c0.009,0.053,0.012,0.103,0.002,0.149c-0.066,0.077-0.132,0.155-0.198,0.231 58 | c-0.418,0.088-3.567,0.439-3.679,0.4c-1.028-0.494-2.058-0.988-3.086-1.482c0.056,0.009,0.111,0.018,0.166,0.027 59 | c-0.018-0.017-0.035-0.033-0.053-0.049c-0.235-0.01-0.472-0.02-0.707-0.029c0.074,0.492,0.148,0.984,0.222,1.477 60 | c-0.073,0.001-0.146,0.002-0.22,0.004c0.04,0.105,0.08,0.209,0.119,0.314c1.035,1.088,5.562,1.627,6.976,1.698 61 | c0.21,0.011,2.481,0.174,2.481-0.304c-0.281-0.221-0.562-0.441-0.843-0.662C33.03,46.155,33.644,45.527,33.361,45.211z 62 | M32.374,45.173c-0.046-0.047-0.085-0.104-0.123-0.167c0.067,0.056,0.134,0.111,0.185,0.17 63 | C32.416,45.175,32.395,45.174,32.374,45.173z M35.719,27.645c0.056,0.062,0.112,0.125,0.167,0.188l-0.001-0.003 64 | C35.83,27.768,35.774,27.706,35.719,27.645z M18.868,30.65l0.179-0.141c0.622-0.961,3.872-1.893,3.872-2.636 65 | c-0.556,0.134-1.111,0.269-1.667,0.402c0,0,1.916-2.063,1.964-2.132c0.081-0.103,0.163-0.205,0.244-0.307 66 | c-0.66,0.412-1.321,0.824-1.981,1.236c-1.526,0.957-2.463,1.818-2.734,3.695C18.786,30.73,18.827,30.69,18.868,30.65z 67 | M39.928,32.968l-0.077-0.589c0.113,0.266,0.226,0.533,0.339,0.8c0.026-0.006,0.053-0.012,0.079-0.017 68 | c0.78-0.247,0.485-2.636,0.53-3.218c0.274-0.732,0.548-1.466,0.822-2.198c0.369-1.4,0.726-5.193-0.912-6.013 69 | c-0.021,0.003-0.044,0.005-0.065,0.007c-0.221,0.727,0.766,5.883-0.46,6.303c-0.014-0.016-0.026-0.029-0.039-0.043 70 | c-0.05-0.597,0.024-2.172-0.623-2.51c0.065,0.622,0.131,1.244,0.196,1.865c-0.121,1.283-0.241,2.567-0.362,3.851 71 | C39.546,31.793,39.737,32.381,39.928,32.968z M42.573,37.865c0.378-0.865,0.757-1.731,1.136-2.598 72 | c0.662-2.102,1.132-4.355,0.749-6.553c-0.104-0.602-0.356-2.457-1.068-2.688c-0.004,0-0.008,0.002-0.012,0.002 73 | c0,2.421,0.382,4.592-0.23,7.007c-0.331,0.691-0.662,1.383-0.994,2.075c0-1.326,0.377-2.669,0.308-4.059 74 | c-0.017-0.227-0.033-0.454-0.05-0.681c0,1.519-0.68,2.897-0.946,4.379c-0.199,1.408-0.397,2.817-0.596,4.226 75 | c-0.161,0.621-0.393,2.291-1.023,2.639h-0.059c-0.002-0.652-0.005-1.305-0.008-1.956c0,1.653-1.511,2.868-1.26,4.539 76 | l-0.083-0.728l-0.003,0.164c-0.312,0.831-1.482,0.658-2.085,0.959c0.058,0.143,0.115,0.283,0.173,0.426 77 | c-0.069,0.084-0.138,0.167-0.207,0.25c-0.099-0.002-0.197-0.005-0.295-0.008c0.151,0.094,0.304,0.188,0.457,0.281 78 | c1.68,0.582,2.854-1.464,3.691-2.402c0.118,0.076,0.236,0.153,0.354,0.23c0.401,0,2.597-4.418,3.612-6.087 79 | C43.669,37.671,42.895,37.988,42.573,37.865z M44.505,36.736c-0.089,0.102-0.218,0.295-0.372,0.548 80 | C44.35,37.104,44.505,36.911,44.505,36.736z M32.264,31.625c0.615,0.559,1.748,4.132,2.195,4.132 81 | c0.019-0.02,0.037-0.04,0.056-0.06c0.524-1.348-1.574-4.104-1.574-4.703L32.264,31.625z M36.02,40.563 82 | c0.04,0.055,0.08,0.109,0.121,0.164c0-0.916,0.409-4.144,0.246-5.899c0.122,0.371,0.249,0.727,0.458,0.965 83 | c0.093,0.02,0.185,0.039,0.276,0.059c0.085-0.324,0.169-0.648,0.253-0.973c0.022,0,0.32,1.158,0.739,1.264 84 | c0.218,0.966,0.437,1.932,0.654,2.898c0.801,0,0.748-3.886,0.426-4.576c-0.242-0.52-0.643-0.344-0.896-0.667 85 | c-0.292-1.547-0.248-2.977-1.038-4.424c-0.458-0.515-0.916-1.028-1.374-1.542c0.75,1.406,0.219,2.737,0.543,4.15 86 | c0.148,0.648,1.004,1.371,0.691,2.07c-0.124,0.203-0.827-0.427-0.908-0.525l-0.343,0.07c0.051,0.071,0.083,0.162,0.125,0.242 87 | l-0.432,0.578C36.422,36.295,35.378,38.584,36.02,40.563z M26.229,29.23l-0.7,0.201c-0.745-0.049-1.325,0.134-1.846,0.406 88 | c0.219-0.473,0.486-0.865,0.863-1.094l-0.549,0.394c-0.38,0.156-0.756,0.611-1.12,1.196c-0.752,0.543-1.466,1.171-2.498,1.412 89 | c-0.579,0.055-1.158,0.111-1.736,0.166c0.191,0.202,0.381,0.404,0.572,0.606c1.161,0.203,1.991-0.003,2.712-0.399 90 | c-0.654,1.348-1.233,2.635-1.673,2.635c1.939,0,2.422-1.858,2.936-3.546c0.525-0.454,1.059-0.953,1.709-1.407 91 | C25.342,29.611,25.785,29.421,26.229,29.23z M32.549,44.323l-0.016,0.165c0,0.013,0.009,0.021,0.009,0.035 92 | C32.544,44.457,32.547,44.391,32.549,44.323z"/> 93 | <path class="nest_outside_highlight" fill-rule="evenodd" clip-rule="evenodd" fill="#CEE18D" d="M25.299,0.993c-0.016-0.003-0.032-0.007-0.048-0.011 94 | c-0.119,0.086-0.223,0.193-0.31,0.325C25.057,1.203,25.168,1.099,25.299,0.993z M25.196,0.97 95 | c0.018,0.005,0.036,0.009,0.054,0.013c0.051-0.037,0.092-0.088,0.15-0.115L25.196,0.97z M22.455,5.215 96 | c-0.344,0.554-0.689,1.107-1.034,1.661c0.285-0.254,0.569-0.508,0.853-0.761c0.316-0.306,0.66-0.533,0.983-0.791 97 | c-0.162,0.611-0.325,1.223-0.487,1.835c-0.485,0.911-2.549,3.189-2.549,4.13c0.003,0.005,0.006,0.01,0.009,0.016 98 | c1.874,0,3.531-4.451,3.596-6.559c0.047-0.061,0.101-0.11,0.145-0.179c0.443-0.696,0.402-2.394,0.97-3.26 99 | C23.692,2.438,23.245,3.535,22.455,5.215z M19.698,12.461c0.011,0.091,0.021,0.181,0.032,0.271c0.02,0,0.04,0,0.059,0 100 | c0.033-0.033,0.074-0.075,0.098-0.101C19.824,12.575,19.761,12.518,19.698,12.461z M20.511,14.887 101 | c-0.166-0.021-0.332-0.045-0.498-0.067c0.019-0.694,0.038-1.39,0.057-2.083c-0.094-0.002-0.188-0.002-0.281-0.004 102 | c-0.481,0.494-1.777,1.848-1.506,2.475c0.168-0.014,0.336-0.027,0.504-0.041c0.146,1.32-2.428,3.838-1.362,5.971 103 | c0.145-0.545,0.288-1.088,0.432-1.633c0.542-1.698,2.039-2.581,2.583-4.246C20.464,15.135,20.487,15.011,20.511,14.887z 104 | M19.726,9.287c-0.002,0.031-0.005,0.062-0.007,0.094c0,0,0,0,0.001,0C19.721,9.35,19.724,9.318,19.726,9.287z M18.676,12.309 105 | c0.317-0.584,0.634-1.166,0.951-1.75c0.03-0.392,0.061-0.785,0.091-1.178c-0.347,0.367-1.876,2.305-1.076,2.93 106 | C18.654,12.31,18.665,12.309,18.676,12.309z M33.221,7.578c0.286-1.675-0.283-2.73-0.283-4.271 107 | c-0.05,0.008-0.101,0.018-0.151,0.027C32.825,3.223,32.863,3.111,32.901,3c-0.234,0.244-0.468,0.488-0.702,0.733 108 | c-0.129,0.62,0.63,3.919,0.928,4.449C33.158,7.981,33.189,7.779,33.221,7.578z M34.089,7.311l-0.091-0.424 109 | c-0.307,1.74,0.586,2.062,0.48,3.777c-0.17,0.916-0.341,1.832-0.511,2.748c0.05,0.01,0.1,0.018,0.148,0.025 110 | c0.183-0.161,0.942-2.046,0.942-2.046c0.018,0.168,0.035,0.336,0.052,0.504c0.036,0.967-0.529,2.176-0.375,3.065 111 | c0.037,0.009,0.074,0.018,0.111,0.026c0.432-0.168,0.637-0.856,0.743-1.249c0.473-1.748,0.125-3.878-0.711-5.451 112 | C34.615,7.961,34.353,7.636,34.089,7.311z M33.831,6.107c0.056,0.26,0.111,0.52,0.167,0.779c0.026-0.148,0.045-0.289,0.091-0.461 113 | C34.003,6.319,33.917,6.214,33.831,6.107z M30.759,16.951c0.177,0.049,0.354,0.098,0.53,0.146 114 | c0.825-0.336,1.686-1.012,2.067-1.828c0.055-0.123,0.109-0.246,0.164-0.369c0.008,0.004,0.016,0.008,0.023,0.012 115 | c0.04-0.045,0.081-0.09,0.121-0.136c-0.015-0.066-0.029-0.133-0.044-0.2C32.667,15.367,31.713,16.16,30.759,16.951z 116 | M22.158,12.854c0.46,2.253-2.373,4.041-2.373,5.417c0.003,0.013,0.005,0.025,0.008,0.038c0.293-0.146,0.587-0.291,0.881-0.436 117 | c-0.301,0.936-0.603,1.871-0.904,2.808c0.009,0.188,0.019,0.378,0.028,0.566c0-1.101,1.289-2.087,1.824-2.928 118 | c0.857-1.347,1.363-4.153,0.651-5.577C22.234,12.779,22.196,12.816,22.158,12.854z M22.451,11.973 119 | c0.201-0.104,0.403-0.207,0.604-0.311c0.352,0.252-0.184,1.327,0.471,1.327c0,0,0.348-3.579,0.406-4.075 120 | c-0.079,0.113-0.158,0.225-0.236,0.337C23.275,9.902,22.362,11.123,22.451,11.973z M37.312,24.561 121 | c0.35,1.297-0.788,3.143,0.792,4.148l0.108-0.004c0.024-0.027,0.048-0.056,0.072-0.084c0.314-1.003-0.297-2.547-0.662-3.465 122 | L37.312,24.561z M39.517,14.902c0.112,0.135,0.239,0.302,0.37,0.483c-0.075-0.378-0.234-0.694-0.537-0.898L39.517,14.902z 123 | M39.887,15.386c0.239,1.201-0.399,3.032-0.174,3.934c0.465,1.009,0.649-0.673,0.783-0.788c0.138,0.281,0.275,0.562,0.413,0.844 124 | c0.053-0.021,0.104-0.043,0.157-0.064C41.625,18.205,40.658,16.451,39.887,15.386z"/> 125 | </g> 126 | <g class="bird"> 127 | <path class="bird_wings_light bird_right" fill-rule="evenodd" clip-rule="evenodd" fill="#F0D26B" d="M46.859,98.241c0.002,0,0.006,0.002,0.009,0.002 128 | c0-0.897-0.815-1.87-1.644-2.759c0.203-1.197-1.104-1.819-1.176-3.043c0.03-0.016,0.061-0.033,0.091-0.051 129 | c0.043,0.023,0.087,0.045,0.13,0.068c0.021-0.043,0.012-0.101,0.019-0.15c0.017-0.01,0.033-0.018,0.049-0.027 130 | c-0.011-0.037-0.021-0.074-0.032-0.111c-0.012-0.141-0.059-0.297-0.133-0.467c-0.103-0.363-0.207-0.725-0.31-1.088 131 | c0.093-0.085,0.187-0.17,0.28-0.255c0.158-0.415-0.458-1.287-0.719-1.562c0.023-0.088,0.049-0.177,0.072-0.265 132 | c0-0.351-0.337-2.192-0.843-2.192c0-0.265,0.689-2.647-0.681-2.647c-0.136-0.198-0.23-1.762-0.675-2.418 133 | c-0.327-0.484-0.953-0.847-1.29-1.018c-0.1-0.031-0.186-0.066-0.23-0.11c0,0,0.094,0.041,0.23,0.11 134 | c0.496,0.152,1.53,0.135,1.751-0.088c0-1.194-1.403-1.485-1.536-1.906c0.406-0.148,0.812-0.296,1.219-0.444 135 | c0.366-0.55,0.047-1.157-0.536-1.394c-0.001-0.007-0.001-0.015-0.002-0.021c0.441-0.087,0.884-0.173,1.325-0.26 136 | c0.351-0.92-2.139-2.065-2.735-2.065c0.156-0.105,0.314-0.212,0.472-0.317c-0.002-0.049-0.004-0.098-0.007-0.146l-0.448-0.287 137 | c-0.008-0.012-0.015-0.022-0.021-0.034c0.525-0.251,1.051-0.501,1.576-0.751c0.518-1.034-3.134-1.462-3.509-1.418 138 | c-0.174,0.026-0.349,0.053-0.522,0.08c0-0.511,2.03-1.027,1.737-1.803c-0.072-0.104-0.146-0.206-0.219-0.309 139 | c-1.822-0.607-4.125,0.416-5.447,1.674c-0.047,0.013-0.095,0.024-0.143,0.037c0,5.08,2.023,9.756,4.825,13.866 140 | c-0.039,0.895,1.364,3.48,1.879,3.96c0.115,0.286,0.267,0.604,0.453,0.953c0.931,2.127,1.993,4.182,3.477,5.92 141 | c0.323,0.379,1.099,1.286,1.87,1.955c0.683,0.691,1.313,1.211,1.453,1.123c0.002-0.004,0.004-0.007,0.006-0.011 142 | C46.928,98.469,46.898,98.357,46.859,98.241z"/> 143 | <path class="bird_wings_light bird_left" fill-rule="evenodd" clip-rule="evenodd" fill="#F0D26B" d="M24.307,68.202l0.032,0.498 144 | c0.191-2.344-6.252-5.391-7.093-2.265c0,0.838,1.323,1.471,1.323,2.036c-0.626,0-0.896-0.706-1.366,0.143 145 | c-0.001,0.033-0.001,0.065-0.002,0.099c0.061,0.056,0.122,0.111,0.183,0.168c-0.314,0.172-0.628,0.345-0.943,0.517 146 | c-0.009,0.057-0.017,0.113-0.025,0.17c0.171,0.208,0.343,0.415,0.515,0.622c-0.285,0-0.701,0.181-0.701,0.508 147 | c0.085,0.105,0.17,0.211,0.255,0.315c-0.619,0-1.424,0.051-1.724,0.698c0,0.341,0.837,1.325,1.188,1.325v0.013 148 | c-1.311,0-2.587,1.031-2.587,2.266c-0.678,0-1.001,0.421-1.444,0.864c0.023,0.328,0.046,0.657,0.07,0.985 149 | c0,0.211-1.113,0.556-1.113,1.166c-0.521,0.125-1.357,0.658-1.419,1.274c0.009,0.105,0.017,0.212,0.025,0.317 150 | c-0.096,0.021-0.193,0.044-0.29,0.066c-0.534,0.366-0.855,0.681-0.855,1.353c-0.955,0-0.856,1.208-0.856,1.777 151 | c-0.921,0.442-1.57,1.125-1.57,2.108c-1.257,0-2.554,1.66-3.29,2.396c-1.293,0-1.413,1.66-1.796,1.924 152 | c-0.4,0.191-0.88,0.608-0.819,1.116c0.667,0.792,6.02-3.064,6.933-3.604c5.494-3.249,10.675-7.733,14.402-12.93 153 | C22.36,72.705,24.836,70.198,24.307,68.202z"/> 154 | <path class="bird_wings_mid bird_left" fill-rule="evenodd" clip-rule="evenodd" fill="#C19B27" d="M2.761,88.942c-0.509,0.307-1.019,0.614-1.528,0.921 155 | c2.251,0,5.055-1.95,6.924-3.373c0.311-0.236,0.615-0.469,0.922-0.703c-0.513,0.221-1.026,0.441-1.54,0.661 156 | C5.924,87.16,4.14,87.819,2.761,88.942z M9.385,85.655c-0.048,0-0.097,0.007-0.145,0.009c-0.053,0.041-0.106,0.082-0.16,0.123 157 | C9.182,85.742,9.284,85.699,9.385,85.655z M9.24,85.664c0.694-0.529,1.381-1.059,2.056-1.588 158 | c-2.11,0.785-4.222,1.568-6.332,2.354C6.404,86.43,7.862,85.713,9.24,85.664z M14.575,81.744c-0.098,0-0.267,0.016-0.477,0.037 159 | c-0.07,0.061-0.139,0.12-0.209,0.18C14.118,81.889,14.347,81.816,14.575,81.744z M11.388,84.042 160 | c-0.016-0.001-0.031-0.001-0.046-0.001c-0.015,0.012-0.031,0.023-0.045,0.035C11.326,84.064,11.357,84.053,11.388,84.042z 161 | M16.195,79.903h-0.009l-0.002,0.002C16.187,79.904,16.191,79.904,16.195,79.903z M22.349,65.987 162 | C22.349,65.987,22.349,65.987,22.349,65.987c0.004-0.011,0.004-0.02,0.008-0.03C22.354,65.967,22.352,65.977,22.349,65.987z 163 | M22.349,65.987c-0.15,0.419,0.295,0.604,0.295,0.936c-0.307-0.075-0.614-0.149-0.921-0.224c0,0.642,0.966,1.042,0.966,1.34 164 | c-0.726,0-1.533-0.712-2.298-0.357c-0.46,0.636,1.934,1.593,2.064,1.843c-0.634,0-3.73-0.035-4.03,0.321 165 | c0,0.994,2.335,0.895,2.747,1.566c-1.455,0.285-2.91,0.571-4.364,0.857v0.042c0.492,0.803,3.397,0.344,3.539,0.649 166 | c-0.975,0.308-5.145,1.154-5.522,1.902c-0.009,0.105-0.019,0.211-0.027,0.317c0,0.417,2.73,0.159,3.668,0.159v0.007 167 | c-1.643,0.52-3.286,1.04-4.928,1.56c-0.252,0.797,3.18,0.688,3.415,0.688c0.116,0.013,0.232,0.024,0.349,0.037 168 | c-1.757,0.467-3.514,0.934-5.271,1.401c0.014,0.046,0.028,0.091,0.042,0.137c0.386,1.014,3.283,0.735,4.113,0.733 169 | c1.636-1.56,3.177-3.247,4.586-5.197C22.333,72.547,26.388,67.861,22.349,65.987z M22.323,66.082 170 | c0.008-0.031,0.017-0.063,0.025-0.095c-0.018-0.009-0.029-0.019-0.047-0.026C22.309,66.001,22.316,66.042,22.323,66.082z 171 | M8.541,83.774c-0.001,0.021-0.001,0.043-0.002,0.065c0.037,0.046,0.074,0.091,0.11,0.137c0.897,0.021,1.794,0.043,2.692,0.064 172 | c0.869-0.683,1.717-1.375,2.548-2.08c-0.051,0.016-0.103,0.033-0.154,0.049C13.028,82.199,8.919,83.101,8.541,83.774z 173 | M14.098,81.781c0.71-0.607,1.408-1.23,2.085-1.876c-2.133,0.401-4.266,0.8-6.398,1.201 174 | C9.785,82.312,12.808,81.916,14.098,81.781z"/> 175 | <path class="bird_wings_mid bird_right" fill-rule="evenodd" clip-rule="evenodd" fill="#C19B27" d="M43.888,94.938c0.001-0.023,0.003-0.047,0.004-0.07 176 | c0.076,0.018,0.153,0.037,0.229,0.056c-0.393-0.8-0.786-1.599-1.179-2.398c0.117-0.065,0.234-0.132,0.352-0.198 177 | c0.202-0.471-1.119-1.336-1.352-1.657c-0.064-0.229,1.127-0.075,1.328-0.136c0-1.125-2.707-1.775-3.186-2.532l2.159,0.457 178 | c0-1.251-2.653-3.417-2.653-3.417c0.569-0.05,1.139-0.1,1.707-0.149c0.385-0.694-1.875-1.692-2.068-2.18v-0.032l1.57-0.117 179 | c0-0.99-3.186-1.982-3.686-2.498l0.009-0.032c1.024-0.202,2.05-0.404,3.074-0.606c0.232-0.845-1.791-1.56-2.321-1.651 180 | c-0.123-0.032-0.245-0.065-0.368-0.098c0.448-0.156,0.896-0.312,1.343-0.47c0-0.447-2.763-0.939-2.977-1.052v-0.013 181 | c1.268-0.286,2.536-0.572,3.804-0.859c0-1.069-3.549-1.002-4.045-1.168c1.123-0.474,2.246-0.947,3.369-1.422 182 | c-0.062-0.034-0.125-0.069-0.188-0.104c-0.938-0.173-2.58-0.353-3.438,0.139c-0.131,0.077-0.262,0.155-0.392,0.233 183 | c0.192-0.457,0.385-0.915,0.576-1.372c-0.058-0.064-0.116-0.129-0.174-0.194c-0.172,0.061-0.345,0.119-0.518,0.18 184 | c0-0.626,1.64-1.467,1.217-2.156c-1.146-0.274-2.161,1.291-2.562,2.107l-0.377-0.041c-0.408,5.262,3.519,12.397,6.075,16.913 185 | l1.541,3.064c0.994,1.487,1.564,3.181,2.892,4.39c0.54,0.562,1.08,1.123,1.619,1.686C44.812,96.672,44.35,95.805,43.888,94.938z" 186 | /> 187 | <path class="bird_beak" fill-rule="evenodd" clip-rule="evenodd" fill="#807459" d="M34.859,52.907c-1.285,0-2.446,0.512-3.687,0.598 188 | c-0.008,0.04-0.016,0.081-0.023,0.121c0.042,0.063,0.088,0.11,0.133,0.167c-0.037,0.021-0.074,0.043-0.111,0.063 189 | c0.006,0.028,0.01,0.058,0.016,0.086c0.63,1.086,1.307,1.616,1.54,2.963c0.02,0.147,0.04,0.294,0.061,0.441 190 | c0.004,0.006,0.009,0.011,0.013,0.017h0.012c0.103-0.258,0.206-0.515,0.31-0.771c0.579-1.223,1.159-2.445,1.737-3.666 191 | C34.859,52.92,34.859,52.913,34.859,52.907z"/> 192 | <path class="bird_leg bird_left" fill-rule="evenodd" clip-rule="evenodd" fill="#F0D26B" d="M23.751,50.975c0.052-0.09,0.1-0.151,0.148-0.219 193 | c0.005-0.023,0.007-0.049,0.013-0.073L23.751,50.975z M25.247,53.586c-0.084-0.334-0.251-4.348-1.348-2.83 194 | c-0.26,1.188,0.085,2.23,0.004,3.433c-0.104,1.542-0.489,2.977-0.515,4.534c-0.006,0.32-0.033,2.615,0.818,2.173 195 | c0.197-1.052,0.394-2.105,0.591-3.156l1.028-2.654c0.002-0.059,0.005-0.117,0.007-0.176 196 | C25.638,54.468,25.442,54.026,25.247,53.586z"/> 197 | <path class="bird_foot bird_right" fill-rule="evenodd" clip-rule="evenodd" fill="#807459" d="M35.169,52.573c0.011-0.044,0.008-0.093,0.017-0.137 198 | c-0.034-0.062-0.069-0.125-0.103-0.188L35.169,52.573z M36.702,46.453l-0.448,0.559c-0.148-0.058-0.297-0.115-0.446-0.174 199 | c-0.521-0.676,0.704-4.403-0.193-3.994c-0.849,0.59-0.159,1.64-0.468,2.393c-0.02-0.025-0.039-0.051-0.059-0.076 200 | c-0.295-0.84-0.576-1.943-0.04-2.479v-0.008c-0.11-0.052-0.22-0.104-0.329-0.155c-1.079-0.049-0.492,1.454-0.492,1.955 201 | c-0.062,0-0.844-1.454-0.448-1.71c0.099,0.01,0.198,0.018,0.298,0.025c-0.227-0.453-0.991-0.154-1.079,0.244 202 | c0.168,0.462,0.336,0.924,0.504,1.385c0.548,2.308,2.18,5.602,1.685,8.019c0.125,0.229,0.251,0.457,0.377,0.686 203 | c1.274,0.449-0.037-4.181,0.073-4.988c0.033-0.029,0.066-0.058,0.1-0.086l0.179,0.021c0.946,0.471,0.696-1.641,1.266-1.641 204 | C37.021,46.437,36.861,46.444,36.702,46.453z"/> 205 | <path class="bird_foot bird_left" fill-rule="evenodd" clip-rule="evenodd" fill="#807459" d="M24.584,42.182c-0.559,0.113-0.728,1.102-1.056,1.176 206 | c-0.005-0.002-0.009-0.004-0.013-0.006c-0.197-0.393,0.562-0.77,0.562-0.955c0.002-0.002,0.004-0.003,0.007-0.005 207 | c-0.043-0.054-0.087-0.108-0.13-0.163c-0.488-0.701-1.067,0.893-1.067,0.893c0-0.26,0.03-1.025,0.405-1.025 208 | c-0.182-0.061-0.362-0.119-0.544-0.18c-2.619,1.77,0.924,6.799,1.22,8.928l-0.044-0.228c0.107,0.136,0.214,0.271,0.321,0.408 209 | c1.331,0.142-0.92-3.851-0.92-4.417c0.431,0,0.744,0.246,1.22-0.017c-0.061-0.069-0.12-0.14-0.181-0.21 210 | c-1.266-0.032-0.687-4.287,0.825-3.43c0.033-0.008,0.066-0.016,0.101-0.023C25.653,42.732,24.865,42.125,24.584,42.182z"/> 211 | <path class="bird_wings_dark" fill-rule="evenodd" clip-rule="evenodd" fill="#A88714" d="M44.62,96.442c-3.014-3.387-5.575-8.883-7.348-13.56 212 | c-0.48-0.894-0.901-1.818-1.388-2.701c-0.095-0.714-0.488-1.478-0.854-2.047c-0.251-0.39-0.99-3.704-1.393-5.931 213 | c0.336-0.63,0.329-1.396,0.816-2.122c0.811-0.46,1.621-0.972,1.756-1.121c0.001-0.012,0.003-0.023,0.004-0.035 214 | c-0.096-0.421-0.66-0.211-0.898-0.211c0.174-0.333,0.348-0.666,0.521-0.998c-0.034-0.206-0.53,0.121-0.668,0.276 215 | c0.331-0.532,0.664-1.064,0.995-1.596c-0.017-0.047-0.033-0.093-0.05-0.139c-0.052,0-0.099,0.025-0.148,0.037 216 | c0.089-0.197,0.162-0.379,0.199-0.519c0.012-0.046,0.063-0.301,0.104-0.592c0.017-0.078,0.021-0.154,0.029-0.233 217 | c0.039-0.376,0.036-0.738-0.111-0.738c-0.003,0.004-0.005,0.007-0.008,0.011c-0.007-0.014-0.006-0.03-0.014-0.043 218 | c-0.029-0.003-0.06-0.006-0.089-0.009c-0.285,0.097-0.388,0.402-0.432,0.736c-0.351,0.448-0.701,0.896-1.052,1.346 219 | c-0.066-0.027-0.133-0.055-0.199-0.082c-0.039-0.108-0.078-0.216-0.117-0.324l-0.42,0.357c-0.216-0.037-0.432-0.075-0.648-0.112 220 | c-0.433-0.193-0.865-0.386-1.298-0.579c-1.014-0.118-1.854,0.881-2.806,1.064l-0.152-0.015l-0.431-0.762 221 | c-0.872-0.224-1.311,0.737-2.05,0.737c-2.21-0.86-2.352-3.703-2.352-5.716l-0.121,0.205l-0.627-1.104 222 | c-0.005,0.008-0.009,0.016-0.013,0.022c-0.035,0.896-0.069,1.791-0.104,2.687c-0.463,0.621-0.562,0.965-0.503,1.19 223 | c-0.09-0.021-0.181-0.031-0.271-0.01c-0.104,0.094-0.208,0.187-0.313,0.28c0.202,0.321,0.405,0.642,0.606,0.962 224 | c0-0.045-1.393-0.188-1.393,0.128c0.192,0.139,0.384,0.276,0.577,0.415c-0.147,0.062-0.295,0.123-0.443,0.185 225 | c0.198,0,0.395,0.092,0.592,0.222c-0.006,0.003-0.012,0.006-0.019,0.01c-0.005,0.036-0.011,0.072-0.016,0.109 226 | c0.555,0.546,1.111,1.092,1.666,1.638c0.049,0.187,0.083,0.377,0.101,0.563c-1.694,3.693-3.855,7.102-6.607,10.13 227 | c-1.303,1.277-2.605,2.555-3.909,3.832l0.051,0.053c-2.729,2.318-6.702,4.88-9.035,6.215c-1.428,0.779-2.856,1.56-4.283,2.338 228 | c0.019,0.011,0.038,0.021,0.057,0.031c2.301,0,10.431-5.957,12.604-7.571c0.732-0.544,2.703-1.625,3.11-2.691 229 | c0.395-0.374,0.722-0.696,0.831-0.812c1.647-1.222,3.295-2.443,4.942-3.665c0.52-0.87,1.041-1.739,1.56-2.609 230 | c0.609-0.787,1.218-1.574,1.827-2.361c0.627-0.332,1.254-0.665,1.88-0.997c0.58,0,1.144-0.051,1.708-0.113 231 | c0.9,0.066,1.817,0.049,2.15,0.196c1.13,1.844,1.1,4.259,1.836,6.315c0.596,0.985,1.191,1.97,1.787,2.955 232 | c0.108,0.239,0.217,0.479,0.326,0.719c0.57,1.443,1.141,2.888,1.711,4.331c1.96,3.137,3.92,6.274,5.88,9.411 233 | c0.822,1.375,1.754,2.969,3.043,3.964c0.385,0.129,0.769,0.259,1.152,0.389c0.016,0.011,0.021,0.006,0.021-0.009 234 | C45.711,97.652,44.999,96.868,44.62,96.442z"/> 235 | <path class="bird_leg bird_right" fill-rule="evenodd" clip-rule="evenodd" fill="#F0D26B" d="M36.286,54.261c0.029-0.443,0.451-1.786-0.075-2.049 236 | c-0.122,0-1.047-0.01-1.068,0c-0.574,0.63,0.458,4.866-1.316,6.157c-0.479,0.13-0.959,0.266-1.157,0.612 237 | c0.053,0.298,0.106,0.596,0.16,0.894c0.346,0.33,0.748,0.459,1.217,0.448c0.144-0.028,0.288-0.093,0.428-0.174 238 | c0.147,0.161,0.29,0.337,0.418,0.547c0.263,0.661,0.526,1.323,0.789,1.984c0.022,0.026,0.046,0.052,0.068,0.078 239 | c0.028,0.009,0.057,0.017,0.085,0.024c0.623,0,0.517-1.71,0.489-2.042C36.128,58.304,36.124,56.702,36.286,54.261z"/> 240 | <path class="bird_head" fill-rule="evenodd" clip-rule="evenodd" fill="#C19B27" d="M36.232,64.212c0.035,0.04,0.053,0.102,0.074,0.159 241 | c-0.074-1.517-0.629-3.146-1.57-4.255c-0.472-0.428-0.941-0.857-1.413-1.286c-0.633-1.561-0.462-3.318-1.092-4.775 242 | c-3.904-2.394-5.732-1.371-7.756,2.966l0.261-0.475c-1.144,2.629-2.141,9.364,1.791,10.536c0.69,0.205,1.118-0.964,1.715-0.964 243 | c0.012,0.006,0.023,0.012,0.035,0.017c0.073,0.225,0.147,0.448,0.221,0.673c0.962,0.822,2.769-1.089,3.634-0.993 244 | c0.438,0.271,0.876,0.543,1.313,0.813c0.007,0.075,0.007,0.152,0.011,0.228c0.007,0.004,0.015,0.008,0.022,0.011 245 | c0.2-0.229,0.399-0.458,0.6-0.688c0.055,0.053,0.109,0.104,0.164,0.155c1.168,0.369,1.397-1.874,1.944-2.119 246 | C36.202,64.214,36.217,64.213,36.232,64.212z"/> 247 | <path class="bird_mask" fill-rule="evenodd" clip-rule="evenodd" fill="#DAB852" d="M32.067,54.058c-0.433-0.278-0.666-0.357-1.099-0.636 248 | c-0.073-0.07-0.656-0.128-0.729-0.197c3.152,1.567-1.142,5.89,0.223,6.927h0.056c0.051-0.047,0.101-0.094,0.151-0.141 249 | c0.008-0.001,0.016-0.003,0.023-0.004c0.181,0.237-0.016,0.958,0.408,0.958c0.054-0.076,0.106-0.152,0.159-0.229 250 | c0.008,0.001,0.015,0.002,0.022,0.002c0.247,0.367,0.099,1.107,0.608,1.315c0.025,0.001,0.051,0.003,0.076,0.005 251 | c0.007-0.007,0.013-0.014,0.02-0.021c0.045-0.407,0.091-0.814,0.136-1.223c0.111,0.143,0.222,0.285,0.332,0.428 252 | c0.134-0.414,0.267-0.828,0.399-1.243c0.06,0.034,0.118,0.068,0.177,0.103C33.833,59.493,32.577,54.797,32.067,54.058z"/> 253 | <path class="bird_eye" fill-rule="evenodd" clip-rule="evenodd" fill="#563B07" d="M32.463,56.46c-0.095-0.03-0.19-0.061-0.284-0.09 254 | c0.043,0.016,0.086,0.03,0.13,0.046c-0.753-0.041-0.986,1.233-0.198,1.233C32.562,57.495,32.693,56.853,32.463,56.46z"/> 255 | </g> 256 | </svg> 257 | --------------------------------------------------------------------------------