diff --git a/README.rst b/README.rst index 60e1b9e8c..139453344 100644 --- a/README.rst +++ b/README.rst @@ -49,7 +49,7 @@ integration. It's commercial open-source software, released under the MIT licens `New in v2.0`_ New features, backwards incompatibilities and migration guide. `API Reference`_ The detailed reference for spaCy's API. `Models`_ Download statistical language models for spaCy. -`Resources`_ Libraries, extensions, demos, books and courses. +`Universe`_ Libraries, extensions, demos, books and courses. `Changelog`_ Changes and version history. `Contribute`_ How to contribute to the spaCy project and code base. =================== === @@ -59,7 +59,7 @@ integration. It's commercial open-source software, released under the MIT licens .. _Usage Guides: https://spacy.io/usage/ .. _API Reference: https://spacy.io/api/ .. _Models: https://spacy.io/models -.. _Resources: https://spacy.io/usage/resources +.. _Universe: https://spacy.io/universe .. _Changelog: https://spacy.io/usage/#changelog .. _Contribute: https://github.com/explosion/spaCy/blob/master/CONTRIBUTING.md diff --git a/examples/training/train_textcat.py b/examples/training/train_textcat.py index 34de9f836..61cd7e51e 100644 --- a/examples/training/train_textcat.py +++ b/examples/training/train_textcat.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # coding: utf8 -"""Train a multi-label convolutional neural network text classifier on the +"""Train a convolutional neural network text classifier on the IMDB dataset, using the TextCategorizer component. The dataset will be loaded automatically via Thinc's built-in dataset loader. The model is added to spacy.pipeline, and predictions are available via `doc.cats`. For more details, diff --git a/website/_harp.json b/website/_harp.json index 7da11afa4..a7e12da4b 100644 --- a/website/_harp.json +++ b/website/_harp.json @@ -1,7 +1,7 @@ { "globals": { "title": "spaCy", - "description": "spaCy is a free open-source library featuring state-of-the-art speed and accuracy and a powerful Python API.", + "description": "spaCy is a free open-source library for Natural Language Processing in Python. It features NER, POS tagging, dependency parsing, word vectors and more.", "SITENAME": "spaCy", "SLOGAN": "Industrial-strength Natural Language Processing in Python", @@ -10,10 +10,13 @@ "COMPANY": "Explosion AI", "COMPANY_URL": "https://explosion.ai", - "DEMOS_URL": "https://demos.explosion.ai", + "DEMOS_URL": "https://explosion.ai/demos", "MODELS_REPO": "explosion/spacy-models", + "KERNEL_BINDER": "ines/spacy-binder", + "KERNEL_PYTHON": "python3", "SPACY_VERSION": "2.0", + "BINDER_VERSION": "2.0.11", "SOCIAL": { "twitter": "spacy_io", @@ -26,7 +29,8 @@ "NAVIGATION": { "Usage": "/usage", "Models": "/models", - "API": "/api" + "API": "/api", + "Universe": "/universe" }, "FOOTER": { @@ -34,7 +38,7 @@ "Usage": "/usage", "Models": "/models", "API Reference": "/api", - "Resources": "/usage/resources" + "Universe": "/universe" }, "Support": { "Issue Tracker": "https://github.com/explosion/spaCy/issues", @@ -82,8 +86,8 @@ } ], - "V_CSS": "2.0.1", - "V_JS": "2.0.1", + "V_CSS": "2.1.1", + "V_JS": "2.1.0", "DEFAULT_SYNTAX": "python", "ANALYTICS": "UA-58931649-1", "MAILCHIMP": { diff --git a/website/_includes/_functions.jade b/website/_includes/_functions.jade index 39139cc58..c93469db1 100644 --- a/website/_includes/_functions.jade +++ b/website/_includes/_functions.jade @@ -15,12 +15,39 @@ - MODEL_META = public.models._data.MODEL_META - MODEL_LICENSES = public.models._data.MODEL_LICENSES - MODEL_BENCHMARKS = public.models._data.MODEL_BENCHMARKS +- EXAMPLE_SENT_LANGS = public.models._data.EXAMPLE_SENT_LANGS - EXAMPLE_SENTENCES = public.models._data.EXAMPLE_SENTENCES - IS_PAGE = (SECTION != "index") && !landing - IS_MODELS = (SECTION == "models" && LANGUAGES[current.source]) - HAS_MODELS = IS_MODELS && CURRENT_MODELS.length +//- Get page URL + +- function getPageUrl() { +- var path = current.path; +- if(path[path.length - 1] == 'index') path = path.slice(0, path.length - 1); +- return `${SITE_URL}/${path.join('/')}`; +- } + +//- Get pretty page title depending on section + +- function getPageTitle() { +- var sections = ['api', 'usage', 'models']; +- if (sections.includes(SECTION)) { +- var titleSection = (SECTION == "api") ? 'API' : SECTION.charAt(0).toUpperCase() + SECTION.slice(1); +- return `${title} · ${SITENAME} ${titleSection} Documentation`; +- } +- else if (SECTION != 'index') return `${title} · ${SITENAME}`; +- return `${SITENAME} · ${SLOGAN}`; +- } + +//- Get social image based on section and settings + +- function getPageImage() { +- var img = (SECTION == 'api') ? 'api' : 'default'; +- return `${SITE_URL}/assets/img/social/preview_${preview || img}.jpg`; +- } //- Add prefixes to items of an array (for modifier CSS classes) array - [array] list of class names or options, e.g. ["foot"] diff --git a/website/_includes/_mixins.jade b/website/_includes/_mixins.jade index 158668de5..105d04b8a 100644 --- a/website/_includes/_mixins.jade +++ b/website/_includes/_mixins.jade @@ -7,7 +7,7 @@ include _functions id - [string] anchor assigned to section (used for breadcrumb navigation) mixin section(id) - section.o-section(id="section-" + id data-section=id) + section.o-section(id=id ? "section-" + id : null data-section=id)&attributes(attributes) block @@ -143,7 +143,7 @@ mixin aside-wrapper(label, emoji) mixin aside(label, emoji) +aside-wrapper(label, emoji) - .c-aside__text.u-text-small + .c-aside__text.u-text-small&attributes(attributes) block @@ -154,7 +154,7 @@ mixin aside(label, emoji) prompt - [string] prompt displayed before first line, e.g. "$" mixin aside-code(label, language, prompt) - +aside-wrapper(label) + +aside-wrapper(label)&attributes(attributes) +code(false, language, prompt).o-no-block block @@ -165,7 +165,7 @@ mixin aside-code(label, language, prompt) argument to be able to wrap it for spacing mixin infobox(label, emoji) - aside.o-box.o-block.u-text-small + aside.o-box.o-block.u-text-small&attributes(attributes) if label h3.u-heading.u-text-label.u-color-theme if emoji @@ -242,7 +242,9 @@ mixin button(url, trusted, ...style) wrap - [boolean] wrap text and disable horizontal scrolling mixin code(label, language, prompt, height, icon, wrap) - pre.c-code-block.o-block(class="lang-#{(language || DEFAULT_SYNTAX)}" class=icon ? "c-code-block--has-icon" : null style=height ? "height: #{height}px" : null)&attributes(attributes) + - var lang = (language != "none") ? (language || DEFAULT_SYNTAX) : null + - var lang_class = (language != "none") ? "lang-" + (language || DEFAULT_SYNTAX) : null + pre.c-code-block.o-block(data-language=lang class=lang_class class=icon ? "c-code-block--has-icon" : null style=height ? "height: #{height}px" : null)&attributes(attributes) if label h4.u-text-label.u-text-label--dark=label if icon @@ -253,6 +255,15 @@ mixin code(label, language, prompt, height, icon, wrap) code.c-code-block__content(class=wrap ? "u-wrap" : null data-prompt=prompt) block +//- Executable code + +mixin code-exec(label, large) + - label = (label || "Editable code example") + " (experimental)" + +terminal-wrapper(label, !large) + figure.thebelab-wrapper + span.thebelab-wrapper__text.u-text-tiny v#{BINDER_VERSION} · Python 3 · via #[+a("https://mybinder.org/").u-hide-link Binder] + +code(data-executable="true")&attributes(attributes) + block //- Wrapper for code blocks to display old/new versions @@ -658,12 +669,16 @@ mixin qs(data, style) //- Terminal-style code window label - [string] title displayed in top bar of terminal window -mixin terminal(label, button_text, button_url) - .x-terminal - .x-terminal__icons: span - .u-padding-small.u-text-label.u-text-center=label +mixin terminal-wrapper(label, small) + .x-terminal(class=small ? "x-terminal--small" : null) + .x-terminal__icons(class=small ? "x-terminal__icons--small" : null): span + .u-padding-small.u-text-center(class=small ? "u-text-tiny" : "u-text") + strong=label + block - +code.x-terminal__code +mixin terminal(label, button_text, button_url, exec) + +terminal-wrapper(label) + +code.x-terminal__code(data-executable=exec ? "" : null) block if button_text && button_url diff --git a/website/_includes/_navigation.jade b/website/_includes/_navigation.jade index 8ce5e394b..71868987a 100644 --- a/website/_includes/_navigation.jade +++ b/website/_includes/_navigation.jade @@ -10,10 +10,7 @@ nav.c-nav.u-text.js-nav(class=landing ? "c-nav--theme" : null) li.c-nav__menu__item(class=is_active ? "is-active" : null) +a(url)(tabindex=is_active ? "-1" : null)=item - li.c-nav__menu__item.u-hidden-xs - +a("https://survey.spacy.io", true) User Survey 2018 - - li.c-nav__menu__item.u-hidden-xs + li.c-nav__menu__item +a(gh("spaCy"))(aria-label="GitHub") #[+icon("github", 20)] progress.c-progress.js-progress(value="0" max="1") diff --git a/website/_includes/_page_models.jade b/website/_includes/_page_models.jade index d0fe05a6c..16d68fd8a 100644 --- a/website/_includes/_page_models.jade +++ b/website/_includes/_page_models.jade @@ -1,77 +1,110 @@ //- 💫 INCLUDES > MODELS PAGE TEMPLATE for id in CURRENT_MODELS + - var comps = getModelComponents(id) +section(id) - +grid("vcenter").o-no-block(id=id) - +grid-col("two-thirds") - +h(2) - +a("#" + id).u-permalink=id + section(data-vue=id data-model=id) + +grid("vcenter").o-no-block(id=id) + +grid-col("two-thirds") + +h(2) + +a("#" + id).u-permalink=id - +grid-col("third").u-text-right - .u-color-subtle.u-text-tiny - +button(gh("spacy-models") + "/releases", true, "secondary", "small")(data-tpl=id data-tpl-key="download") - | Release details - .u-padding-small Latest: #[code(data-tpl=id data-tpl-key="version") n/a] + +grid-col("third").u-text-right + .u-color-subtle.u-text-tiny + +button(gh("spacy-models") + "/releases", true, "secondary", "small")(v-bind:href="releaseUrl") + | Release details + .u-padding-small Latest: #[code(v-text="version") n/a] - +aside-code("Installation", "bash", "$"). - python -m spacy download #{id} + +aside-code("Installation", "bash", "$"). + python -m spacy download #{id} - - var comps = getModelComponents(id) + p(v-if="description" v-text="description") - p(data-tpl=id data-tpl-key="description") - - div(data-tpl=id data-tpl-key="error") - +infobox + +infobox(v-if="error") | Unable to load model details from GitHub. To find out more | about this model, see the overview of the | #[+a(gh("spacy-models") + "/releases") latest model releases]. - +table.o-block-small(data-tpl=id data-tpl-key="table") - +row - +cell #[+label Language] - +cell #[+tag=comps.lang] #{LANGUAGES[comps.lang]} - for comp, label in {"Type": comps.type, "Genre": comps.genre} + +table.o-block-small(v-bind:data-loading="loading") +row - +cell #[+label=label] - +cell #[+tag=comp] #{MODEL_META[comp]} - +row - +cell #[+label Size] - +cell #[+tag=comps.size] #[span(data-tpl=id data-tpl-key="size") #[em n/a]] + +cell #[+label Language] + +cell #[+tag=comps.lang] #{LANGUAGES[comps.lang]} + for comp, label in {"Type": comps.type, "Genre": comps.genre} + +row + +cell #[+label=label] + +cell #[+tag=comp] #{MODEL_META[comp]} + +row + +cell #[+label Size] + +cell #[+tag=comps.size] #[span(v-text="sizeFull" v-if="sizeFull")] #[em(v-else="") n/a] - each label in ["Pipeline", "Vectors", "Sources", "Author", "License"] - - var field = label.toLowerCase() - if field == "vectors" - - field = "vecs" - +row - +cell.u-nowrap - +label=label - if MODEL_META[field] - | #[+help(MODEL_META[field]).u-color-subtle] + +row(v-if="pipeline && pipeline.length" v-cloak="") +cell - span(data-tpl=id data-tpl-key=field) #[em n/a] + +label Pipeline #[+help(MODEL_META.pipeline).u-color-subtle] + +cell + span(v-for="(pipe, index) in pipeline" v-if="pipeline") + code(v-text="pipe") + span(v-if="index != pipeline.length - 1") ,  - +row(data-tpl=id data-tpl-key="compat-wrapper" hidden="") - +cell - +label Compat #[+help("Latest compatible model version for your spaCy installation").u-color-subtle] - +cell - .o-field.u-float-left - select.o-field__select.u-text-small(data-tpl=id data-tpl-key="compat") - div(data-tpl=id data-tpl-key="compat-versions")   + +row(v-if="vectors" v-cloak="") + +cell + +label Vectors #[+help(MODEL_META.vectors).u-color-subtle] + +cell(v-text="vectors") - section(data-tpl=id data-tpl-key="benchmarks" hidden="") - +grid.o-block-small + +row(v-if="sources && sources.length" v-cloak="") + +cell + +label Sources #[+help(MODEL_META.sources).u-color-subtle] + +cell + span(v-for="(source, index) in sources") {{ source }} + span(v-if="index != sources.length - 1") ,  + + +row(v-if="author" v-cloak="") + +cell #[+label Author] + +cell + +a("")(v-bind:href="url" v-if="url" v-text="author") + span(v-else="" v-text="author") {{ model.author }} + + +row(v-if="license" v-cloak="") + +cell #[+label License] + +cell + +a("")(v-bind:href="modelLicenses[license]" v-if="modelLicenses[license]") {{ license }} + span(v-else="") {{ license }} + + +row(v-cloak="") + +cell #[+label Compat #[+help(MODEL_META.compat).u-color-subtle]] + +cell + .o-field.u-float-left + select.o-field__select.u-text-small(v-model="spacyVersion") + option(v-for="version in orderedCompat" v-bind:value="version") spaCy v{{ version }} + code(v-if="compatVersion" v-text="compatVersion") + em(v-else="") not compatible + + +grid.o-block-small(v-cloak="" v-if="hasAccuracy") for keys, label in MODEL_BENCHMARKS - .u-flex-full.u-padding-small(data-tpl=id data-tpl-key=label.toLowerCase() hidden="") + .u-flex-full.u-padding-small +table.o-block-small +row("head") +head-cell(colspan="2")=(MODEL_META["benchmark_" + label] || label) for label, field in keys - +row(hidden="") + +row +cell.u-nowrap +label=label if MODEL_META[field] | #[+help(MODEL_META[field]).u-color-subtle] - +cell("num")(data-tpl=id data-tpl-key=field) - | n/a + +cell("num") + span(v-if="#{field}" v-text="#{field}") + em(v-if="!#{field}") n/a + + p.u-text-small.u-color-dark(v-if="notes" v-text="notes" v-cloak="") + + if comps.size == "sm" && EXAMPLE_SENT_LANGS.includes(comps.lang) + section + +code-exec("Test the model live"). + import spacy + from spacy.lang.#{comps.lang}.examples import sentences + + nlp = spacy.load('#{id}') + doc = nlp(sentences[0]) + print(doc.text) + for token in doc: + print(token.text, token.pos_, token.dep_) - p.u-text-small.u-color-dark(data-tpl=id data-tpl-key="notes") diff --git a/website/_includes/_scripts.jade b/website/_includes/_scripts.jade index a70f880a0..b93b8cd00 100644 --- a/website/_includes/_scripts.jade +++ b/website/_includes/_scripts.jade @@ -1,86 +1,33 @@ //- 💫 INCLUDES > SCRIPTS -if quickstart - script(src="/assets/js/vendor/quickstart.min.js") +if IS_PAGE || SECTION == "index" + script(type="text/x-thebe-config") + | { bootstrap: true, binderOptions: { repo: "#{KERNEL_BINDER}"}, + | kernelOptions: { name: "#{KERNEL_PYTHON}" }} -if IS_PAGE - script(src="/assets/js/vendor/in-view.min.js") +- scripts = ["vendor/prism.min", "vendor/vue.min"] +- if (SECTION == "universe") scripts.push("vendor/vue-markdown.min") +- if (quickstart) scripts.push("vendor/quickstart.min") +- if (IS_PAGE) scripts.push("vendor/in-view.min") +- if (IS_PAGE || SECTION == "index") scripts.push("vendor/thebelab.custom.min") + +for script in scripts + script(src="/assets/js/" + script + ".js") +script(src="/assets/js/main.js?v#{V_JS}" type=(environment == "deploy") ? null : "module") if environment == "deploy" - script(async src="https://www.google-analytics.com/analytics.js") - -script(src="/assets/js/vendor/prism.min.js") - -if compare_models - script(src="/assets/js/vendor/chart.min.js") - -script - if quickstart - | new Quickstart("#qs"); - - if environment == "deploy" + script(src="https://www.google-analytics.com/analytics.js", async) + script | window.ga=window.ga||function(){ | (ga.q=ga.q||[]).push(arguments)}; ga.l=+new Date; | ga('create', '#{ANALYTICS}', 'auto'); ga('send', 'pageview'); - if IS_PAGE +if IS_PAGE + script(src="https://sidecar.gitter.im/dist/sidecar.v1.js" async defer) + script | ((window.gitter = {}).chat = {}).options = { | useStyles: false, | activationElement: '.js-gitter-button', | targetElement: '.js-gitter', | room: '!{SOCIAL.gitter}' | }; - -if IS_PAGE - script(src="https://sidecar.gitter.im/dist/sidecar.v1.js" async defer) - - -//- JS modules – slightly hacky, but necessary to dynamically instantiate the - classes with data from the Harp JSON files, while still being able to - support older browsers that can't handle JS modules. More details: - https://medium.com/dev-channel/es6-modules-in-chrome-canary-m60-ba588dfb8ab7 - -- ProgressBar = "new ProgressBar('.js-progress');" -- Accordion = "new Accordion('.js-accordion');" -- Changelog = "new Changelog('" + SOCIAL.github + "', 'spacy');" -- NavHighlighter = "new NavHighlighter('data-section', 'data-nav');" -- GitHubEmbed = "new GitHubEmbed('" + SOCIAL.github + "', 'data-gh-embed');" -- ModelLoader = "new ModelLoader('" + MODELS_REPO + "'," + JSON.stringify(CURRENT_MODELS) + "," + JSON.stringify(MODEL_LICENSES) + "," + JSON.stringify(MODEL_BENCHMARKS) + ");" -- ModelComparer = "new ModelComparer('" + MODELS_REPO + "'," + JSON.stringify(MODEL_LICENSES) + "," + JSON.stringify(MODEL_BENCHMARKS) + "," + JSON.stringify(LANGUAGES) + "," + JSON.stringify(MODEL_META) + "," + JSON.stringify(default_models || false) + ");" - -if environment == "deploy" - //- DEPLOY: use compiled rollup.js and instantiate classes directly - script(src="/assets/js/rollup.js?v#{V_JS}") - script - !=ProgressBar - if changelog - !=Changelog - if IS_PAGE - !=NavHighlighter - !=GitHubEmbed - !=Accordion - if HAS_MODELS - !=ModelLoader - if compare_models - !=ModelComparer -else - //- DEVELOPMENT: Use ES6 modules - script(type="module") - | import ProgressBar from '/assets/js/progress.js'; - !=ProgressBar - if changelog - | import Changelog from '/assets/js/changelog.js'; - !=Changelog - if IS_PAGE - | import NavHighlighter from '/assets/js/nav-highlighter.js'; - !=NavHighlighter - | import GitHubEmbed from '/assets/js/github-embed.js'; - !=GitHubEmbed - | import Accordion from '/assets/js/accordion.js'; - !=Accordion - if HAS_MODELS - | import { ModelLoader } from '/assets/js/models.js'; - !=ModelLoader - if compare_models - | import { ModelComparer } from '/assets/js/models.js'; - !=ModelComparer diff --git a/website/_includes/_svg.jade b/website/_includes/_svg.jade index 77f5fa543..b7b988db9 100644 --- a/website/_includes/_svg.jade +++ b/website/_includes/_svg.jade @@ -7,6 +7,12 @@ svg(style="position: absolute; visibility: hidden; width: 0; height: 0;" width=" symbol#svg_github(viewBox="0 0 27 32") path(d="M13.714 2.286q3.732 0 6.884 1.839t4.991 4.991 1.839 6.884q0 4.482-2.616 8.063t-6.759 4.955q-0.482 0.089-0.714-0.125t-0.232-0.536q0-0.054 0.009-1.366t0.009-2.402q0-1.732-0.929-2.536 1.018-0.107 1.83-0.321t1.679-0.696 1.446-1.188 0.946-1.875 0.366-2.688q0-2.125-1.411-3.679 0.661-1.625-0.143-3.643-0.5-0.161-1.446 0.196t-1.643 0.786l-0.679 0.429q-1.661-0.464-3.429-0.464t-3.429 0.464q-0.286-0.196-0.759-0.482t-1.491-0.688-1.518-0.241q-0.804 2.018-0.143 3.643-1.411 1.554-1.411 3.679 0 1.518 0.366 2.679t0.938 1.875 1.438 1.196 1.679 0.696 1.83 0.321q-0.696 0.643-0.875 1.839-0.375 0.179-0.804 0.268t-1.018 0.089-1.17-0.384-0.991-1.116q-0.339-0.571-0.866-0.929t-0.884-0.429l-0.357-0.054q-0.375 0-0.518 0.080t-0.089 0.205 0.161 0.25 0.232 0.214l0.125 0.089q0.393 0.179 0.777 0.679t0.563 0.911l0.179 0.411q0.232 0.679 0.786 1.098t1.196 0.536 1.241 0.125 0.991-0.063l0.411-0.071q0 0.679 0.009 1.58t0.009 0.973q0 0.321-0.232 0.536t-0.714 0.125q-4.143-1.375-6.759-4.955t-2.616-8.063q0-3.732 1.839-6.884t4.991-4.991 6.884-1.839zM5.196 21.982q0.054-0.125-0.125-0.214-0.179-0.054-0.232 0.036-0.054 0.125 0.125 0.214 0.161 0.107 0.232-0.036zM5.75 22.589q0.125-0.089-0.036-0.286-0.179-0.161-0.286-0.054-0.125 0.089 0.036 0.286 0.179 0.179 0.286 0.054zM6.286 23.393q0.161-0.125 0-0.339-0.143-0.232-0.304-0.107-0.161 0.089 0 0.321t0.304 0.125zM7.036 24.143q0.143-0.143-0.071-0.339-0.214-0.214-0.357-0.054-0.161 0.143 0.071 0.339 0.214 0.214 0.357 0.054zM8.054 24.589q0.054-0.196-0.232-0.286-0.268-0.071-0.339 0.125t0.232 0.268q0.268 0.107 0.339-0.107zM9.179 24.679q0-0.232-0.304-0.196-0.286 0-0.286 0.196 0 0.232 0.304 0.196 0.286 0 0.286-0.196zM10.214 24.5q-0.036-0.196-0.321-0.161-0.286 0.054-0.25 0.268t0.321 0.143 0.25-0.25z") + symbol#svg_twitter(viewBox="0 0 30 32") + path(d="M28.929 7.286q-1.196 1.75-2.893 2.982 0.018 0.25 0.018 0.75 0 2.321-0.679 4.634t-2.063 4.437-3.295 3.759-4.607 2.607-5.768 0.973q-4.839 0-8.857-2.589 0.625 0.071 1.393 0.071 4.018 0 7.161-2.464-1.875-0.036-3.357-1.152t-2.036-2.848q0.589 0.089 1.089 0.089 0.768 0 1.518-0.196-2-0.411-3.313-1.991t-1.313-3.67v-0.071q1.214 0.679 2.607 0.732-1.179-0.786-1.875-2.054t-0.696-2.75q0-1.571 0.786-2.911 2.161 2.661 5.259 4.259t6.634 1.777q-0.143-0.679-0.143-1.321 0-2.393 1.688-4.080t4.080-1.688q2.5 0 4.214 1.821 1.946-0.375 3.661-1.393-0.661 2.054-2.536 3.179 1.661-0.179 3.321-0.893z") + + symbol#svg_website(viewBox="0 0 32 32") + path(d="M22.658 10.988h5.172c0.693 1.541 1.107 3.229 1.178 5.012h-5.934c-0.025-1.884-0.181-3.544-0.416-5.012zM20.398 3.896c2.967 1.153 5.402 3.335 6.928 6.090h-4.836c-0.549-2.805-1.383-4.799-2.092-6.090zM16.068 9.986v-6.996c1.066 0.047 2.102 0.216 3.092 0.493 0.75 1.263 1.719 3.372 2.33 6.503h-5.422zM9.489 22.014c-0.234-1.469-0.396-3.119-0.421-5.012h5.998v5.012h-5.577zM9.479 10.988h5.587v5.012h-6.004c0.025-1.886 0.183-3.543 0.417-5.012zM11.988 3.461c0.987-0.266 2.015-0.435 3.078-0.469v6.994h-5.422c0.615-3.148 1.591-5.265 2.344-6.525zM3.661 9.986c1.551-2.8 4.062-4.993 7.096-6.131-0.715 1.29-1.559 3.295-2.114 6.131h-4.982zM8.060 16h-6.060c0.066-1.781 0.467-3.474 1.158-5.012h5.316c-0.233 1.469-0.39 3.128-0.414 5.012zM8.487 22.014h-5.29c-0.694-1.543-1.139-3.224-1.204-5.012h6.071c0.024 1.893 0.188 3.541 0.423 5.012zM8.651 23.016c0.559 2.864 1.416 4.867 2.134 6.142-3.045-1.133-5.557-3.335-7.11-6.142h4.976zM15.066 23.016v6.994c-1.052-0.033-2.067-0.199-3.045-0.46-0.755-1.236-1.736-3.363-2.356-6.534h5.401zM21.471 23.016c-0.617 3.152-1.592 5.271-2.344 6.512-0.979 0.271-2.006 0.418-3.059 0.465v-6.977h5.403zM16.068 17.002h5.998c-0.023 1.893-0.188 3.542-0.422 5.012h-5.576v-5.012zM22.072 16h-6.004v-5.012h5.586c0.235 1.469 0.393 3.126 0.418 5.012zM23.070 17.002h5.926c-0.066 1.787-0.506 3.468-1.197 5.012h-5.152c0.234-1.471 0.398-3.119 0.423-5.012zM27.318 23.016c-1.521 2.766-3.967 4.949-6.947 6.1 0.715-1.276 1.561-3.266 2.113-6.1h4.834z") + symbol#svg_code(viewBox="0 0 20 20") path(d="M5.719 14.75c-0.236 0-0.474-0.083-0.664-0.252l-5.060-4.498 5.341-4.748c0.412-0.365 1.044-0.33 1.411 0.083s0.33 1.045-0.083 1.412l-3.659 3.253 3.378 3.002c0.413 0.367 0.45 0.999 0.083 1.412-0.197 0.223-0.472 0.336-0.747 0.336zM14.664 14.748l5.341-4.748-5.060-4.498c-0.413-0.367-1.045-0.33-1.411 0.083s-0.33 1.045 0.083 1.412l3.378 3.003-3.659 3.252c-0.413 0.367-0.45 0.999-0.083 1.412 0.197 0.223 0.472 0.336 0.747 0.336 0.236 0 0.474-0.083 0.664-0.252zM9.986 16.165l2-12c0.091-0.545-0.277-1.060-0.822-1.151-0.547-0.092-1.061 0.277-1.15 0.822l-2 12c-0.091 0.545 0.277 1.060 0.822 1.151 0.056 0.009 0.11 0.013 0.165 0.013 0.48 0 0.904-0.347 0.985-0.835z") diff --git a/website/_layout.jade b/website/_layout.jade index 5ee65da77..b0a1342e5 100644 --- a/website/_layout.jade +++ b/website/_layout.jade @@ -3,23 +3,15 @@ include _includes/_mixins - title = IS_MODELS ? LANGUAGES[current.source] || title : title -- social_title = (SECTION == "index") ? SITENAME + " - " + SLOGAN : title + " - " + SITENAME -- social_img = SITE_URL + "/assets/img/social/preview_" + (preview || ALPHA ? "alpha" : "default") + ".jpg" + +- PAGE_URL = getPageUrl() +- PAGE_TITLE = getPageTitle() +- PAGE_IMAGE = getPageImage() doctype html html(lang="en") head - title - if SECTION == "api" || SECTION == "usage" || SECTION == "models" - - var title_section = (SECTION == "api") ? "API" : SECTION.charAt(0).toUpperCase() + SECTION.slice(1) - | #{title} | #{SITENAME} #{title_section} Documentation - - else if SECTION != "index" - | #{title} | #{SITENAME} - - else - | #{SITENAME} - #{SLOGAN} - + title=PAGE_TITLE meta(charset="utf-8") meta(name="viewport" content="width=device-width, initial-scale=1.0") meta(name="referrer" content="always") @@ -27,23 +19,24 @@ html(lang="en") meta(property="og:type" content="website") meta(property="og:site_name" content=sitename) - meta(property="og:url" content="#{SITE_URL}/#{current.path.join('/')}") - meta(property="og:title" content=social_title) + meta(property="og:url" content=PAGE_URL) + meta(property="og:title" content=PAGE_TITLE) meta(property="og:description" content=description) - meta(property="og:image" content=social_img) + meta(property="og:image" content=PAGE_IMAGE) meta(name="twitter:card" content="summary_large_image") meta(name="twitter:site" content="@" + SOCIAL.twitter) - meta(name="twitter:title" content=social_title) + meta(name="twitter:title" content=PAGE_TITLE) meta(name="twitter:description" content=description) - meta(name="twitter:image" content=social_img) + meta(name="twitter:image" content=PAGE_IMAGE) link(rel="shortcut icon" href="/assets/img/favicon.ico") link(rel="icon" type="image/x-icon" href="/assets/img/favicon.ico") if SECTION == "api" link(href="/assets/css/style_green.css?v#{V_CSS}" rel="stylesheet") - + else if SECTION == "universe" + link(href="/assets/css/style_purple.css?v#{V_CSS}" rel="stylesheet") else link(href="/assets/css/style.css?v#{V_CSS}" rel="stylesheet") @@ -54,6 +47,9 @@ html(lang="en") if !landing include _includes/_page-docs + else if SECTION == "universe" + !=yield + else main!=yield include _includes/_footer diff --git a/website/api/_architecture/_nn-model.jade b/website/api/_architecture/_nn-model.jade index 8080af2ec..b7c32eae6 100644 --- a/website/api/_architecture/_nn-model.jade +++ b/website/api/_architecture/_nn-model.jade @@ -1,5 +1,13 @@ //- 💫 DOCS > API > ARCHITECTURE > NN MODEL ARCHITECTURE +p + | spaCy's statistical models have been custom-designed to give a + | high-performance mix of speed and accuracy. The current architecture + | hasn't been published yet, but in the meantime we prepared a video that + | explains how the models work, with particular focus on NER. + ++youtube("sqDHBH9IjRU") + p | The parsing model is a blend of recent results. The two recent | inspirations have been the work of Eli Klipperwasser and Yoav Goldberg at @@ -44,7 +52,7 @@ p +cell First two words of the buffer. +row - +cell.u-nowrap + +cell | #[code S0L1], #[code S1L1], #[code S2L1], #[code B0L1], | #[code B1L1]#[br] | #[code S0L2], #[code S1L2], #[code S2L2], #[code B0L2], @@ -54,7 +62,7 @@ p | #[code S2], #[code B0] and #[code B1]. +row - +cell.u-nowrap + +cell | #[code S0R1], #[code S1R1], #[code S2R1], #[code B0R1], | #[code B1R1]#[br] | #[code S0R2], #[code S1R2], #[code S2R2], #[code B0R2], diff --git a/website/assets/css/_base/_objects.sass b/website/assets/css/_base/_objects.sass index b8a20f5dd..f54667c3f 100644 --- a/website/assets/css/_base/_objects.sass +++ b/website/assets/css/_base/_objects.sass @@ -60,6 +60,13 @@ padding-bottom: 4rem border-bottom: 1px dotted $color-subtle + &.o-section--small + overflow: auto + + &:not(:last-child) + margin-bottom: 3.5rem + padding-bottom: 2rem + .o-block margin-bottom: 4rem @@ -142,6 +149,14 @@ .o-badge border-radius: 1em +.o-thumb + @include size(100px) + overflow: hidden + border-radius: 50% + + &.o-thumb--small + @include size(35px) + //- SVG diff --git a/website/assets/css/_base/_utilities.sass b/website/assets/css/_base/_utilities.sass index 9b1c0cedc..10fe843a4 100644 --- a/website/assets/css/_base/_utilities.sass +++ b/website/assets/css/_base/_utilities.sass @@ -103,6 +103,9 @@ &:hover color: $color-theme-dark +.u-hand + cursor: pointer + .u-hide-link.u-hide-link border: none color: inherit @@ -224,6 +227,7 @@ $spinner-size: 75px $spinner-bar: 8px + min-height: $spinner-size * 2 position: relative & > * @@ -245,10 +249,19 @@ //- Hidden elements -.u-hidden - display: none +.u-hidden, +[v-cloak] + display: none !important @each $breakpoint in (xs, sm, md) .u-hidden-#{$breakpoint}.u-hidden-#{$breakpoint} @include breakpoint(max, $breakpoint) display: none + +//- Transitions + +.u-fade-enter-active + transition: opacity 0.5s + +.u-fade-enter + opacity: 0 diff --git a/website/assets/css/_components/_code.sass b/website/assets/css/_components/_code.sass index 0fec230c0..0d0161ca6 100644 --- a/website/assets/css/_components/_code.sass +++ b/website/assets/css/_components/_code.sass @@ -2,7 +2,8 @@ //- Code block -.c-code-block +.c-code-block, +.thebelab-cell background: $color-front color: darken($color-back, 20) padding: 0.75em 0 @@ -13,11 +14,11 @@ white-space: pre direction: ltr - &.c-code-block--has-icon - padding: 0 - display: flex - border-top-left-radius: 0 - border-bottom-left-radius: 0 +.c-code-block--has-icon + padding: 0 + display: flex + border-top-left-radius: 0 + border-bottom-left-radius: 0 .c-code-block__icon padding: 0 0 0 1rem @@ -28,26 +29,66 @@ &.c-code-block__icon--border border-left: 6px solid - - //- Code block content -.c-code-block__content +.c-code-block__content, +.thebelab-input, +.jp-OutputArea display: block font: normal normal 1.1rem/#{1.9} $font-code padding: 1em 2em - &[data-prompt]:before, - content: attr(data-prompt) - margin-right: 0.65em - display: inline-block - vertical-align: middle - opacity: 0.5 +.c-code-block__content[data-prompt]:before, + content: attr(data-prompt) + margin-right: 0.65em + display: inline-block + vertical-align: middle + opacity: 0.5 +//- Thebelab + +[data-executable] + margin-bottom: 0 + +.thebelab-input.thebelab-input + padding: 3em 2em 1em + +.jp-OutputArea + &:not(:empty) + padding: 2rem 2rem 1rem + border-top: 1px solid $color-dark + margin-top: 2rem + + .entities, svg + white-space: initial + font-family: inherit + + .entities + font-size: 1.35rem + +.jp-OutputArea pre + font: inherit + +.jp-OutputPrompt.jp-OutputArea-prompt + padding-top: 0.5em + margin-right: 1rem + font-family: inherit + font-weight: bold + +.thebelab-run-button + @extend .u-text-label, .u-text-label--dark + +.thebelab-wrapper + position: relative + +.thebelab-wrapper__text + @include position(absolute, top, right, 1.25rem, 1.25rem) + color: $color-subtle-dark + z-index: 10 //- Code -code +code, .CodeMirror, .jp-RenderedText, .jp-OutputArea -webkit-font-smoothing: subpixel-antialiased -moz-osx-font-smoothing: auto @@ -73,7 +114,7 @@ code text-shadow: none -//- Syntax Highlighting +//- Syntax Highlighting (Prism) [class*="language-"] .token &.comment, &.prolog, &.doctype, &.cdata, &.punctuation @@ -103,3 +144,50 @@ code &.italic font-style: italic + +//- Syntax Highlighting (CodeMirror) + +.CodeMirror.cm-s-default + background: $color-front + color: darken($color-back, 20) + + .CodeMirror-selected + background: $color-theme + color: $color-back + + .CodeMirror-cursor + border-left-color: currentColor + + .cm-variable-2 + color: inherit + font-style: italic + + .cm-comment + color: map-get($syntax-highlighting, comment) + + .cm-keyword, .cm-builtin + color: map-get($syntax-highlighting, keyword) + + .cm-operator + color: map-get($syntax-highlighting, operator) + + .cm-string + color: map-get($syntax-highlighting, selector) + + .cm-number + color: map-get($syntax-highlighting, number) + + .cm-def + color: map-get($syntax-highlighting, function) + +//- Syntax highlighting (Jupyter) + +.jp-RenderedText pre + .ansi-cyan-fg + color: map-get($syntax-highlighting, function) + + .ansi-green-fg + color: $color-green + + .ansi-red-fg + color: map-get($syntax-highlighting, operator) diff --git a/website/assets/css/_components/_misc.sass b/website/assets/css/_components/_misc.sass index c09fdf79a..0abbb6ae9 100644 --- a/website/assets/css/_components/_misc.sass +++ b/website/assets/css/_components/_misc.sass @@ -8,6 +8,12 @@ width: 100% position: relative + &.x-terminal--small + background: $color-dark + color: $color-subtle + border-radius: 4px + margin-bottom: 4rem + .x-terminal__icons position: absolute padding: 10px @@ -32,6 +38,13 @@ content: "" background: $color-yellow + &.x-terminal__icons--small + &:before, + &:after, + span + @include size(10px) + + .x-terminal__code margin: 0 border: none diff --git a/website/assets/css/_components/_navigation.sass b/website/assets/css/_components/_navigation.sass index 2f1cfb6e3..eaeb33957 100644 --- a/website/assets/css/_components/_navigation.sass +++ b/website/assets/css/_components/_navigation.sass @@ -9,7 +9,7 @@ display: flex justify-content: space-between flex-flow: row nowrap - padding: 0 2rem 0 1rem + padding: 0 0 0 1rem z-index: 30 width: 100% box-shadow: $box-shadow @@ -21,11 +21,20 @@ .c-nav__menu @include size(100%) display: flex - justify-content: flex-end flex-flow: row nowrap border-color: inherit flex: 1 + + @include breakpoint(max, sm) + @include scroll-shadow-base($color-front) + overflow-x: auto + overflow-y: hidden + -webkit-overflow-scrolling: touch + + @include breakpoint(min, md) + justify-content: flex-end + .c-nav__menu__item display: flex align-items: center @@ -39,6 +48,14 @@ &:not(:first-child) margin-left: 2em + &:last-child + @include scroll-shadow-cover(right, $color-back) + padding-right: 2rem + + &:first-child + @include scroll-shadow-cover(left, $color-back) + padding-left: 2rem + &.is-active color: $color-dark pointer-events: none diff --git a/website/assets/css/_variables.sass b/website/assets/css/_variables.sass index fbceb5a6f..bac6c4f69 100644 --- a/website/assets/css/_variables.sass +++ b/website/assets/css/_variables.sass @@ -26,7 +26,7 @@ $font-code: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace // Colors -$colors: ( blue: #09a3d5, green: #05b083 ) +$colors: ( blue: #09a3d5, green: #05b083, purple: #6542d1 ) $color-back: #fff !default $color-front: #1a1e23 !default diff --git a/website/assets/css/style_purple.sass b/website/assets/css/style_purple.sass new file mode 100644 index 000000000..f00573cc7 --- /dev/null +++ b/website/assets/css/style_purple.sass @@ -0,0 +1,4 @@ +//- 💫 STYLESHEET (PURPLE) + +$theme: purple +@import style diff --git a/website/assets/img/pattern_purple.jpg b/website/assets/img/pattern_purple.jpg new file mode 100644 index 000000000..1bc5686de Binary files /dev/null and b/website/assets/img/pattern_purple.jpg differ diff --git a/website/assets/img/social/preview_api.jpg b/website/assets/img/social/preview_api.jpg new file mode 100644 index 000000000..d707abd1b Binary files /dev/null and b/website/assets/img/social/preview_api.jpg differ diff --git a/website/assets/img/social/preview_universe.jpg b/website/assets/img/social/preview_universe.jpg new file mode 100644 index 000000000..960b8b29a Binary files /dev/null and b/website/assets/img/social/preview_universe.jpg differ diff --git a/website/assets/js/accordion.js b/website/assets/js/accordion.js deleted file mode 100644 index 7293f95b8..000000000 --- a/website/assets/js/accordion.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict'; - -import { $$ } from './util.js'; - -export default class Accordion { - /** - * Simple, collapsible accordion sections. - * Inspired by: https://inclusive-components.design/collapsible-sections/ - * @param {string} selector - Query selector of button element. - */ - constructor(selector) { - [...$$(selector)].forEach(btn => - btn.addEventListener('click', this.onClick.bind(this))) - } - - /** - * Toggle aria-expanded attribute on button and visibility of section. - * @param {node} Event.target - The accordion button. - */ - onClick({ target }) { - const exp = target.getAttribute('aria-expanded') === 'true' || false; - target.setAttribute('aria-expanded', !exp); - target.parentElement.nextElementSibling.hidden = exp; - } -} diff --git a/website/assets/js/changelog.js b/website/assets/js/changelog.js deleted file mode 100644 index 94f2149ad..000000000 --- a/website/assets/js/changelog.js +++ /dev/null @@ -1,72 +0,0 @@ -'use strict'; - -import { Templater, handleResponse } from './util.js'; - -export default class Changelog { - /** - * Fetch and render changelog from GitHub. Clones a template node (table row) - * to avoid doubling templating markup in JavaScript. - * @param {string} user - GitHub username. - * @param {string} repo - Repository to fetch releases from. - */ - constructor(user, repo) { - this.url = `https://api.github.com/repos/${user}/${repo}/releases`; - this.template = new Templater('changelog'); - this.fetchChangelog() - .then(json => this.render(json)) - .catch(this.showError.bind(this)); - // make sure scroll positions for progress bar etc. are recalculated - window.dispatchEvent(new Event('resize')); - } - - fetchChangelog() { - return new Promise((resolve, reject) => - fetch(this.url) - .then(res => handleResponse(res)) - .then(json => json.ok ? resolve(json) : reject())) - } - - showError() { - this.template.get('error').style.display = 'block'; - } - - /** - * Get template section from template row. Hacky, but does make sense. - * @param {node} item - Parent element. - * @param {string} id - ID of child element, set via data-changelog. - */ - getField(item, id) { - return item.querySelector(`[data-changelog="${id}"]`); - } - - render(json) { - this.template.get('table').style.display = 'block'; - this.row = this.template.get('item'); - this.releases = this.template.get('releases'); - this.prereleases = this.template.get('prereleases'); - Object.values(json) - .filter(release => release.name) - .forEach(release => this.renderRelease(release)); - this.row.remove(); - } - - /** - * Clone the template row and populate with content from API response. - * https://developer.github.com/v3/repos/releases/#list-releases-for-a-repository - * @param {string} name - Release title. - * @param {string} tag (tag_name) - Release tag. - * @param {string} url (html_url) - URL to the release page on GitHub. - * @param {string} date (published_at) - Timestamp of release publication. - * @param {boolean} prerelease - Whether the release is a prerelease. - */ - renderRelease({ name, tag_name: tag, html_url: url, published_at: date, prerelease }) { - const container = prerelease ? this.prereleases : this.releases; - const tagLink = `${tag}`; - const title = (name.split(': ').length == 2) ? name.split(': ')[1] : name; - const row = this.row.cloneNode(true); - this.getField(row, 'date').textContent = date.split('T')[0]; - this.getField(row, 'tag').innerHTML = tagLink; - this.getField(row, 'title').textContent = title; - container.appendChild(row); - } -} diff --git a/website/assets/js/changelog.vue.js b/website/assets/js/changelog.vue.js new file mode 100644 index 000000000..a68042d3e --- /dev/null +++ b/website/assets/js/changelog.vue.js @@ -0,0 +1,40 @@ +/** + * Initialise changelog table for releases and prereleases + * @param {string} selector - The element selector to initialise the app. + * @param {string} repo - Repository to load from, in the format user/repo. + */ +export default function(selector, repo) { + new Vue({ + el: selector, + data: { + url: `https://api.github.com/repos/${repo}/releases`, + releases: [], + prereleases: [], + error: false + }, + beforeMount() { + fetch(this.url) + .then(res => res.json()) + .then(json => this.$_update(json)) + .catch(err => { this.error = true }); + }, + updated() { + window.dispatchEvent(new Event('resize')); // scroll position for progress + }, + methods: { + $_update(json) { + const allReleases = Object.values(json) + .filter(release => release.name) + .map(release => ({ + title: (release.name.split(': ').length == 2) ? release.name.split(': ')[1] : release.name, + url: release.html_url, + date: release.published_at.split('T')[0], + tag: release.tag_name, + pre: release.prerelease + })); + this.releases = allReleases.filter(release => !release.pre); + this.prereleases = allReleases.filter(release => release.pre); + } + } + }); +} diff --git a/website/assets/js/github-embed.js b/website/assets/js/github-embed.js deleted file mode 100644 index ec72fd713..000000000 --- a/website/assets/js/github-embed.js +++ /dev/null @@ -1,42 +0,0 @@ -'use strict'; - -import { $$ } from './util.js'; - -export default class GitHubEmbed { - /** - * Embed code from GitHub repositories, similar to Gist embeds. Fetches the - * raw text and places it inside element. - * Usage:
-     * @param {string} user - GitHub user or organization.
-     * @param {string} attr - Data attribute used to select containers. Attribute
-     *                        value should be path to file relative to user.
-     */
-    constructor(user, attr) {
-        this.url = `https://raw.githubusercontent.com/${user}`;
-        this.attr = attr;
-        [...$$(`[${this.attr}]`)].forEach(el => this.embed(el));
-    }
-
-    /**
-     * Fetch code from GitHub and insert it as element content. File path is
-     * read off the container's data attribute.
-     * @param {node} el - The element.
-     */
-    embed(el) {
-        el.parentElement.setAttribute('data-loading', '');
-        fetch(`${this.url}/${el.getAttribute(this.attr)}`)
-            .then(res => res.text().then(text => ({ text, ok: res.ok })))
-            .then(({ text, ok }) => ok ? this.render(el, text) : false)
-        el.parentElement.removeAttribute('data-loading');
-    }
-
-    /**
-     * Add text to container and apply syntax highlighting via Prism, if available.
-     * @param {node} el - The element.
-     * @param {string} text - The raw code, fetched from GitHub.
-     */
-    render(el, text) {
-        el.textContent = text;
-        if (window.Prism) Prism.highlightElement(el);
-    }
-}
diff --git a/website/assets/js/main.js b/website/assets/js/main.js
new file mode 100644
index 000000000..f52dbb9ce
--- /dev/null
+++ b/website/assets/js/main.js
@@ -0,0 +1,148 @@
+/**
+ * Initialise changelog
+ */
+import initChangelog from './changelog.vue.js';
+
+{
+    const selector = '[data-vue="changelog"]';
+    if (window.Vue && document.querySelector(selector)) {
+        initChangelog(selector, 'explosion/spacy');
+    }
+}
+
+/**
+ * Initialise models
+ */
+import initModels from './models.vue.js';
+
+{
+    if (window.Vue && document.querySelector('[data-model]')) {
+        initModels('explosion/spacy-models')
+    }
+}
+
+/**
+ * Initialise Universe
+ */
+import initUniverse from './universe.vue.js';
+
+{
+    const selector = '[data-vue="universe"]';
+    if (window.Vue && document.querySelector(selector)) {
+        initUniverse(selector, '/universe/universe.json');
+    }
+}
+
+/**
+ * Initialise Quickstart
+ */
+if (document.querySelector('#qs') && window.Quickstart) {
+    new Quickstart('#qs');
+}
+
+/**
+ * Thebelabs
+ */
+if (window.thebelab) {
+    window.thebelab.on('status', (ev, data) => {
+        if (data.status == 'failed') {
+            const msg = "Failed to connect to kernel :( This can happen if too many users are active at the same time. Please reload the page and try again!";
+            const wrapper = `${msg}`;
+            document.querySelector('.jp-OutputArea-output pre').innerHTML = wrapper;
+        }
+    });
+}
+
+/**
+ * Highlight section in viewport in sidebar, using in-view library
+ */
+{
+    const sectionAttr = 'data-section';
+    const navAttr = 'data-nav';
+    const activeClass = 'is-active';
+    const sections = [...document.querySelectorAll(`[${navAttr}]`)];
+    if (window.inView) {
+        if (sections.length) {  // highlight first item regardless
+            sections[0].classList.add(activeClass);
+        }
+        inView(`[${sectionAttr}]`).on('enter', section => {
+            const id = section.getAttribute(sectionAttr);
+            const el = document.querySelector(`[${navAttr}="${id}"]`);
+            if (el) {
+                sections.forEach(el => el.classList.remove(activeClass));
+                el.classList.add(activeClass);
+            }
+        });
+    }
+}
+
+/**
+ * Simple, collapsible accordion sections.
+ * Inspired by: https://inclusive-components.design/collapsible-sections/
+ */
+{
+    const elements = [...document.querySelectorAll('.js-accordion')];
+    elements.forEach(el => el.addEventListener('click', ({ target }) => {
+        const exp = target.getAttribute('aria-expanded') === 'true' || false;
+        target.setAttribute('aria-expanded', !exp);
+        target.parentElement.nextElementSibling.hidden = exp;
+    }));
+}
+
+/**
+ * Reading indicator as progress bar
+ * @param {string} selector - Selector of  element.
+ */
+class ProgressBar {
+    constructor(selector) {
+        this.scrollY = 0;
+        this.sizes = this.updateSizes();
+        this.el = document.querySelector(selector);
+        this.el.setAttribute('max', 100);
+        window.addEventListener('scroll', ev => {
+            this.scrollY = (window.pageYOffset || document.scrollTop) - (document.clientTop || 0);
+            requestAnimationFrame(this.update.bind(this));
+        });
+        window.addEventListener('resize', ev => {
+            this.sizes = this.updateSizes();
+            requestAnimationFrame(this.update.bind(this));
+        });
+    }
+
+    update() {
+        const offset = 100 - ((this.sizes.height - this.scrollY - this.sizes.vh) / this.sizes.height * 100);
+        this.el.setAttribute('value', (this.scrollY == 0) ? 0 : offset || 0);
+    }
+
+    updateSizes() {
+        return {
+            height: Math.max(document.body.scrollHeight, document.body.offsetHeight, document.documentElement.clientHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight),
+            vh: Math.max(document.documentElement.clientHeight, window.innerHeight || 0)
+        }
+    }
+}
+
+new ProgressBar('.js-progress');
+
+/**
+ * Embed code from GitHub repositories, similar to Gist embeds. Fetches the
+ * raw text and places it inside element.
+ * Usage: 
+ */
+{
+    const attr = 'data-gh-embed';
+    const url = 'https://raw.githubusercontent.com/explosion';
+    const elements = [...document.querySelectorAll(`[${attr}]`)];
+    elements.forEach(el => {
+        el.parentElement.setAttribute('data-loading', '');
+        fetch(`${url}/${el.getAttribute(attr)}`)
+            .then(res => res.text().then(text => ({ text, ok: res.ok })))
+            .then(({ text, ok }) => {
+                if (ok) {
+                    el.textContent = text;
+                    if (window.Prism) Prism.highlightElement(el);
+                }
+                el.parentElement.removeAttribute('data-loading');
+            })
+    });
+}
diff --git a/website/assets/js/models.js b/website/assets/js/models.js
deleted file mode 100644
index 2f1771350..000000000
--- a/website/assets/js/models.js
+++ /dev/null
@@ -1,332 +0,0 @@
-'use strict';
-
-import { Templater, handleResponse, convertNumber, abbrNumber } from './util.js';
-
-/**
- * Chart.js defaults
- */
-const CHART_COLORS = { model1: '#09a3d5', model2: '#066B8C' };
-const CHART_FONTS = {
-    legend: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"',
-    ticks: 'Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace'
-};
-
-/**
- * Formatters for model details.
- * @property {function} author – Format model author with optional link.
- * @property {function} license - Format model license with optional link.
- * @property {function} sources - Format training data sources (list or string).
- * @property {function} pipeline - Format list of pipeline components.
- * @property {function} vectors - Format vector data (entries and dimensions).
- * @property {function} version - Format model version number.
- */
-const formats = {
-    author: (author, url) => url ? `${author}` : author,
-    license: (license, url) => url ? `${license}` : license,
-    sources: sources => (sources instanceof Array) ? sources.join(', ') : sources,
-    pipeline: pipes => (pipes && pipes.length) ? pipes.map(p => `${p}`).join(', ') : '-',
-    vectors: vec => formatVectors(vec),
-    version: version => `v${version}`
-};
-
-/**
- * Format word vectors data depending on contents.
- * @property {Object} data - The vectors object from the model's meta.json.
- */
-const formatVectors = data => {
-    if (!data) return 'n/a';
-    if (Object.values(data).every(n => n == 0)) return 'context vectors only';
-    const { keys, vectors: vecs, width } = data;
-    return `${abbrNumber(keys)} keys, ${abbrNumber(vecs)} unique vectors (${width} dimensions)`;
-}
-
-
-/**
- * Find the latest version of a model in a compatibility table.
- * @param {string} model - The model name.
- * @param {Object} compat - Compatibility table, keyed by spaCy version.
- */
-const getLatestVersion = (model, compat = {}) => {
-    for (let [spacy_v, models] of Object.entries(compat)) {
-        if (models[model]) return models[model][0];
-    }
-};
-
-export class ModelLoader {
-    /**
-     * Load model meta from GitHub and update model details on site. Uses the
-     * Templater mini template engine to update DOM.
-     * @param {string} repo - Path tp GitHub repository containing releases.
-     * @param {Array} models - List of model IDs, e.g. "en_core_web_sm".
-     * @param {Object} licenses - License IDs mapped to URLs.
-     * @param {Object} benchmarkKeys - Objects of available keys by type, e.g.
-     *                                 'parser', 'ner', 'speed', mapped to labels.
-     */
-    constructor(repo, models = [], licenses = {}, benchmarkKeys = {}) {
-        this.url = `https://raw.githubusercontent.com/${repo}/master`;
-        this.repo = `https://github.com/${repo}`;
-        this.modelIds = models;
-        this.licenses = licenses;
-        this.benchKeys = benchmarkKeys;
-        this.init();
-    }
-
-    init() {
-        this.modelIds.forEach(modelId =>
-            new Templater(modelId).get('table').setAttribute('data-loading', ''));
-        this.fetch(`${this.url}/compatibility.json`)
-            .then(json => this.getModels(json.spacy))
-            .catch(_ => this.modelIds.forEach(modelId => this.showError(modelId)));
-        // make sure scroll positions for progress bar etc. are recalculated
-        window.dispatchEvent(new Event('resize'));
-    }
-
-    fetch(url) {
-        return new Promise((resolve, reject) =>
-            fetch(url).then(res => handleResponse(res))
-                .then(json => json.ok ? resolve(json) : reject()))
-    }
-
-    getModels(compat) {
-        this.compat = compat;
-        for (let modelId of this.modelIds) {
-            const version = getLatestVersion(modelId, compat);
-            if (version) this.fetch(`${this.url}/meta/${modelId}-${version}.json`)
-                .then(json => this.render(json))
-                .catch(_ => this.showError(modelId))
-            else this.showError(modelId);
-        }
-    }
-
-    showError(modelId) {
-        const tpl = new Templater(modelId);
-        tpl.get('table').removeAttribute('data-loading');
-        tpl.get('error').hidden = false;
-        for (let key of ['sources', 'pipeline', 'vecs', 'author', 'license']) {
-            tpl.get(key).parentElement.parentElement.hidden = true;
-        }
-    }
-
-    /**
-     * Update model details in tables. Currently quite hacky :(
-     */
-    render(data) {
-        const modelId = `${data.lang}_${data.name}`;
-        const model = `${modelId}-${data.version}`;
-        const tpl = new Templater(modelId);
-        this.renderDetails(tpl, data)
-        this.renderBenchmarks(tpl, data.accuracy, data.speed);
-        this.renderCompat(tpl, modelId);
-        tpl.get('download').setAttribute('href', `${this.repo}/releases/tag/${model}`);
-        tpl.get('table').removeAttribute('data-loading');
-        tpl.get('error').hidden = true;
-    }
-
-    renderDetails(tpl, { version, size, description, notes, author, url,
-        license, sources, vectors, pipeline }) {
-        const basics = { version, size, description, notes }
-        for (let [key, value] of Object.entries(basics)) {
-            if (value) tpl.fill(key, value);
-        }
-        if (author) tpl.fill('author', formats.author(author, url), true);
-        if (license) tpl.fill('license', formats.license(license, this.licenses[license]), true);
-        if (sources) tpl.fill('sources', formats.sources(sources));
-        if (vectors) tpl.fill('vecs', formats.vectors(vectors));
-        else tpl.get('vecs').parentElement.parentElement.hidden = true;
-        if (pipeline && pipeline.length) tpl.fill('pipeline', formats.pipeline(pipeline), true);
-        else tpl.get('pipeline').parentElement.parentElement.hidden = true;
-    }
-
-    renderBenchmarks(tpl, accuracy = {}, speed = {}) {
-        if (!accuracy && !speed) return;
-        this.renderTable(tpl, 'parser', accuracy, val => val.toFixed(2));
-        this.renderTable(tpl, 'ner', accuracy, val => val.toFixed(2));
-        tpl.get('benchmarks').hidden = false;
-    }
-
-    renderTable(tpl, id, benchmarks, converter = val => val) {
-        if (!this.benchKeys[id] || !Object.keys(this.benchKeys[id]).some(key => benchmarks[key])) return;
-        for (let key of Object.keys(this.benchKeys[id])) {
-            if (benchmarks[key]) tpl
-                .fill(key, convertNumber(converter(benchmarks[key])))
-                .parentElement.hidden = false;
-        }
-        tpl.get(id).hidden = false;
-    }
-
-    renderCompat(tpl, modelId) {
-        tpl.get('compat-wrapper').hidden = false;
-        const header = '';
-        const options = Object.keys(this.compat)
-            .map(v => ``)
-            .join('');
-        tpl
-            .fill('compat', header + options, true)
-            .addEventListener('change', ({ target: { value }}) =>
-                tpl.fill('compat-versions', this.getCompat(value, modelId), true))
-    }
-
-    getCompat(version, model) {
-        const res = this.compat[version][model];
-        return res ? `${model}-${res[0]}` : 'not compatible';
-    }
-}
-
-export class ModelComparer {
-    /**
-     * Compare to model meta files and render chart and comparison table.
-     * @param {string} repo - Path tp GitHub repository containing releases.
-     * @param {Object} licenses - License IDs mapped to URLs.
-     * @param {Object} benchmarkKeys - Objects of available keys by type, e.g.
-     *                                 'parser', 'ner', 'speed', mapped to labels.
-     * @param {Object} languages - Available languages, ID mapped to name.
-     * @param {Object} defaultModels - Models to compare on load, 'model1' and
-     *                                 'model2' mapped to model names.
-     */
-    constructor(repo, licenses = {}, benchmarkKeys = {}, languages = {}, labels = {}, defaultModels) {
-        this.url = `https://raw.githubusercontent.com/${repo}/master`;
-        this.repo = `https://github.com/${repo}`;
-        this.tpl = new Templater('compare');
-        this.benchKeys = benchmarkKeys;
-        this.licenses = licenses;
-        this.languages = languages;
-        this.labels = labels;
-        this.models = {};
-        this.colors = CHART_COLORS;
-        this.fonts = CHART_FONTS;
-        this.defaultModels = defaultModels;
-        this.tpl.get('result').hidden = false;
-        this.tpl.get('error').hidden = true;
-        this.fetchCompat()
-            .then(compat => this.init(compat))
-            .catch(this.showError.bind(this))
-    }
-
-    init(compat) {
-        this.compat = compat;
-        const selectA = this.tpl.get('model1');
-        const selectB = this.tpl.get('model2');
-        selectA.addEventListener('change', this.onSelect.bind(this));
-        selectB.addEventListener('change', this.onSelect.bind(this));
-        this.chart = new Chart('chart_compare_accuracy', { type: 'bar', options: {
-            responsive: true,
-            legend: { position: 'bottom', labels: { fontFamily: this.fonts.legend, fontSize: 13 }},
-            scales: {
-                yAxes: [{ label: 'Accuracy', ticks: { min: 70, fontFamily: this.fonts.ticks }}],
-                xAxes: [{ barPercentage: 0.75, ticks: { fontFamily: this.fonts.ticks }}]
-            }
-        }});
-        if (this.defaultModels) {
-            selectA.value = this.defaultModels.model1;
-            selectB.value = this.defaultModels.model2;
-            this.getModels(this.defaultModels);
-        }
-    }
-
-    fetchCompat() {
-        return new Promise((resolve, reject) =>
-            fetch(`${this.url}/compatibility.json`)
-                .then(res => handleResponse(res))
-                .then(json => json.ok ? resolve(json.spacy) : reject()))
-    }
-
-    fetchModel(name) {
-        const version = getLatestVersion(name, this.compat);
-        const modelName = `${name}-${version}`;
-        return new Promise((resolve, reject) => {
-            if (!version) reject();
-            // resolve immediately if model already loaded, e.g. in this.models
-            else if (this.models[name]) resolve(this.models[name]);
-            else fetch(`${this.url}/meta/${modelName}.json`)
-                .then(res => handleResponse(res))
-                .then(json => json.ok ? resolve(this.saveModel(name, json)) : reject())
-        })
-    }
-
-    /**
-     * "Save" meta to this.models so it only has to be fetched from GitHub once.
-     * @param {string} name - The model name.
-     * @param {Object} data - The model meta data.
-     */
-    saveModel(name, data) {
-        this.models[name] = data;
-        return data;
-    }
-
-    showError(err) {
-        console.error(err || 'Error');
-        this.tpl.get('result').hidden = true;
-        this.tpl.get('error').hidden = false;
-    }
-
-    onSelect(ev) {
-        const modelId = ev.target.value;
-        const otherId = (ev.target.id == 'model1') ? 'model2' : 'model1';
-        const otherVal = this.tpl.get(otherId);
-        const otherModel = otherVal.options[otherVal.selectedIndex].value;
-        if (otherModel != '') this.getModels({
-            [ev.target.id]: modelId,
-            [otherId]: otherModel
-        })
-    }
-
-    getModels({ model1, model2 }) {
-        this.tpl.get('result').setAttribute('data-loading', '');
-        this.fetchModel(model1)
-            .then(data1 => this.fetchModel(model2)
-                .then(data2 => this.render({ model1: data1, model2: data2 })))
-                .catch(this.showError.bind(this))
-    }
-
-    /**
-     * Render two models, and populate the chart and table. Currently quite hacky :(
-     * @param {Object} models - The models to render.
-     * @param {Object} models.model1 - The first model (via first ).
-     */
-    render({ model1, model2 }) {
-        const accKeys = Object.assign({}, this.benchKeys.parser, this.benchKeys.ner);
-        const allKeys = [...Object.keys(model1.accuracy || []), ...Object.keys(model2.accuracy || [])];
-        const metaKeys = Object.keys(accKeys).filter(k => allKeys.includes(k));
-        const labels = metaKeys.map(key => accKeys[key]);
-        const datasets = [model1, model2]
-            .map(({ lang, name, version, accuracy = {} }, i) => ({
-                label: `${lang}_${name}-${version}`,
-                backgroundColor: this.colors[`model${i + 1}`],
-                data: metaKeys.map(key => (accuracy[key] || 0).toFixed(2))
-            }));
-        this.chart.data = { labels, datasets };
-        this.chart.update();
-        [model1, model2].forEach((model, i) => this.renderTable(metaKeys, i + 1, model));
-        this.tpl.get('result').removeAttribute('data-loading');
-        this.tpl.get('error').hidden = true;
-        this.tpl.get('result').hidden = false;
-    }
-
-    renderTable(metaKeys, i, { lang, name, version, size, description,
-        notes, author, url, license, sources, vectors, pipeline, accuracy = {},
-        speed = {}}) {
-        const type = name.split('_')[0];  // extract type from model name
-        const genre = name.split('_')[1];  // extract genre from model name
-        this.tpl.fill(`table-head${i}`, `${lang}_${name}`);
-        this.tpl.get(`link${i}`).setAttribute('href', `/models/${lang}#${lang}_${name}`);
-        this.tpl.fill(`download${i}`, `python -m spacy download ${lang}_${name}\n`);
-        this.tpl.fill(`lang${i}`, this.languages[lang] || lang);
-        this.tpl.fill(`type${i}`, this.labels[type] || type);
-        this.tpl.fill(`genre${i}`, this.labels[genre] || genre);
-        this.tpl.fill(`version${i}`, formats.version(version), true);
-        this.tpl.fill(`size${i}`, size);
-        this.tpl.fill(`desc${i}`, description || 'n/a');
-        this.tpl.fill(`pipeline${i}`, formats.pipeline(pipeline), true);
-        this.tpl.fill(`vecs${i}`, formats.vectors(vectors));
-        this.tpl.fill(`sources${i}`, formats.sources(sources));
-        this.tpl.fill(`author${i}`, formats.author(author, url), true);
-        this.tpl.fill(`license${i}`, formats.license(license, this.licenses[license]), true);
-        // check if model accuracy or speed includes one of the pre-set keys
-        const allKeys = [].concat(...Object.entries(this.benchKeys).map(([_, v]) => Object.keys(v)));
-        for (let key of allKeys) {
-            if (accuracy[key]) this.tpl.fill(`${key}${i}`, accuracy[key].toFixed(2))
-            else this.tpl.fill(`${key}${i}`, 'n/a')
-        }
-    }
-}
diff --git a/website/assets/js/models.vue.js b/website/assets/js/models.vue.js
new file mode 100644
index 000000000..b14e2791e
--- /dev/null
+++ b/website/assets/js/models.vue.js
@@ -0,0 +1,138 @@
+/**
+ * Initialise model overviews
+ * @param {string} repo - Repository to load from, in the format user/repo.
+ */
+export default function(repo) {
+    const LICENSES = {
+        'CC BY 4.0':       'https://creativecommons.org/licenses/by/4.0/',
+        'CC BY-SA':        'https://creativecommons.org/licenses/by-sa/3.0/',
+        'CC BY-SA 3.0':    'https://creativecommons.org/licenses/by-sa/3.0/',
+        'CC BY-SA 4.0':    'https://creativecommons.org/licenses/by-sa/4.0/',
+        'CC BY-NC':        'https://creativecommons.org/licenses/by-nc/3.0/',
+        'CC BY-NC 3.0':    'https://creativecommons.org/licenses/by-nc/3.0/',
+        'CC-BY-NC-SA 3.0': 'https://creativecommons.org/licenses/by-nc-sa/3.0/',
+        'GPL':             'https://www.gnu.org/licenses/gpl.html',
+        'LGPL':            'https://www.gnu.org/licenses/lgpl.html',
+        'MIT':             'https://opensource.org/licenses/MIT'
+    };
+    const URL = `https://raw.githubusercontent.com/${repo}/master`;
+    const models = [...document.querySelectorAll('[data-vue]')]
+        .map(el => el.getAttribute('data-vue'));
+
+    document.addEventListener('DOMContentLoaded', ev => {
+        fetch(`${URL}/compatibility.json`)
+            .then(res => res.json())
+            .then(json => models.forEach(modelId => new Vue({
+                el: `[data-vue="${modelId}"]`,
+                data: {
+                    repo: `https://github.com/${repo}`,
+                    compat: json.spacy,
+                    loading: false,
+                    error: false,
+                    id: modelId,
+                    version: 'n/a',
+                    notes: null,
+                    sizeFull: null,
+                    pipeline: null,
+                    releaseUrl: null,
+                    description: null,
+                    license: null,
+                    author: null,
+                    url: null,
+                    vectors: null,
+                    sources: null,
+                    uas: null,
+                    las: null,
+                    tags_acc: null,
+                    ents_f: null,
+                    ents_p: null,
+                    ents_r: null,
+                    modelLicenses: LICENSES,
+                    spacyVersion: Object.keys(json.spacy)[0]
+                },
+                computed: {
+                    compatVersion() {
+                        const res = this.compat[this.spacyVersion][this.id];
+                        return res ? `${this.id}-${res[0]}` : false;
+                    },
+                    orderedCompat() {
+                        return Object.keys(this.compat)
+                            .filter(v => !v.includes('a') && !v.includes('dev') && !v.includes('rc'));
+                    },
+                    hasAccuracy() {
+                        return this.uas || this.las || this.tags_acc || this.ents_f || this.ents_p || this.ents_r;
+                    }
+                },
+                beforeMount() {
+                    const version = this.$_getLatestVersion(this.id);
+                    if (version) {
+                        this.loading = true;
+                        fetch(`${URL}/meta/${this.id}-${version}.json`)
+                            .then(res => res.json())
+                            .then(json => this.$_updateData(json))
+                            .catch(err => { this.error = true });
+                    }
+                },
+                updated() {
+                    window.dispatchEvent(new Event('resize'));  // scroll position for progress
+                },
+                methods: {
+                    $_updateData(data) {
+                        const fullName = `${data.lang}_${data.name}-${data.version}`;
+                        this.version = data.version;
+                        this.releaseUrl = `${this.repo}/releases/tag/${fullName}`;
+                        this.sizeFull = data.size;
+                        this.pipeline = data.pipeline;
+                        this.notes = data.notes;
+                        this.description = data.description;
+                        this.vectors = this.$_formatVectors(data.vectors);
+                        this.sources = data.sources;
+                        this.author = data.author;
+                        this.url = data.url;
+                        this.license = data.license;
+                        const accuracy = data.accuracy || {};
+                        for (let key of Object.keys(accuracy)) {
+                            this[key] = accuracy[key].toFixed(2);
+                        }
+                        this.loading = false;
+                    },
+
+                    $_getLatestVersion(modelId) {
+                        for (let [spacy_v, models] of Object.entries(this.compat)) {
+                            if (models[modelId]) {
+                                return models[modelId][0];
+                            }
+                        }
+                    },
+
+                    $_formatVectors(data) {
+                        if (!data) {
+                            return 'n/a';
+                        }
+                        if (Object.values(data).every(n => n == 0)) {
+                            return 'context vectors only';
+                        }
+                        const { keys, vectors, width } = data;
+                        const nKeys = this.$_abbrNum(keys);
+                        const nVectors = this.$_abbrNum(vectors);
+                        return `${nKeys} keys, ${nVectors} unique vectors (${width} dimensions)`;
+                    },
+
+                    /**
+                     * Abbreviate a number, e.g. 14249930 --> 14.25m.
+                     * @param {number|string} num - The number to convert.
+                     * @param {number} fixed - Number of decimals.
+                     */
+                    $_abbrNum: function(num = 0, fixed = 1) {
+                        const suffixes = ['', 'k', 'm', 'b', 't'];
+                        if (num === null || num === 0) return 0;
+                        const b = num.toPrecision(2).split('e');
+                        const k = (b.length === 1) ? 0 : Math.floor(Math.min(b[1].slice(1), 14) / 3);
+                        const n = (k < 1) ? num : num / Math.pow(10, k * 3);
+                        const c = (k >= 1 && n >= 100 ) ? Math.round(n) : n.toFixed(fixed);
+                        return (c < 0 ? c : Math.abs(c)) + suffixes[k];
+                    }
+                }
+            })))
+    });
+}
diff --git a/website/assets/js/nav-highlighter.js b/website/assets/js/nav-highlighter.js
deleted file mode 100644
index a7bb227d5..000000000
--- a/website/assets/js/nav-highlighter.js
+++ /dev/null
@@ -1,35 +0,0 @@
-'use strict';
-
-import { $, $$ } from './util.js';
-
-export default class NavHighlighter {
-    /**
-     * Hightlight section in viewport in sidebar, using in-view library.
-     * @param {string} sectionAttr - Data attribute of sections.
-     * @param {string} navAttr - Data attribute of navigation items.
-     * @param {string} activeClass – Class name of active element.
-     */
-    constructor(sectionAttr, navAttr, activeClass = 'is-active') {
-        this.sections = [...$$(`[${navAttr}]`)];
-        // highlight first item regardless
-        if (this.sections.length) this.sections[0].classList.add(activeClass);
-        this.navAttr = navAttr;
-        this.sectionAttr = sectionAttr;
-        this.activeClass = activeClass;
-        if (window.inView) inView(`[${sectionAttr}]`)
-            .on('enter', this.highlightSection.bind(this));
-    }
-
-    /**
-     * Check if section in view exists in sidebar and mark as active.
-     * @param {node} section - The section in view.
-     */
-    highlightSection(section) {
-        const id = section.getAttribute(this.sectionAttr);
-        const el = $(`[${this.navAttr}="${id}"]`);
-        if (el) {
-            this.sections.forEach(el => el.classList.remove(this.activeClass));
-            el.classList.add(this.activeClass);
-        }
-    }
-}
diff --git a/website/assets/js/progress.js b/website/assets/js/progress.js
deleted file mode 100644
index 1497547d8..000000000
--- a/website/assets/js/progress.js
+++ /dev/null
@@ -1,52 +0,0 @@
-'use strict';
-
-import { $ } from './util.js';
-
-export default class ProgressBar {
-    /**
-     * Animated reading progress bar.
-     * @param {string} selector – CSS selector of progress bar element.
-     */
-    constructor(selector) {
-        this.scrollY = 0;
-        this.sizes = this.updateSizes();
-        this.el = $(selector);
-        this.el.setAttribute('max', 100);
-        window.addEventListener('scroll', this.onScroll.bind(this));
-        window.addEventListener('resize', this.onResize.bind(this));
-    }
-
-    onScroll(ev) {
-        this.scrollY = (window.pageYOffset || document.scrollTop) - (document.clientTop || 0);
-        requestAnimationFrame(this.update.bind(this));
-    }
-
-    onResize(ev) {
-        this.sizes = this.updateSizes();
-        requestAnimationFrame(this.update.bind(this));
-    }
-
-    update() {
-        const offset = 100 - ((this.sizes.height - this.scrollY - this.sizes.vh) / this.sizes.height * 100);
-        this.el.setAttribute('value', (this.scrollY == 0) ? 0 : offset || 0);
-    }
-
-    /**
-     * Update scroll and viewport height. Called on load and window resize.
-     */
-    updateSizes() {
-        return {
-            height: Math.max(
-                document.body.scrollHeight,
-                document.body.offsetHeight,
-                document.documentElement.clientHeight,
-                document.documentElement.scrollHeight,
-                document.documentElement.offsetHeight
-            ),
-            vh: Math.max(
-                document.documentElement.clientHeight,
-                window.innerHeight || 0
-            )
-        }
-    }
-}
diff --git a/website/assets/js/rollup.js b/website/assets/js/rollup.js
deleted file mode 100644
index ed1763022..000000000
--- a/website/assets/js/rollup.js
+++ /dev/null
@@ -1,25 +0,0 @@
-/**
- * This file is bundled by Rollup, compiled with Babel and included as
- *