diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index bccd4b6d..00000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,22 +0,0 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the -// README at: https://github.com/devcontainers/templates/tree/main/src/jekyll -{ - "name": "Jekyll", - // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile - "image": "mcr.microsoft.com/devcontainers/jekyll:1-bullseye", - - // Features to add to the dev container. More info: https://containers.dev/features. - // "features": {}, - - // Use 'forwardPorts' to make a list of ports inside the container available locally. - // "forwardPorts": [], - - // Uncomment the next line to run commands after the container is created. - "postCreateCommand": "bundle install && bundle exec jekyll serve" - - // Configure tool-specific properties. - // "customizations": {}, - - // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. - // "remoteUser": "root" -} diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index f659d9e3..21a9ed78 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,19 +1,4 @@ -# CODEOWNERS file sets rules for who should be notified when a pull request -# modifies code in a specific path. -# See https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners for more information. +# CODEOWNERS — who gets requested for review on pull requests. +# See https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners -# Configuration parameters for site -_config.yml @lwasser @kierisi -CNAME @lwasser @kierisi - -# Workflow files for GitHub Actions -.github/** @lwasser @willingc - -# Site pages -_site/* @lwasser @kierisi - -# Blog posts -_posts/* @lwasser @kierisi - -# Scripts -scripts/* @lwasser @willingc +* @lwasser @willingc diff --git a/.github/workflows/build-site.yml b/.github/workflows/build-site.yml index 05e3a280..67a34f31 100644 --- a/.github/workflows/build-site.yml +++ b/.github/workflows/build-site.yml @@ -20,7 +20,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: - node-version: "20" + node-version: "24" cache: npm - name: Install npm dependencies diff --git a/.github/workflows/deploy-gh-pages.yml b/.github/workflows/deploy-gh-pages.yml index f21f5ae2..9268f6a2 100644 --- a/.github/workflows/deploy-gh-pages.yml +++ b/.github/workflows/deploy-gh-pages.yml @@ -28,7 +28,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: - node-version: "20" + node-version: "24" cache: npm - name: Install npm dependencies diff --git a/.github/workflows/linkcheck.yml b/.github/workflows/linkcheck.yml index a09565c1..97e60098 100644 --- a/.github/workflows/linkcheck.yml +++ b/.github/workflows/linkcheck.yml @@ -21,7 +21,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: - node-version: "20" + node-version: "24" cache: npm - name: Install npm dependencies diff --git a/.gitignore b/.gitignore index bf9b000d..8af37a4f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,3 @@ -_site/ -.sass-cache/ -.jekyll-metadata -_config-dev.yml -_config.yml -Gemfile.lock .DS_Store images/full-images/ all_contribs.pickle @@ -18,7 +12,6 @@ public/ resources/ .lycheecache -# site redesign +# local agent / test work (never commit) .cursor/* _law_tests/* -jekyll/* diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..a45fd52c --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +24 diff --git a/.ruby-version b/.ruby-version deleted file mode 100644 index 619b5376..00000000 --- a/.ruby-version +++ /dev/null @@ -1 +0,0 @@ -3.3.3 diff --git a/404.md b/404.md deleted file mode 100644 index e523d615..00000000 --- a/404.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -layout: splash -permalink: /404.html -title: Oops! I couldn't find the page that you want. Page Not Found -feature_404: - - title: "Oopsies" - excerpt: "Oopsies" ---- - -## Are you looking for any of the following: - -* [our package guide](https://www.pyopensci.org/python-package-guide) -* [our peer review guide](https://www.pyopensci.org/peer-review-guide) - -If not, please try to use the top navigation to get to the right spot. -If you think there is an error on our site - [please open an issue here](https://github.com/pyOpenSci/pyopensci.github.io/issues). diff --git a/CNAME b/CNAME deleted file mode 100644 index b3eaf6ed..00000000 --- a/CNAME +++ /dev/null @@ -1 +0,0 @@ -www.pyopensci.org diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 60a45cad..e5401c7a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,9 +11,7 @@ We have a bash script, `scripts/date-updated.sh`, that walks blog posts and sets the `last_modified:` date in front matter from your local git history. It only updates posts that already include a `last_modified:` key. -Hugo blog posts live in `content/blog/`. The script still scans `_posts/` -(Jekyll layout) — update the script path after cutover, or edit front matter -manually until then. +Hugo blog posts live in `content/blog/`. From the repository root: @@ -35,9 +33,8 @@ questions, please open an issue in the repository or contact us via Slack. This repository, [`pyOpenSci/pyopensci.github.io`](https://github.com/pyOpenSci/pyopensci.github.io), contains the source code for the [pyOpenSci.org](https://www.pyopensci.org) website. -The site is migrating from Jekyll to **[Hugo](https://gohugo.io)**. Hugo source -lives at the repo root (`content/`, `data/`, `themes/clean-hugo/`). The legacy -Jekyll site remains in `jekyll/` until production cutover. +The site is built with **[Hugo](https://gohugo.io)**. Source lives at the repo +root (`content/`, `data/`, `themes/clean-hugo/`). If you want to contribute pages, blog posts, theme layouts, SCSS, or shortcodes, start here: diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index a0b5a399..712af6fd 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -32,8 +32,7 @@ There is no `static/scss/` folder. All authored styles live under ### `assets/css/` — main styling (SCSS) -* Entry file: `themes/clean-hugo/assets/css/site.scss` (see **Jekyll coexistence** - below — normally this would be `main.scss`) +* Entry file: `themes/clean-hugo/assets/css/main.scss` * Partials: `_footer.scss`, `_blog.scss`, `_events.scss`, etc. * Hugo compiles this at **build time** (local `hugo server` or Netlify). * Output goes to `public/css/site.min..css` — never commit that file. @@ -55,19 +54,18 @@ Defined in `themes/clean-hugo/layouts/_default/baseof.html`: 2. **Main stylesheet** — Hugo Pipes pipeline: ```go - {{ $styles := resources.Get "css/site.scss" | toCSS | postCSS | minify | fingerprint }} + {{ $styles := resources.Get "css/main.scss" | toCSS | postCSS | minify | fingerprint }} ``` Steps: SCSS → CSS → PostCSS (autoprefixer) → minify → fingerprinted URL. 3. **Syntax CSS** — plain link to `/css/syntax.css` from theme `static/`. -Root `package.json` and `postcss.config.js` exist **for step 2 only**. They are -not used by Jekyll. +Root `package.json` and `postcss.config.js` exist for the Hugo asset pipeline. ## SCSS file map -`site.scss` imports partials in this order (simplified): +`main.scss` imports partials in this order (simplified): | Partial | Purpose | |---------|---------| @@ -102,7 +100,7 @@ colors there for site-wide brand updates. **SCSS variables** — in `_variables.scss` (`$spacing-md`, `$breakpoint-lg`, etc.). Use for layout, spacing, and component structure inside the theme. -**Fonts** — loaded via `@font-face` in `site.scss` from +**Fonts** — loaded via `@font-face` in `main.scss` from `themes/clean-hugo/static/fonts/`. Font family names are configured in `hugo.toml` `[params.theme.fonts]`. @@ -133,28 +131,6 @@ hugo gen chromastyles --style=monokai > themes/clean-hugo/static/css/syntax.css Then commit the updated `syntax.css`. -## Jekyll coexistence (temporary — revert at cutover) - -While Jekyll and Hugo share this repo, **root** `assets/css/main.scss` remains -on `main`. That file is Jekyll’s Minimal Mistakes entry point and starts with -YAML front matter (`---`), which Hugo’s SCSS compiler cannot parse. - -Hugo’s `resources.Get "css/main.scss"` resolves **site `assets/` before theme -`assets/`**, so the Jekyll file shadows the theme stylesheet and Netlify builds -fail. - -**Workaround (current):** - -* Theme entry SCSS is named `site.scss` (not `main.scss`). -* Layouts load `resources.Get "css/site.scss"` in `baseof.html` and `404.html`. - -**Revert when Jekyll is retired** (after root Jekyll assets are removed or -moved under `jekyll/`): - -1. Rename `themes/clean-hugo/assets/css/site.scss` → `main.scss`. -2. Update `baseof.html` and `404.html` to `resources.Get "css/main.scss"`. -3. Remove this section from `DEVELOPMENT.md`. - ## Netlify `netlify.toml` runs `npm ci && hugo --gc --minify`. CSS is built on Netlify the @@ -177,10 +153,3 @@ the same Hugo build as Netlify, then validates the output: CircleCI and the CircleCI artifact redirector were removed during the Hugo migration. - -**Still on Jekyll (migrate later):** - -| Workflow | Purpose | -|----------|---------| -| `.github/workflows/linkcheck.yml` | Weekly scheduled link check | -| `.github/workflows/deploy-gh-pages.yml` | GitHub Pages deploy badge in README | diff --git a/Gemfile b/Gemfile deleted file mode 100644 index 8c823b49..00000000 --- a/Gemfile +++ /dev/null @@ -1,17 +0,0 @@ -source "https://rubygems.org" - -gem "jekyll-include-cache" -gem "webrick" -gem "html-proofer" -gem "jekyll-webp" -gem "ffi", "= 1.16.3" - -gem "jekyll", "~>4.3.3" - -group :jekyll_plugins do - gem "jekyll-sitemap", "~> 1.4" - gem "jekyll-gist", "~> 1.5" - gem "jekyll-feed", "~> 0.17.0" - gem "jemoji", "~> 0.13.0" - gem "jekyll-redirect-from", "~> 0.16.0" -end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 00000000..68453caf --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,160 @@ +GEM + remote: https://rubygems.org/ + specs: + activesupport (7.2.2.1) + base64 + benchmark (>= 0.3) + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + logger (>= 1.4.2) + minitest (>= 5.1) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + base64 (0.2.0) + benchmark (0.4.1) + bigdecimal (3.2.2) + colorator (1.1.0) + concurrent-ruby (1.3.5) + connection_pool (2.5.3) + drb (2.2.3) + em-websocket (0.5.3) + eventmachine (>= 0.12.9) + http_parser.rb (~> 0) + ethon (0.16.0) + ffi (>= 1.15.0) + eventmachine (1.2.7) + faraday (2.13.1) + faraday-net_http (>= 2.0, < 3.5) + json + logger + faraday-net_http (3.4.1) + net-http (>= 0.5.0) + ffi (1.16.3) + forwardable-extended (2.6.0) + gemoji (4.1.0) + google-protobuf (4.27.2-arm64-darwin) + bigdecimal + rake (>= 13) + html-pipeline (2.14.3) + activesupport (>= 2) + nokogiri (>= 1.4) + html-proofer (3.19.4) + addressable (~> 2.3) + mercenary (~> 0.3) + nokogiri (~> 1.13) + parallel (~> 1.10) + rainbow (~> 3.0) + typhoeus (~> 1.3) + yell (~> 2.0) + http_parser.rb (0.8.0) + i18n (1.14.7) + concurrent-ruby (~> 1.0) + jekyll (4.3.3) + addressable (~> 2.4) + colorator (~> 1.0) + em-websocket (~> 0.5) + i18n (~> 1.0) + jekyll-sass-converter (>= 2.0, < 4.0) + jekyll-watch (~> 2.0) + kramdown (~> 2.3, >= 2.3.1) + kramdown-parser-gfm (~> 1.0) + liquid (~> 4.0) + mercenary (>= 0.3.6, < 0.5) + pathutil (~> 0.9) + rouge (>= 3.0, < 5.0) + safe_yaml (~> 1.0) + terminal-table (>= 1.8, < 4.0) + webrick (~> 1.7) + jekyll-feed (0.17.0) + jekyll (>= 3.7, < 5.0) + jekyll-gist (1.5.0) + octokit (~> 4.2) + jekyll-include-cache (0.2.1) + jekyll (>= 3.7, < 5.0) + jekyll-paginate (1.1.0) + jekyll-redirect-from (0.16.0) + jekyll (>= 3.3, < 5.0) + jekyll-sass-converter (3.0.0) + sass-embedded (~> 1.54) + jekyll-sitemap (1.4.0) + jekyll (>= 3.7, < 5.0) + jekyll-watch (2.2.1) + listen (~> 3.0) + jekyll-webp (1.0.0) + jemoji (0.13.0) + gemoji (>= 3, < 5) + html-pipeline (~> 2.2) + jekyll (>= 3.0, < 5.0) + json (2.15.0) + kramdown (2.4.0) + rexml + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + liquid (4.0.4) + listen (3.9.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + logger (1.6.6) + mercenary (0.3.6) + minitest (5.25.5) + net-http (0.6.0) + uri + nokogiri (1.18.8-arm64-darwin) + racc (~> 1.4) + octokit (4.25.1) + faraday (>= 1, < 3) + sawyer (~> 0.9) + parallel (1.24.0) + pathutil (0.16.2) + forwardable-extended (~> 2.6) + public_suffix (5.1.1) + racc (1.8.1) + rainbow (3.1.1) + rake (13.2.1) + rb-fsevent (0.11.2) + rb-inotify (0.11.1) + ffi (~> 1.0) + rexml (3.4.1) + rouge (4.3.0) + safe_yaml (1.0.5) + sass-embedded (1.77.8-arm64-darwin) + google-protobuf (~> 4.26) + sawyer (0.9.2) + addressable (>= 2.3.5) + faraday (>= 0.17.3, < 3) + securerandom (0.4.1) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) + typhoeus (1.4.1) + ethon (>= 0.9.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + unicode-display_width (2.5.0) + uri (1.0.3) + webrick (1.9.1) + yell (2.2.2) + +PLATFORMS + arm64-darwin + +DEPENDENCIES + ffi (= 1.16.3) + html-proofer + jekyll (~> 4.3.3) + jekyll-feed (~> 0.17.0) + jekyll-gist (~> 1.5) + jekyll-include-cache + jekyll-paginate (~> 1.1) + jekyll-redirect-from (~> 0.16.0) + jekyll-sitemap (~> 1.4) + jekyll-webp + jemoji (~> 0.13.0) + webrick + +BUNDLED WITH + 2.5.18 diff --git a/README.md b/README.md index b0782829..4c64e302 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![All Contributors](https://img.shields.io/badge/all_contributors-55-orange.svg?style=flat-square)](#contributors-) -[![Deploy Jekyll site to Pages](https://github.com/pyOpenSci/pyopensci.github.io/actions/workflows/deploy-gh-pages.yml/badge.svg)](https://github.com/pyOpenSci/pyopensci.github.io/actions/workflows/deploy-gh-pages.yml) +[![Deploy Hugo site to Pages](https://github.com/pyOpenSci/pyopensci.github.io/actions/workflows/deploy-gh-pages.yml/badge.svg)](https://github.com/pyOpenSci/pyopensci.github.io/actions/workflows/deploy-gh-pages.yml) [![DOI](https://zenodo.org/badge/174412809.svg)](https://zenodo.org/doi/10.5281/zenodo.10594115) [![Netlify Status](https://api.netlify.com/api/v1/badges/ddb85e67-0925-455a-a616-5bece97a65b2/deploy-status)](https://app.netlify.com/projects/pyos-website/deploys) @@ -26,15 +26,13 @@ For organization-wide norms, see ## Installation and development -The site is migrating from [Jekyll](https://jekyllrb.com) to [Hugo](https://gohugo.io). -Hugo source lives at the **repository root** (`content/`, `data/`, `themes/clean-hugo/`). -The legacy Jekyll site remains in `jekyll/` until production cutover; active Hugo -work is on the `website-redesign` branch ([PR #883](https://github.com/pyOpenSci/pyopensci.github.io/pull/883)). +This site is built with [Hugo](https://gohugo.io). Source lives at the repository +root (`content/`, `data/`, `themes/clean-hugo/`). ### Prerequisites - **[Hugo Extended](https://gohugo.io/installation/)** `0.139.4` (matches Netlify and CI) -- **[Node.js](https://nodejs.org/)** `20` and npm (PostCSS / autoprefixer for the theme CSS pipeline) +- **[Node.js](https://nodejs.org/)** `24` (Active LTS) and npm (PostCSS / autoprefixer for the theme CSS pipeline). An `.nvmrc` is in the repo root — run `nvm use` if you use nvm. ### Run the site locally @@ -82,7 +80,6 @@ matter are included in local builds and production deploys without extra flags. | `themes/clean-hugo/` | Site theme (layouts, SCSS, shortcodes) | | `static/` | Images, favicons, Netlify `_redirects` | | `hugo.toml` | Site config, navigation, theme parameters | -| `jekyll/` | Legacy Jekyll mirror (reference only during migration) | For CSS architecture, SCSS partials, and Netlify build details, see [DEVELOPMENT.md](./DEVELOPMENT.md). For contributor workflows and the docs @@ -139,9 +136,8 @@ chmod +x scripts/date-updated.sh ./scripts/date-updated.sh ``` -The script currently scans `_posts/` (Jekyll layout). Hugo blog posts live in -`content/blog/` — update the script path when cutting over, or edit front matter -manually until then. +The script scans `content/blog/` for posts that include a `last_modified:` key +in front matter. ## How to update contributor names diff --git a/_config.yml b/_config.yml deleted file mode 100644 index 58cc029c..00000000 --- a/_config.yml +++ /dev/null @@ -1,350 +0,0 @@ -# Welcome to Jekyll! -# -# This config file is meant for settings that affect your entire site, values -# which you are expected to set up once and rarely need to edit after that. -# For technical reasons, this file is *NOT* reloaded automatically when you use -# `jekyll serve`. If you change this file, please restart the server process. - -# Theme Settings -# -# Review documentation to determine if you should use `theme` or `remote_theme` -# https://mmistakes.github.io/minimal-mistakes/docs/quick-start-guide/#installing-the-theme - -# theme : "minimal-mistakes-jekyll" -remote_theme: #"mmistakes/minimal-mistakes" -minimal_mistakes_skin: "default" # "air", "aqua", "contrast", "dark", "dirt", "neon", "mint", "plum", "sunrise" - -# Site Settings -locale: "en-US" -title: "pyOpenSci" -title_separator: "-" -name: "pyOpenSci" -description: "pyOpenSci's Website" -url: "https://www.pyopensci.org" # the base hostname & protocol for your site e.g. "https://mmistakes.github.io" -baseurl: "" # the subpath of your site, e.g. "/blog" -repository: "pyopensci/pyopensci.github.io" # GitHub username/repo-name e.g. "mmistakes/minimal-mistakes" -teaser: # path of fallback teaser image, e.g. "/assets/images/500x300.png" -logo: "/images/logo.png" # path of logo image to display in the masthead, e.g. "/assets/images/88x88.png" -masthead_title: # overrides the website title displayed in the masthead, use " " for no title -# breadcrumbs : false # true, false (default) -words_per_minute: 200 -comments: - provider: "giscus" - giscus: - repo_id: "MDEwOlJlcG9zaXRvcnkxNzQ0MTI4MDk=" - category_name: "website-comments" - category_id: "DIC_kwDOCmVUCc4CSE-o" - discussion_term: "pathname" - reactions_enabled: "1" # '1' for enabled (default), '0' for disabled - theme: "light" - disqus: - shortname: # https://help.disqus.com/customer/portal/articles/466208-what-s-a-shortname- - discourse: - server: # https://meta.discourse.org/t/embedding-discourse-comments-via-javascript/31963 , e.g.: meta.discourse.org - facebook: - # https://developers.facebook.com/docs/plugins/comments - appid: - num_posts: # 5 (default) - colorscheme: # "light" (default), "dark" - utterances: - theme: # "github-light" (default), "github-dark" - issue_term: # "pathname" (default) -staticman: - allowedFields: # ['name', 'email', 'url', 'message'] - branch: # "master" - commitMessage: # "New comment by {fields.name}" - filename: # comment-{@timestamp} - format: # "yml" - moderation: # true - path: # "/_data/comments/{options.slug}" (default) - requiredFields: # ['name', 'email', 'message'] - transforms: - email: # "md5" - generatedFields: - date: - type: # "date" - options: - format: # "iso8601" (default), "timestamp-seconds", "timestamp-milliseconds" - endpoint: # URL of your own deployment with trailing slash, will fallback to the public instance -reCaptcha: - siteKey: - secret: -atom_feed: - path: # blank (default) uses feed.xml -search: true #true # true, false (default) -search_full_content: # true, false (default) -search_provider: # lunr (default), algolia, google -algolia: - application_id: # YOUR_APPLICATION_ID - index_name: # YOUR_INDEX_NAME - search_only_api_key: # YOUR_SEARCH_ONLY_API_KEY - powered_by: # true (default), false -google: - search_engine_id: # YOUR_SEARCH_ENGINE_ID - instant_search: # false (default), true -# SEO Related -google_site_verification: -bing_site_verification: -yandex_site_verification: -naver_site_verification: - -# Social Sharing -twitter: - username: -facebook: - username: - app_id: - publisher: -og_image: # Open Graph/Twitter default site image -# For specifying social profiles -# - https://developers.google.com/structured-data/customize/social-profiles -social: - type: # Person or Organization (defaults to Person) - name: # If the user or organization name differs from the site's name - links: # An array of links to social media profiles - bluesky: https://bsky.app/profile/pyopensci.org -# Analytics -analytics: - provider: false # false (default), "google", "google-universal", "custom" - google: - tracking_id: - anonymize_ip: # true, false (default) - -# Site Author -author: - name: "pyOpenSci" - avatar: "/images/people/pyopensc-executive-council.png" # circle pyos logo - bio: "We make it easier for scientists to create, find, maintain, and contribute to reusable code and software." - location: "Global" - email: - links: - - label: "Email" - icon: "fas fa-fw fa-envelope-square" - url: mailto:media@pyopensci.org - - label: "GitHub" - icon: "fab fa-fw fa-github" - url: "https://github.com/pyopensci" - - label: "Bluesky" - icon: "fa-solid fa-cloud" - url: "https://bsky.app/profile/pyopensci.org" - - label: "Mastodon" - icon: "fa-brands fa-mastodon" - url: - - label: "LinkedIn" - icon: "fa-brands fa-linkedin" - url: "https://www.linkedin.com/company/pyopensci/" - - label: "Discord" - icon: "fa-brands fa-discord" - url: "https://discord.gg/yYyDFP2BcP" - - -# Site Footer -footer: - scripts: - links: - - label: "Mastodon" - icon: "fab fa-fw fa-mastodon" - url: https://fosstodon.org/@pyOpenSci - - label: "Bluesky" - icon: "fa-solid fa-cloud" - url: https://bsky.app/profile/pyopensci.org - - label: "LinkedIn" - icon: "fab fa-fw fa-linkedin" - url: https://www.linkedin.com/company/pyopensci - - label: "GitHub" - icon: "fab fa-fw fa-github" - url: https://github.com/pyOpenSci - - label: "YouTube" - icon: "fa-brands fa-youtube" - url: "https://www.youtube.com/@pyopensci" - - label: "Discord" - icon: "fa-brands fa-discord" - url: "https://discord.gg/yYyDFP2BcP" - -# Reading Files -include: - - .htaccess - - _pages -exclude: - - "*.sublime-project" - - "*.sublime-workspace" - - vendor - - .asset-cache - - .bundle - - .jekyll-assets-cache - - .sass-cache - - assets/js/plugins - - assets/js/_main.js - - assets/js/vendor - - Capfile - - CHANGELOG - - config - - Gemfile - - Gruntfile.js - - gulpfile.js - - LICENSE - - log - - node_modules - - package.json - - Rakefile - - README - - tmp - - /docs # ignore Minimal Mistakes /docs - - /test # ignore Minimal Mistakes /test - - styles/* # text spelling check - - lychee-report.md -keep_files: - - .git - - .svn -encoding: "utf-8" -markdown_ext: "markdown,mkdown,mkdn,mkd,md" - -# Conversion -markdown: kramdown -highlighter: rouge -lsi: false -excerpt_separator: "\n\n" -incremental: false - -# Markdown Processing -kramdown: - input: GFM - hard_wrap: false - auto_ids: true - footnote_nr: 1 - entity_output: as_char - toc_levels: 1..2 - smart_quotes: lsquo,rsquo,ldquo,rdquo - enable_coderay: false - -# Sass/SCSS -sass: - sass_dir: _sass - style: compressed # http://sass-lang.com/documentation/file.SASS_REFERENCE.html#output_style - quiet_deps: true - -# Outputting -permalink: /:categories/:title/ -blog_archive_age_days: 548 # posts older than this appear in "From the archives" (~18 months) -timezone: # https://en.wikipedia.org/wiki/List_of_tz_database_time_zones - -# Plugins (previously gems:) -plugins: - - jekyll-sitemap - - jekyll-gist - - jekyll-feed - - jemoji - - jekyll-include-cache - - jekyll-redirect-from - -# mimic GitHub Pages with --safe -whitelist: - - jekyll-sitemap - - jekyll-gist - - jekyll-feed - - jemoji - - jekyll-include-cache - - jekyll-redirect-from - -collections: - packaging: - people: true - tutorials: - people: true - packages: - output: true - permalink: /:collection/:path/ - -# Archives -# Type -# - GitHub Pages compatible archive pages built with Liquid ~> type: liquid (default) -# - Jekyll Archives plugin archive pages ~> type: jekyll-archives -# Path (examples) -# - Archive page should exist at path when using Liquid method or you can -# expect broken links (especially with breadcrumbs enabled) -# - /tags/my-awesome-tag/index.html ~> path: /tags/ -# - path: /categories/ -# - path: / -category_archive: - type: liquid - path: /categories/ -tag_archive: - type: liquid - path: /tags/ -# https://github.com/jekyll/jekyll-archives -# jekyll-archives: -# enabled: -# - categories -# - tags -# layouts: -# category: archive-taxonomy -# tag: archive-taxonomy -# permalinks: -# category: /categories/:name/ -# tag: /tags/:name/ - -# HTML Compression -# - http://jch.penibelst.de/ -compress_html: - clippings: all - ignore: - envs: development - -# Defaults -defaults: - # _posts - - scope: - path: "" - type: packages - values: - layout: single - author_profile: true - comments: true - share: true - related: true - toc: true - - scope: - path: "" - type: posts - values: - layout: single - author_profile: true - read_time: true - comments: # true - share: true - related: true -#### Support webp -webp: - enabled: true - - # The quality of the webp conversion 0 to 100 (where 100 is least lossy) - quality: 75 - - # List of directories containing images to optimize, nested directories will only be checked if `nested` is true - # By default the generator will search for a folder called `/img` under the site root and process all jpg, png and tiff image files found there. - img_dir: ["/images"] - - # Whether to search in nested directories or not - nested: false - - # add ".gif" to the format list to generate webp for animated gifs as well - formats: [".jpeg", ".jpg", ".png", ".tiff"] - - # File extensions for animated gif files - gifs: [".gif"] - - # Set to true to always regenerate existing webp files - regenerate: false - - # Local path to the WebP utilities to use (relative or absolute) - # Omit or leave as nil to use the utilities shipped with the gem, override only to use your local install - # Eg : "/usr/local/bin/cwebp" - webp_path: nil - - # List of files or directories to exclude - # e.g. custom or hand generated webp conversion files - exclude: [] - - # append '.webp' to filename after original extension rather than replacing it. - # Default transforms `image.png` to `image.webp`, while changing to true transforms `image.png` to `image.png.webp` - append_ext: false diff --git a/_data/_site/navigation.yml b/_data/_site/navigation.yml deleted file mode 100644 index 5175161c..00000000 --- a/_data/_site/navigation.yml +++ /dev/null @@ -1,12 +0,0 @@ -# main links -main: - - title: "Quick-Start Guide" - url: https://mmistakes.github.io/minimal-mistakes/docs/quick-start-guide/ - # - title: "About" - # url: https://mmistakes.github.io/minimal-mistakes/about/ - # - title: "Sample Posts" - # url: /year-archive/ - # - title: "Sample Collections" - # url: /collection-archive/ - # - title: "Sitemap" - # url: /sitemap/ diff --git a/_data/_site/ui-text.yml b/_data/_site/ui-text.yml deleted file mode 100644 index f5cfcfca..00000000 --- a/_data/_site/ui-text.yml +++ /dev/null @@ -1,1304 +0,0 @@ -# User interface text and labels - -# English (default) -# ----------------- -en: &DEFAULT_EN - page : "Page" - pagination_previous : "Previous" - pagination_next : "Next" - breadcrumb_home_label : "Home" - breadcrumb_separator : "/" - menu_label : "Toggle menu" - search_label : "Toggle search" - toc_label : "On this page" - ext_link_label : "Direct link" - less_than : "less than" - minute_read : "minute read" - share_on_label : "Share on" - meta_label : - tags_label : "Tags:" - categories_label : "Categories:" - date_label : "Updated:" - comments_label : "Leave a comment" - comments_title : "Comments" - more_label : "Learn more" - related_label : "You may also enjoy" - follow_label : "Follow:" - feed_label : "Feed" - powered_by : "Powered by" - website_label : "Website" - email_label : "Email" - recent_posts : "Recent posts" - undefined_wpm : "Undefined parameter words_per_minute at _config.yml" - comment_form_info : "Your email address will not be published. Required fields are marked" - comment_form_comment_label : "Comment" - comment_form_md_info : "Markdown is supported." - comment_form_name_label : "Name" - comment_form_email_label : "Email address" - comment_form_website_label : "Website (optional)" - comment_btn_submit : "Submit comment" - comment_btn_submitted : "Submitted" - comment_success_msg : "Thanks for your comment! It will show on the site once it has been approved." - comment_error_msg : "Sorry, there was an error with your submission. Please make sure all required fields have been completed and try again." - loading_label : "Loading..." - search_placeholder_text : "Enter your search term..." - results_found : "Result(s) found" - back_to_top : "Back to top" -en-US: - <<: *DEFAULT_EN -en-CA: - <<: *DEFAULT_EN -en-GB: - <<: *DEFAULT_EN -en-AU: - <<: *DEFAULT_EN - -# Spanish -# ------- -es: &DEFAULT_ES - page : "Página" - pagination_previous : "Anterior" - pagination_next : "Siguiente" - breadcrumb_home_label : "Inicio" - breadcrumb_separator : "/" - menu_label : - search_label : - toc_label : "Contenidos" - ext_link_label : "Enlace" - less_than : "menos de" - minute_read : "minuto de lectura" - share_on_label : "Compartir" - meta_label : - tags_label : "Etiquetas:" - categories_label : "Categorías:" - date_label : "Actualizado:" - comments_label : "Dejar un comentario" - comments_title : "Comentar" - more_label : "Ver más" - related_label : "Podrías ver también" - follow_label : "Seguir:" - feed_label : "Feed" - powered_by : "Powered by" - website_label : "Sitio web" - email_label : "Email" - recent_posts : "Entradas recientes" - undefined_wpm : "Parametro words_per_minute (Palabras por minuto) no definido en _config.yml" - comment_form_info : "Su dirección de correo no será publicada. Se han resaltado los campos requeridos" - comment_form_comment_label : "Comentario" - comment_form_md_info : "Markdown está soportado." - comment_form_name_label : "Nombre" - comment_form_email_label : "Dirección de E-mail" - comment_form_website_label : "Sitio web (opcional)" - comment_btn_submit : "Enviar Commentario" - comment_btn_submitted : "Enviado" - comment_success_msg : "Gracias por su comentario!, Este se visualizará en el sitio una vez haya sido aprobado" - comment_error_msg : "Lo sentimos, ha ocurrido un error al enviar su comentario. Por favor asegurese que todos los campos han sido diligenciados e intente de nuevo" - loading_label : "Cargando..." -es-ES: - <<: *DEFAULT_ES -es-CO: - <<: *DEFAULT_ES - -# French -# ------ -fr: &DEFAULT_FR - page : "Page" - pagination_previous : "Précédent" - pagination_next : "Suivant" - breadcrumb_home_label : "Accueil" - breadcrumb_separator : "/" - menu_label : "Menu" - search_label : - toc_label : "Sur cette page" - ext_link_label : "Lien direct" - less_than : "moins de" - minute_read : "minute(s) de lecture" - share_on_label : "Partager sur" - meta_label : - tags_label : "Tags :" - categories_label : "Catégories :" - date_label : "Mis à jour :" - comments_label : "Laisser un commentaire" - comments_title : "Commentaires" - more_label : "Lire plus" - related_label : "Vous pourriez aimer aussi" - follow_label : "Contact" - feed_label : "Flux" - powered_by : "Propulsé par" - website_label : "Site" - email_label : "Email" - recent_posts : "Posts récents" - undefined_wpm : "Le paramètre words_per_minute n'est pas défini dans _config.yml" - comment_form_info : "Votre adresse email ne sera pas visible. Les champs obligatoires sont marqués" - comment_form_comment_label : "Commentaire" - comment_form_md_info : "Markdown est supporté." - comment_form_name_label : "Nom" - comment_form_email_label : "Adresse mail" - comment_form_website_label : "Site web (optionnel)" - comment_btn_submit : "Envoyer" - comment_btn_submitted : "Envoyé" - comment_success_msg : "Merci pour votre commentaire, il sera visible sur le site une fois approuvé." - comment_error_msg : "Désolé, une erreur est survenue lors de la soumission. Vérifiez que les champs obligatoires ont été remplis et réessayez." - loading_label : "Chargement..." - search_placeholder_text : "Entrez votre recherche..." - results_found : "Aucun résultat trouvé" - back_to_top : "Retour en haut" -fr-FR: - <<: *DEFAULT_FR -fr-BE: - <<: *DEFAULT_FR -fr-CH: - <<: *DEFAULT_FR - -# Turkish -# ------- -tr: &DEFAULT_TR - page : "Sayfa" - pagination_previous : "Önceki" - pagination_next : "Sonraki" - breadcrumb_home_label : "Ana Sayfa" - breadcrumb_separator : "/" - menu_label : - search_label : - toc_label : "İçindekiler" - ext_link_label : "Doğrudan Bağlantı" - less_than : "Şu süreden az: " - minute_read : "dakika tahmini okuma süresi" - share_on_label : "Paylaş" - meta_label : - tags_label : "Etiketler:" - categories_label : "Kategoriler:" - date_label : "Güncelleme tarihi:" - comments_label : "Yorum yapın" - comments_title : "Yorumlar" - more_label : "Daha fazlasını öğrenin" - related_label : "Bunlar ilginizi çekebilir:" - follow_label : "Takip et:" - feed_label : "RSS" - powered_by : "Emeği geçenler: " - website_label : "Web sayfası" - email_label : "E-posta" - recent_posts : "Son yazılar" - undefined_wpm : "_config.yml dosyasında tanımlanmamış words_per_minute parametresi" - comment_form_info : "Email adresiniz gösterilmeyecektir. Zorunlu alanlar işaretlenmiştir" - comment_form_comment_label : "Yorumunuz" - comment_form_md_info : "Markdown desteklenmektedir." - comment_form_name_label : "Adınız" - comment_form_email_label : "Email adresiniz" - comment_form_website_label : "Websiteniz (opsiyonel)" - comment_btn_submit : "Yorum Yap" - comment_btn_submitted : "Gönderildi" - comment_success_msg : "Yorumunuz için teşekkürler! Yorumunuz onaylandıktan sonra sitede gösterilecektir." - comment_error_msg : "Maalesef bir hata oluştu. Lütfen zorunlu olan tüm alanları doldurduğunuzdan emin olun ve sonrasında tekrar deneyin." - loading_label : "Yükleniyor..." -tr-TR: - <<: *DEFAULT_TR - -# Portuguese -# ---------- -pt: &DEFAULT_PT - page : "Página" - pagination_previous : "Anterior" - pagination_next : "Seguinte" - breadcrumb_home_label : "Início" - breadcrumb_separator : "/" - menu_label : - search_label : - toc_label : "Nesta Página" - ext_link_label : "Link Direto" - less_than : "menos de" - minute_read : "minutos de leitura" - share_on_label : "Partilhar no" - meta_label : - tags_label : "Etiquetas:" - categories_label : "Categorias:" - date_label : "Atualizado:" - comments_label : "Deixe um Comentário" - comments_title : "Comentários" - more_label : "Saber mais" - related_label : "Também pode gostar de" - follow_label : "Siga:" - feed_label : "Feed" - powered_by : "Feito com" - website_label : "Site" - email_label : "Email" - recent_posts : "Artigos Recentes" - undefined_wpm : "Parâmetro words_per_minute não definido em _config.yml" - comment_form_info : "O seu endereço email não será publicado. Os campos obrigatórios estão assinalados" - comment_form_comment_label : "Comentário" - comment_form_md_info : "Markdown é suportado." - comment_form_name_label : "Nome" - comment_form_email_label : "Endereço Email" - comment_form_website_label : "Site (opcional)" - comment_btn_submit : "Sumbeter Comentário" - comment_btn_submitted : "Submetido" - comment_success_msg : "Obrigado pelo seu comentário! Será visível no site logo que aprovado." - comment_error_msg : "Lamento, ocorreu um erro na sua submissão. Por favor verifique se todos os campos obrigatórios estão corretamente preenchidos e tente novamente." - loading_label : "A carregar..." -pt-PT: - <<: *DEFAULT_PT -# Brazilian Portuguese -pt-BR: - page : "Página" - pagination_previous : "Anterior" - pagination_next : "Próxima" - breadcrumb_home_label : "Home" - breadcrumb_separator : "/" - menu_label : - search_label : - toc_label : "Nesta página" - ext_link_label : "Link direto" - less_than : "menos que" - minute_read : "minuto(s) de leitura" - share_on_label : "Compartilhe em" - meta_label : - tags_label : "Tags:" - categories_label : "Categorias:" - date_label : "Atualizado em:" - comments_label : "Deixe um comentário" - comments_title : - more_label : "Aprenda mais" - related_label : "Talvez você goste também" - follow_label : "Acompanhe em" - feed_label : "Feed" - powered_by : "Feito com" - website_label : "Site" - email_label : "Email" - recent_posts : "Postagens recentes" - undefined_wpm : "Parâmetro indefinido em words_per_minute no _config.yml" - comment_form_info : "Seu email não será publicado. Os campos obrigatórios estão marcados" - comment_form_comment_label : "Comentário" - comment_form_md_info : "Markdown é suportado." - comment_form_name_label : "Nome" - comment_form_email_label : "Email" - comment_form_website_label : "Site (opcional)" - comment_btn_submit : "Enviar Comentário" - comment_btn_submitted : "Enviado" - comment_success_msg : "Obrigado pelo seu comentário! Ele aparecerá no site assim que for aprovado." - comment_error_msg : "Desculpe, ocorreu um erro no envio. Por favor verifique se todos os campos obrigatórios foram preenchidos e tente novamente." - loading_label : "Carregando..." - -# Italian -# ------- -it: &DEFAULT_IT - page : "Pagina" - pagination_previous : "Precedente" - pagination_next : "Prossima" - breadcrumb_home_label : "Home" - breadcrumb_separator : "/" - menu_label : - search_label : - toc_label : "Indice della pagina" - ext_link_label : "Link" - less_than : "meno di" - minute_read : "minuto/i di lettura" - share_on_label : "Condividi" - meta_label : - tags_label : "Tags:" - categories_label : "Categorie:" - date_label : "Aggiornato:" - comments_label : "Scrivi un commento" - comments_title : - more_label : "Scopri di più" - related_label : "Potrebbe Piacerti Anche" - follow_label : "Segui:" - feed_label : "Feed" - powered_by : "Powered by" - website_label : "Website" - email_label : "Email" - recent_posts : "Articoli Recenti" - undefined_wpm : "Parametro words_per_minute non definito in _config.yml" - comment_form_info : "Il tuo indirizzo email non sarà pubblicato. Sono segnati i campi obbligatori" - comment_form_comment_label : "Commenta" - comment_form_md_info : "Il linguaggio Markdown è supportato" - comment_form_name_label : "Nome" - comment_form_email_label : "Indirizzo email" - comment_form_website_label : "Sito Web (opzionale)" - comment_btn_submit : "Invia commento" - comment_btn_submitted : "Inviato" - comment_success_msg : "Grazie per il tuo commento! Verrà visualizzato nel sito una volta che sarà approvato." - comment_error_msg : "C'è stato un errore con il tuo invio. Assicurati che tutti i campi richiesti siano stati completati e riprova." - loading_label : "Caricamento..." - search_placeholder_text : "Inserisci termini di ricerca..." - results_found : "Risultati" - back_to_top : "Vai su" -it-IT: - <<: *DEFAULT_IT - -# Chinese (zh-CN Chinese - China) -# -------------------------------- -zh: &DEFAULT_ZH_HANS - page : "页面" - pagination_previous : "向前" - pagination_next : "向后" - breadcrumb_home_label : "首页" - breadcrumb_separator : "/" - menu_label : "切换菜单" - search_label : - toc_label : "在本页上" - ext_link_label : "直接链接" - less_than : "少于" - minute_read : "分钟读完" - share_on_label : "分享" - meta_label : - tags_label : "标签:" - categories_label : "分类:" - date_label : "更新时间:" - comments_label : "留下评论" - comments_title : "评论" - more_label : "了解更多" - related_label : "猜您还喜欢" - follow_label : "关注:" - feed_label : "Feed" - powered_by : "技术来自于" - website_label : "网站" - email_label : "电子邮箱" - recent_posts : "最新文章" - undefined_wpm : "_config.yml配置中words_per_minute字段未定义" - comment_form_info : "您的电子邮箱地址并不会被展示。请填写标记为必须的字段。" - comment_form_comment_label : "评论" - comment_form_md_info : "Markdown语法已支持。" - comment_form_name_label : "姓名" - comment_form_email_label : "电子邮箱" - comment_form_website_label : "网站(可选)" - comment_btn_submit : "提交评论" - comment_btn_submitted : "已提交" - comment_success_msg : "感谢您的评论!被批准后它会立即在此站点展示。" - comment_error_msg : "很抱歉,您的提交存在错误。请确保所有必填字段都已填写正确,然后再试一次。" - loading_label : "正在加载..." - search_placeholder_text : "输入您要搜索的关键词..." - results_found : "条记录匹配" - back_to_top : "返回顶部" -zh-CN: - <<: *DEFAULT_ZH_HANS -zh-SG: - <<: *DEFAULT_ZH_HANS -# Taiwan (Traditional Chinese) -zh-TW: &DEFAULT_ZH_HANT - page : "頁面" - pagination_previous : "較舊" - pagination_next : "較新" - breadcrumb_home_label : "首頁" - breadcrumb_separator : "/" - menu_label : "切換選單" - search_label : - toc_label : "本頁" - ext_link_label : "外部連結" - less_than : "少於" - minute_read : "分鐘閱讀" - share_on_label : "分享到" - meta_label : - tags_label : "標籤:" - categories_label : "分類:" - date_label : "更新時間:" - comments_label : "留言" - comments_title : "留言內容" - more_label : "了解更多" - related_label : "猜您有與趣" - follow_label : "追蹤:" - feed_label : "RSS Feed" - powered_by : "Powered by" - website_label : "網站" - email_label : "電子信箱" - recent_posts : "最新文章" - undefined_wpm : "_config.yml 中未定義 words_per_minute" - comment_form_info : "您的電子信箱不會被公開. 必填部份已標記" - comment_form_comment_label : "留言內容" - comment_form_md_info : "支援Markdown語法。" - comment_form_name_label : "名字" - comment_form_email_label : "電子信箱帳號" - comment_form_website_label : "網頁 (可選填)" - comment_btn_submit : "送出留言" - comment_btn_submitted : "已送出" - comment_success_msg : "感謝您的留言! 審核後將會顯示在站上。" - comment_error_msg : "抱歉,部份資料輸入有問題。請確認資料填寫正確後再試一次。" - loading_label : "載入中..." -zh-HK: - <<: *DEFAULT_ZH_HANT - -# German / Deutsch -# ---------------- -de: &DEFAULT_DE - page : "Seite" - pagination_previous : "Vorherige" - pagination_next : "Nächste" - breadcrumb_home_label : "Start" - breadcrumb_separator : "/" - menu_label : "Menü ein-/ausschalten" - search_label : - toc_label : "Auf dieser Seite" - ext_link_label : "Direkter Link" - less_than : "weniger als" - minute_read : "Minuten zum lesen" - share_on_label : "Teilen auf" - meta_label : - tags_label : "Tags:" - categories_label : "Kategorien:" - date_label : "Aktualisiert:" - comments_label : "Hinterlassen Sie einen Kommentar" - comments_title : "Kommentare" - more_label : "Mehr anzeigen" - related_label : "Ihnen gefällt vielleicht auch" - follow_label : "Folgen:" - feed_label : "Feed" - powered_by : "Möglich durch" - website_label : "Webseite" - email_label : "E-Mail" - recent_posts : "Aktuelle Beiträge" - undefined_wpm : "Undefinierter Parameter words_per_minute in _config.yml" - comment_form_info : "Ihre E-Mail Adresse wird nicht veröffentlicht. Benötigte Felder sind markiert" - comment_form_comment_label : "Kommentar" - comment_form_md_info : "Markdown wird unterstützt." - comment_form_name_label : "Name" - comment_form_email_label : "E-Mail-Addresse" - comment_form_website_label : "Webseite (optional)" - comment_btn_submit : "Kommentar absenden" - comment_btn_submitted : "Versendet" - comment_success_msg : "Danke für Ihren Kommentar! Er wird auf der Seite angezeigt, nachdem er geprüft wurde." - comment_error_msg : "Entschuldigung, es gab einen Fehler. Bitte füllen Sie alle benötigten Felder aus und versuchen Sie es erneut." - loading_label : "Lade..." - search_placeholder_text : "Suchbegriff eingeben..." - results_found : "Ergebnis(se) gefunden" -de-DE: - <<: *DEFAULT_DE -de-AT: - <<: *DEFAULT_DE -de-CH: - <<: *DEFAULT_DE -de-BE: - <<: *DEFAULT_DE -de-LI: - <<: *DEFAULT_DE -de-LU: - <<: *DEFAULT_DE - -# Nepali (Nepal) -# -------------- -ne: &DEFAULT_NE - page : "पृष्‍ठ" - pagination_previous : "अघिल्लो" - pagination_next : "अर्को" - breadcrumb_home_label : "गृह" - breadcrumb_separator : "/" - menu_label : "टगल मेनु" - search_label : - toc_label : "यो पृष्‍ठमा" - ext_link_label : "सिधा सम्पर्क" - less_than : "कम्तिमा" - minute_read : "मिनेट पढ्नुहोस्" - share_on_label : "शेयर गर्नुहोस्" - meta_label : - tags_label : "ट्यागहरू:" - categories_label : "वर्गहरु:" - date_label : "अद्यावधिक:" - comments_label : "टिप्पणी दिनुहोस्" - comments_title : "टिप्पणीहरू" - more_label : "अझै सिक्नुहोस्" - related_label : "तपाईं रुचाउन सक्नुहुन्छ " - follow_label : "पछ्याउनुहोस्:" - feed_label : "फिड" - powered_by : "Powered by" - website_label : "वेबसाइट" - email_label : "इमेल" - recent_posts : "ताजा लेखहरु" - undefined_wpm : "अपरिभाषित प्यारामिटर शब्दहरू_प्रति_मिनेट at _config.yml" - comment_form_info : "तपाइँको इमेल ठेगाना प्रकाशित गरिने छैन।आवश्यक जानकारीहरुमा चिन्ह लगाइको छ" - comment_form_comment_label : "टिप्पणी" - comment_form_md_info : "मार्कडाउन समर्थित छ।" - comment_form_name_label : "नाम" - comment_form_email_label : "इमेल ठेगाना" - comment_form_website_label : "वेबसाइट (वैकल्पिक)" - comment_btn_submit : "टिप्पणी दिनुहोस् " - comment_btn_submitted : "टिप्पणी भयो" - comment_success_msg : "तपाईंको टिप्पणीको लागि धन्यवाद! एक पटक यो अनुमोदन गरेपछी यो साइटमा देखाउनेछ।" - comment_error_msg : "माफ गर्नुहोस्, तपाईंको टिप्पणी त्रुटि थियो।सबै आवश्यक जानकारीहरु पूरा गरिएको छ भने निश्चित गर्नुहोस् र फेरि प्रयास गर्नुहोस्।" - loading_label : "लोड हुँदैछ ..." -ne-NP: - <<: *DEFAULT_NE - -# Korean -# ------ -ko: &DEFAULT_KO - page : "페이지" - pagination_previous : "이전" - pagination_next : "다음" - breadcrumb_home_label : "Home" - breadcrumb_separator : "/" - menu_label : "토글 메뉴" - search_label : - toc_label : "On This Page" - ext_link_label : "직접 링크" - less_than : "최대" - minute_read : "분 소요" - share_on_label : "공유하기" - meta_label : - tags_label : "태그:" - categories_label : "카테고리:" - date_label : "업데이트:" - comments_label : "댓글남기기" - comments_title : "댓글" - more_label : "더 보기" - related_label : "참고" - follow_label : "팔로우:" - feed_label : "피드" - powered_by : "Powered by" - website_label : "웹사이트" - email_label : "이메일" - recent_posts : "최근 포스트" - undefined_wpm : "Undefined parameter words_per_minute at _config.yml" - comment_form_info : "이메일은 공개되지 않습니다. 작성 필요 필드:" - comment_form_comment_label : "댓글" - comment_form_md_info : "마크다운을 지원합니다." - comment_form_name_label : "이름" - comment_form_email_label : "이메일" - comment_form_website_label : "웹사이트(선택사항)" - comment_btn_submit : "댓글 등록" - comment_btn_submitted : "등록됨" - comment_success_msg : "감사합니다! 댓글이 머지된 후 확인하실 수 있습니다." - comment_error_msg : "댓글 등록에 문제가 있습니다. 필요 필드를 작성했는지 확인하고 다시 시도하세요." - loading_label : "로딩중..." -ko-KR: - <<: *DEFAULT_KO - -# Russian / Русский -# ----------------- -ru: &DEFAULT_RU - page : "Страница" - pagination_previous : "Предыдущая" - pagination_next : "Следующая" - breadcrumb_home_label : "Главная" - breadcrumb_separator : "/" - menu_label : "Выпадающее меню" - search_label : - toc_label : "Содержание" - ext_link_label : "Прямая ссылка" - less_than : "менее" - minute_read : "мин на чтение" - share_on_label : "Поделиться" - meta_label : - tags_label : "Метки:" - categories_label : "Разделы:" - date_label : "Дата изменения:" - comments_label : "Оставить комментарий" - comments_title : "Комментарии" - more_label : "Читать далее" - related_label : "Вам также может понравиться" - follow_label : "Связаться со мной:" - feed_label : "RSS-лента" - powered_by : "Сайт работает на" - website_label : "Сайт" - email_label : "Электронная почта" - recent_posts : "Свежие записи" - undefined_wpm : "Не определён параметр words_per_minute в _config.yml" - comment_form_info : "Ваш адрес электронной почты не будет опубликован. Обязательные поля помечены" - comment_form_comment_label : "Комментарий" - comment_form_md_info : "Поддерживается синтаксис Markdown." - comment_form_name_label : "Имя" - comment_form_email_label : "Электронная почта" - comment_form_website_label : "Ссылка на сайт (необязательно)" - comment_btn_submit : "Оставить комментарий" - comment_btn_submitted : "Отправлено" - comment_success_msg : "Спасибо за Ваш комментарий! Он будет опубликован на сайте после проверки." - comment_error_msg : "К сожалению, произошла ошибка с отправкой комментария. Пожалуйста, убедитесь, что все обязательные поля заполнены и попытайтесь снова." - loading_label : "Отправка..." - search_placeholder_text : "Введите поисковый запрос..." - results_found : "Найдено" -ru-RU: - <<: *DEFAULT_RU - -# Lithuanian / Lietuviškai -# ------------------------ -lt: &DEFAULT_LT - page : "Puslapis" - pagination_previous : "Ankstesnis" - pagination_next : "Sekantis" - breadcrumb_home_label : "Pagrindinis" - breadcrumb_separator : "/" - menu_label : "Meniu rodymas" - search_label : - toc_label : "Turinys" - ext_link_label : "Tiesioginė nuoroda" - less_than : "mažiau nei" - minute_read : "min. skaitymo" - share_on_label : "Pasidalinti" - meta_label : - tags_label : "Žymės:" - categories_label : "Kategorijos:" - date_label : "Atnaujinta:" - comments_label : "Palikti komentarą" - comments_title : "Komentaras" - more_label : "Skaityti daugiau" - related_label : "Taip pat turėtų patikti" - follow_label : "Sekti:" - feed_label : "Šaltinis" - powered_by : "Sukurta su" - website_label : "Tinklapis" - email_label : "El. paštas" - recent_posts : "Naujausi įrašai" - undefined_wpm : "Nedeklaruotas parametras words_per_minute faile _config.yml" - comment_form_info : "El. pašto adresas nebus viešinamas. Būtini laukai pažymėti." - comment_form_comment_label : "Komentaras" - comment_form_md_info : "Markdown palaikomas." - comment_form_name_label : "Vardas" - comment_form_email_label : "El. paštas" - comment_form_website_label : "Tinklapis (nebūtina)" - comment_btn_submit : "Komentuoti" - comment_btn_submitted : "Įrašytas" - comment_success_msg : "Ačiū už komentarą! Jis bus parodytas kai bus patvirtintas." - comment_error_msg : "Atleiskite, įvyko netikėta klaida įrašant komentarą. Pasitikrinkite ar užpildėte visus būtinus laukus ir pamėginkite dar kartą." - loading_label : "Kraunama..." -lt-LT: - <<: *DEFAULT_LT - -# Greek -# ----- -gr: &DEFAULT_GR - page : "Σελίδα" - pagination_previous : "Προηγούμενo" - pagination_next : "Επόμενo" - breadcrumb_home_label : "Αρχική" - breadcrumb_separator : "/" - menu_label : "Μενού" - search_label : - toc_label : "Περιεχόμενα" - ext_link_label : "Εξωτερικός Σύνδεσμος" - less_than : "Λιγότερο από" - minute_read : "λεπτά ανάγνωσης" - share_on_label : "Μοιραστείτε το" - meta_label : - tags_label : "Ετικέτες:" - categories_label : "Κατηγορίες:" - date_label : "Ενημερώθηκε:" - comments_label : "Αφήστε ένα σχόλιο" - comments_title : "Σχόλια" - more_label : "Διάβαστε περισσότερα" - related_label : "Σχετικές αναρτήσεις" - follow_label : "Ακολουθήστε:" - feed_label : "RSS Feed" - powered_by : "Δημιουργήθηκε με" - website_label : "Ιστοσελίδα" - email_label : "Email" - recent_posts : "Τελευταίες αναρτήσεις" - undefined_wpm : "Δεν έχει οριστεί η παράμετρος words_per_minute στο αρχείο _config.yml" - comment_form_info : "Η διεύθυνση email σας δεν θα δημοσιευθεί. Τα απαιτούμενα πεδία εμφανίζονται με αστερίσκο" - comment_form_comment_label : "Σχόλιο" - comment_form_md_info : "Το πεδίο υποστηρίζει Markdown." - comment_form_name_label : "Όνομα" - comment_form_email_label : "Διεύθυνση email" - comment_form_website_label : "Ιστοσελίδα (προαιρετικό)" - comment_btn_submit : "Υπόβαλε ένα σχόλιο" - comment_btn_submitted : "Έχει υποβληθεί" - comment_success_msg : "Ευχαριστούμε για το σχόλιό σας! Θα εμφανιστεί στην ιστοσελίδα αφού εγκριθεί." - comment_error_msg : "Λυπούμαστε, παρουσιάστηκε σφάλμα με την υποβολή σας. Παρακαλούμε βεβαιωθείτε ότι έχετε όλα τα απαιτούμενα πεδία συμπληρωμένα και δοκιμάστε ξανά." - loading_label : "Φόρτωση..." - search_placeholder_text : "Εισάγετε όρο αναζήτησης..." - results_found : "Αποτελέσματα" -gr-GR: - <<: *DEFAULT_GR - -# Swedish -# ------- -sv: &DEFAULT_SV - page : "Sidan" - pagination_previous : "Föregående" - pagination_next : "Nästa" - breadcrumb_home_label : "Hem" - breadcrumb_separator : "/" - menu_label : "Växla menyläge" - search_label : "Växla sökläge" - toc_label : "På denna sida" - ext_link_label : "Direkt länk" - less_than : "mindre än" - minute_read : "minut läsning" - share_on_label : "Dela på" - meta_label : - tags_label : "Taggar:" - categories_label : "Kategorier:" - date_label : "Uppdaterades:" - comments_label : "Lämna en kommentar" - comments_title : "Kommentarer" - more_label : "Lär dig mer" - related_label : "Du kanske vill även läsa:" - follow_label : "Följ:" - feed_label : "Flöde" - powered_by : "Framställd med" - website_label : "Webbsida" - email_label : "E-post" - recent_posts : "Senaste inlägg" - undefined_wpm : "Odefinerade parametrar words_per_minute i _config.yml" - comment_form_info : "Din e-post adress kommer inte att publiceras. Obligatoriska fält är markerade." - comment_form_comment_label : "Kommentar" - comment_form_md_info : "Stöd för Markdown finns." - comment_form_name_label : "Namn" - comment_form_email_label : "E-post adress" - comment_form_website_label : "Webdsida (valfritt)" - comment_btn_submit : "Skicka en kommentar" - comment_btn_submitted : "Kommentaren har tagits emot" - comment_success_msg : "Tack för din kommentar! Den kommer att visas på sidan så fort den har godkännts." - comment_error_msg : "Tyvärr det har blivit något fel i ett av fälten, se till att du fyllt i alla obligatoriska fält och försök igen." - loading_label : "Laddar..." - search_placeholder_text : "Fyll i sökterm..." - results_found : "Resultat funna" - back_to_top : "Tillbaka till toppen" -sv-SE: - <<: *DEFAULT_SV -sv-FI: - <<: *DEFAULT_SV - -# Dutch -# ----- -nl: &DEFAULT_NL - page : "Pagina" - pagination_previous : "Vorige" - pagination_next : "Volgende" - breadcrumb_home_label : "Home" - breadcrumb_separator : "/" - menu_label : "Wissel Menu" - search_label : - toc_label : "Op deze pagina" - ext_link_label : "Directe Link" - less_than : "minder dan" - minute_read : "minuut gelezen" - share_on_label : "Deel op" - meta_label : - tags_label : "Labels:" - categories_label : "Categorieën:" - date_label : "Bijgewerkt:" - comments_label : "Laat een reactie achter" - comments_title : "Commentaren" - more_label : "Meer informatie" - related_label : "Bekijk ook eens" - follow_label : "Volg:" - feed_label : "Feed" - powered_by : "Aangedreven door" - website_label : "Website" - email_label : "Email" - recent_posts : "Recente berichten" - undefined_wpm : "Niet gedefinieerde parameter words_per_minute bij _config.yml" - comment_form_info : "Uw e-mailadres wordt niet gepubliceerd. Verplichte velden zijn gemarkeerd" - comment_form_comment_label : "Commentaar" - comment_form_md_info : "Markdown wordt ondersteund." - comment_form_name_label : "Naam" - comment_form_email_label : "E-mailadres" - comment_form_website_label : "Website (optioneel)" - comment_btn_submit : "Commentaar toevoegen" - comment_btn_submitted : "Toegevoegd" - comment_success_msg : "Bedankt voor uw reactie! Het zal op de site worden weergegeven zodra het is goedgekeurd." - comment_error_msg : "Sorry, er is een fout opgetreden bij uw inzending. Zorg ervoor dat alle vereiste velden zijn voltooid en probeer het opnieuw." - loading_label : "Laden..." -nl-BE: - <<: *DEFAULT_NL -nl-NL: - <<: *DEFAULT_NL - -# Indonesian -# ---------- -id: &DEFAULT_ID - page : "Halaman" - pagination_previous : "Kembali" - pagination_next : "Maju" - breadcrumb_home_label : "Home" - breadcrumb_separator : "/" - menu_label : "Menu Toggle" - search_label : - toc_label : "Pada Halaman Ini" - ext_link_label : "Link langsung" - less_than : "Kurang dari" - minute_read : "Waktu baca" - share_on_label : "Berbagi di" - meta_label : - tags_label : "Golongan:" - categories_label : "Kategori:" - date_label : "Diupdate:" - comments_label : "Tinggalkan komentar" - comments_title : "Komentar" - more_label : "Pelajari lagi" - related_label : "Anda juga akan suka" - follow_label : "Ikuti:" - feed_label : "Feed" - powered_by : "Didukung oleh" - website_label : "Website" - email_label : "Email" - recent_posts : "Posting terbaru" - undefined_wpm : "Parameter terdeskripsi words_per_minute di _config.yml" - comment_form_info : "Email Anda tidak akan dipublish. Kolom yang diperlukan ditandai" - comment_form_comment_label : "Komentar" - comment_form_md_info : "Markdown disupport." - comment_form_name_label : "Nama" - comment_form_email_label : "Alamat email" - comment_form_website_label : "Website (opsional)" - comment_btn_submit : "Submit Komentar" - comment_btn_submitted : "Telah disubmit" - comment_success_msg : "Terimakasih atas komentar Anda! Komentar ini akan tampil setelah disetujui." - comment_error_msg : "Maaf, ada kesalahan pada submisi Anda. Pastikan seluruh kolom sudah dilengkapi dan coba kembali." - loading_label : "Sedang meload..." -id-ID: - <<: *DEFAULT_ID - -# Vietnamese -# ---------- -vi: &DEFAULT_VI - page : "Trang" - pagination_previous : "Trước" - pagination_next : "Sau" - breadcrumb_home_label : "Trang chủ" - breadcrumb_separator : "/" - menu_label : "Menu" - search_label : - toc_label : "Tại trang này" - ext_link_label : "Đường dẫn trực tiếp" - less_than : "nhỏ hơn" - minute_read : "phút đọc" - share_on_label : "Chia sẻ tại" - meta_label : - tags_label : "Nhãn:" - categories_label : "Chủ đề:" - date_label : "Cập nhật:" - comments_label : "Để lại bình luận" - comments_title : "Bình luận" - more_label : "Mở rộng" - related_label : "Có thể bạn cũng thích" - follow_label : "Theo dõi:" - feed_label : "Feed" - powered_by : "Được hỗ trợ bởi" - website_label : "Website" - email_label : "Email" - recent_posts : "Bài viết mới" - undefined_wpm : "Chưa định nghĩa thông số words_per_minute tại _config.yml" - comment_form_info : "Email của bạn sẽ được giữ bí mật. Các phần bắt buộc được đánh dấu." - comment_form_comment_label : "Bình luận" - comment_form_md_info : "Hỗ trợ Markdown." - comment_form_name_label : "Tên" - comment_form_email_label : "Địa chỉ email" - comment_form_website_label : "Website (không bắt buộc)" - comment_btn_submit : "Gửi bình luận" - comment_btn_submitted : "Đã được gửi" - comment_success_msg : "Cảm ơn bạn đã bình luận! Bình luận sẽ xuất hiện sau khi được duyệt." - comment_error_msg : "Rất tiếc, có lỗi trong việc gửi bình luận. Hãy đảm bảo toàn bộ các phần bắt buộc đã được điền đầy đủ và thử lại." - loading_label : "Đang tải..." -vi-VN: - <<: *DEFAULT_VI - -# Danish -# ------ -da: &DEFAULT_DA - page : "Side" - pagination_previous : "Forrige" - pagination_next : "Næste" - breadcrumb_home_label : "Home" - breadcrumb_separator : "/" - menu_label : "Vis/skjul menu" - search_label : - toc_label : "På denne side" - ext_link_label : "Direkte link" - less_than : "mindre end" - minute_read : "minutters læsning" - share_on_label : "Del på" - meta_label : - tags_label : "Nøgleord:" - categories_label : "Kategorier:" - date_label : "Opdateret:" - comments_label : "Skriv en kommentar" - comments_title : "Kommentarer" - more_label : "Lær mere" - related_label : "Måske kan du også lide" - follow_label : "Følg:" - feed_label : "Feed" - powered_by : "Drives af" - website_label : "Website" - email_label : "E-mail" - recent_posts : "Seneste indlæg" - undefined_wpm : "Parameteren words_per_minute er ikke defineret i _config.yml" - comment_form_info : "Din e-mail bliver ikke offentliggjort. Obligatoriske felter er markeret" - comment_form_comment_label : "Kommentar" - comment_form_md_info : "Markdown er understøttet." - comment_form_name_label : "Navn" - comment_form_email_label : "E-mail" - comment_form_website_label : "Website (frivillig)" - comment_btn_submit : "Send kommentar" - comment_btn_submitted : "Sendt" - comment_success_msg : "Tak for din kommentar! Den bliver vist på siden, så snart den er godkendt." - comment_error_msg : "Desværre skete der en fejl. Prøv igen, mens du sørger for at alle obligatoriske felter er udfyldt." - loading_label : "Indlæser..." - search_placeholder_text : "Hvad leder du efter..." - results_found : "Resultat(er) fundet" - back_to_top : "Tilbage til toppen" -da-DK: - <<: *DEFAULT_DA - -# Polish -# ------ -pl: &DEFAULT_PL - page : "Strona" - pagination_previous : "Poprzednia" - pagination_next : "Następna" - breadcrumb_home_label : "Strona główna" - breadcrumb_separator : "/" - menu_label : "Przełącz menu" - search_label : - toc_label : "Spis treści" - ext_link_label : "Link bezpośredni" - less_than : "mniej niż" - minute_read : "minut(y)" - share_on_label : "Udostępnij" - meta_label : - tags_label : "Tagi:" - categories_label : "Kategorie:" - date_label : "Ostatnia aktualizacja:" - comments_label : "Zostaw komentarz" - comments_title : "Komentarze" - more_label : "Dowiedz się więcej" - related_label : "Także może Ci się spodobać" - follow_label : "Śledź:" - feed_label : "Feed" - powered_by : "Powstało dzięki" - website_label : "Strona" - email_label : "Email" - recent_posts : "Najnowsze wpisy" - undefined_wpm : "Parametr words_per_minute nie został zdefiniowany w _config.yml." - comment_form_info : "Twój adres email nie będzie udostępiony. Wymagane pola są oznaczone." - comment_form_comment_label : "Skomentuj" - comment_form_md_info : "Markdown jest wspierany" - comment_form_name_label : "Imię" - comment_form_email_label : "Adres email" - comment_form_website_label : "Strona www (opcjonalna)" - comment_btn_submit : "Skomentuj" - comment_btn_submitted : "Komentarz dodany" - comment_success_msg : "Dziękuję za Twój komentarz! Zostanie dodany po akceptacji." - comment_error_msg : "Niestety wystąpił błąd. Proszę upewnij się, że wszystkie wymagane pola zostały wypełnione i spróbuj ponownie." - loading_label : "Trwa ładowanie strony..." -pl-PL: - <<: *DEFAULT_PL - -# Japanese -# -------- -ja: &DEFAULT_JA - page : "ページ" - pagination_previous : "前へ" - pagination_next : "次へ" - breadcrumb_home_label : "ホーム" - breadcrumb_separator : "/" - menu_label : "メニュー" - search_label : - toc_label : "目次" - ext_link_label : "リンク" - less_than : - minute_read : - share_on_label : "共有" - meta_label : - tags_label : "タグ:" - categories_label : "カテゴリー:" - date_label : "更新日時:" - comments_label : "コメントする" - comments_title : "コメント" - more_label : "さらに詳しく" - related_label : "関連記事" - follow_label : "フォロー" - feed_label : - powered_by : - website_label : - email_label : - recent_posts : "最近の投稿" - undefined_wpm : "パラメータ words_per_minute が _config.yml で定義されていません" - comment_form_info : "メールアドレスが公開されることはありません。次の印のある項目は必ず入力してください:" - comment_form_comment_label : "コメント" - comment_form_md_info : "Markdown を使用できます" - comment_form_name_label : "名前" - comment_form_email_label : "メールアドレス" - comment_form_website_label : "URL (任意)" - comment_btn_submit : "コメントを送信する" - comment_btn_submitted : "送信しました" - comment_success_msg : "コメントありがとうございます! コメントは承認されるとページに表示されます。" - comment_error_msg : "送信エラーです。必須項目がすべて入力されていることを確認して再送信してください。" - loading_label : "読み込み中..." - search_placeholder_text : "検索キーワードを入力してください..." - results_found : "件" -ja-JP: - <<: *DEFAULT_JA - -# Slovak -# ----------------- -sk: &DEFAULT_SK - page : "Stránka" - pagination_previous : "Predošlá" - pagination_next : "Ďalšia" - breadcrumb_home_label : "Domov" - breadcrumb_separator : "/" - menu_label : "Menu" - search_label : - toc_label : "Obsah" - ext_link_label : "Priamy odkaz" - less_than : "menej ako" - minute_read : "minút" - share_on_label : "Zdieľaj na" - meta_label : - tags_label : "Tagy:" - categories_label : "Kategórie:" - date_label : "Aktualizované:" - comments_label : "Zanechaj odkaz" - comments_title : "Komentáre" - more_label : "Dozvedieť sa viac" - related_label : "Podobné články" - follow_label : "Sleduj:" - feed_label : "Zoznam" - powered_by : "Stránka vytvorená pomocou" - website_label : "Web stránka" - email_label : "Email" - recent_posts : "Najnovšie príspevky" - undefined_wpm : "Nedefinovaný parameter words_per_minute v _config.yml" - comment_form_info : "Tvoja emailová adresa nebude publikovaná. Požadované polia sú označené" - comment_form_comment_label : "Komentár" - comment_form_md_info : "Markdown je podporovaný." - comment_form_name_label : "Meno" - comment_form_email_label : "Emailová adresa" - comment_form_website_label : "Webstránka (voliteľné)" - comment_btn_submit : "Vlož komentár" - comment_btn_submitted : "Vložený" - comment_success_msg : "Ďakujem za tvoj komentár! Po schválení bude zobrazený na stránke." - comment_error_msg : "Prepáč, pri ukladaní nastala chyba. Ubezpeč sa prosím, že si vyplnil všetky požadované polia a skús znova." - loading_label : "Načítava sa..." - search_placeholder_text : "Zadaj hľadaný výraz..." - results_found : "Nájdených výsledkov" - back_to_top : "Na začiatok stránky" -sk-SK: - <<: *DEFAULT_SK - -# Hungarian -# ----------------- -hu: &DEFAULT_HU - page : "Oldal" - pagination_previous : "Előző" - pagination_next : "Következő" - breadcrumb_home_label : "Kezdőlap" - breadcrumb_separator : "/" - menu_label : "Menü nyit/zár" - search_label : - toc_label : "Ezen az oldalon" - ext_link_label : "Közvetlen Link" - less_than : "kevesebb mint" - minute_read : "eltöltött percek" - share_on_label : "Megosztás" - meta_label : - tags_label : "Tagek:" - categories_label : "Kategóriák:" - date_label : "Frissítve:" - comments_label : "Szólj hozzá!" - comments_title : "Hozzászólások" - more_label : "Tovább" - related_label : "Ajánlások" - follow_label : "Követés:" - feed_label : "Folyam" - powered_by : "Powered by" - website_label : "Honlap" - email_label : "Email" - recent_posts : "Friss cikkek" - undefined_wpm : "Ismeretlen paraméter words_per_minute : _config.yml" - comment_form_info : "Az e-mail címed nem lesz publikus. A csillagozott mezők kitöltése kötelező." - comment_form_comment_label : "Hozzászólás" - comment_form_md_info : "Támogatott formázási mód: Markdown" - comment_form_name_label : "Név" - comment_form_email_label : "Email cím" - comment_form_website_label : "Honlap (nem kötelező):" - comment_btn_submit : "Hozzászólás elküldése" - comment_btn_submitted : "Hozzászólás elküldve" - comment_success_msg : "Köszönjük a Hozzászólást! A Hozzászólások csak előzetes moderáció után lesznek publikusak." - comment_error_msg : "Hoppá, hiba történt a beküldés közben. Kérlek ellenőrizd hogy minden kötelező mező ki van-e töltve." - loading_label : "Betöltés..." - search_placeholder_text : "Keresendő szöveg..." - results_found : "Találatok:" - back_to_top : "Oldal tetejére" -hu-HU: - <<: *DEFAULT_HU - -# Romanian -# ----------------- -ro: &DEFAULT_RO - page : "Pagina" - pagination_previous : "Anterior" - pagination_next : "Următor" - breadcrumb_home_label : "Acasă" - breadcrumb_separator : "/" - menu_label : "Comută meniul" - search_label : - toc_label : "Pe această pagină" - ext_link_label : "Link direct" - less_than : "mai puțin de" - minute_read : "minute de citit" - share_on_label : "Distribuie pe" - meta_label : - tags_label : "Etichete:" - categories_label : "Categorii:" - date_label : "Actualizat:" - comments_label : "Lasă un comentariu" - comments_title : "Comentarii" - more_label : "Citește mai departe" - related_label : "S-ar putea să-ți placă" - follow_label : "Urmărește:" - feed_label : "Feed RSS" - powered_by : "Cu sprijinul" - website_label : "Site" - email_label : "Email" - recent_posts : "Articole recente" - undefined_wpm : "Parametru words_per_minute nedefinit în _config.yml" - comment_form_info : "Adresa ta de email nu va fi făcută publică. Câmpurile marcate sunt obligatorii" - comment_form_comment_label : "Comentariu" - comment_form_md_info : "Markdown este suportat." - comment_form_name_label : "Nume" - comment_form_email_label : "Adresă de email" - comment_form_website_label : "Site (opțional)" - comment_btn_submit : "Trimite comentariul" - comment_btn_submitted : "Trimis" - comment_success_msg : "Mulțumesc pentru comentariu! Va apărea pe site în momentul în care va fi aprobat." - comment_error_msg : "Scuze, este o problemă cu comentariul tău. Asigură-te că toate câmpurile obligatorii au fost completate și încearcă din nou." - loading_label : "Se încarcă..." - search_placeholder_text : "Caută ceva..." - results_found : "Rezultate găsite" - back_to_top : "Înapoi în susul paginii" -ro-RO: - <<: *DEFAULT_RO - -# Punjabi -# ----------------- -pa: &DEFAULT_PA - page : "ਸਫ਼ਾ" - pagination_previous : "ਪਿਛਲਾ" - pagination_next : "ਅਗਲਾ " - breadcrumb_home_label : "ਘਰ" - breadcrumb_separator : "/" - menu_label : "ਟੌਗਲ ਮੀਨੂ" - search_label : - toc_label : "ਇਸ ਸਫ਼ੇ 'ਤੇ" - ext_link_label : "ਸਿੱਧਾ ਸੰਪਰਕ" - less_than : "ਤੋਂ ਘੱਟ" - minute_read : "ਮਿੰਟ ਵਿੱਚ ਪੜਿਆ ਜਾ ਸਕਦਾ ਹੈ" - share_on_label : "ਸਾਂਝਾ ਕਰੋ" - meta_label : - tags_label : "ਟੈਗ" - categories_label : "ਵਰਗ" - date_label : "ਅਪਡੇਟ ਕੀਤਾ:" - comments_label : "ਇੱਕ ਟਿੱਪਣੀ ਛੱਡੋ" - comments_title : "ਟਿੱਪਣੀਆਂ" - more_label : "ਹੋਰ ਜਾਣੋ" - related_label : "ਤੁਸੀਂ ਇਸਦਾ ਆਨੰਦ ਵੀ ਲੈ ਸਕਦੇ ਹੋ" - follow_label : "ਫਾਲੋ ਅੱਪ ਕਰੋ:" - feed_label : "ਫੀਡ" - powered_by : "ਦੁਆਰਾ ਸੰਚਾਲਿਤ" - website_label : "ਵੈੱਬਸਾਇਟ" - email_label : "ਈਮੇਲ" - recent_posts : "ਹਾਲ ਹੀ ਦੇ ਪੋਸਟ" - undefined_wpm : "_config.yml ਤੇ ਅਣ-ਪ੍ਰਭਾਸ਼ਿਤ ਪੈਰਾਮੀਟਰ words_per_minute" - comment_form_info : "ਤੁਹਾਡਾ ਈਮੇਲ ਪਤਾ ਪ੍ਰਕਾਸ਼ਿਤ ਨਹੀਂ ਕੀਤਾ ਜਾਵੇਗਾ। ਅਨੁਮਾਨਿਤ ਸਥਾਨਾਂ ਨੂੰ ਅੰਡਰਲਾਈਨ ਕੀਤਾ ਗਿਆ ਹੈ" - comment_form_comment_label : "ਟਿੱਪਣੀ" - comment_form_md_info : "ਮਾਰਕਡਾਊਨ ਵਰਤ ਸਕਦੇ ਹੋ।" - comment_form_name_label : "ਨਾਮ" - comment_form_email_label : "ਈਮੇਲ ਪਤਾ" - comment_form_website_label : "ਵੈਬਸਾਈਟ (ਵਿਕਲਪਿਕ)" - comment_btn_submit : "ਕੋਈ ਟਿੱਪਣੀ ਭੇਜੋ" - comment_btn_submitted : "ਪੇਸ਼ ਕੀਤਾ" - comment_success_msg : "ਤੁਹਾਡੀਆਂ ਟਿੱਪਣੀਆਂ ਲਈ ਧੰਨਵਾਦ! ਇਹ ਮਨਜ਼ੂਰੀ ਮਿਲਣ ਦੇ ਬਾਅਦ ਸਾਈਟ 'ਤੇ ਦਿਖਾਇਆ ਜਾਵੇਗਾ।" - comment_error_msg : "ਮੁਆਫ ਕਰਨਾ, ਤੁਹਾਡੀ ਅਧੀਨਗੀ ਵਿੱਚ ਕੋਈ ਗਲਤੀ ਹੋਈ ਸੀ ਕਿਰਪਾ ਕਰਕੇ ਯਕੀਨੀ ਬਣਾਓ ਕਿ ਸਾਰੇ ਲੋੜੀਂਦੇ ਖੇਤਰ ਪੂਰੇ ਹੋ ਗਏ ਹਨ ਅਤੇ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।" - loading_label : "ਲੋਡ ਹੋ ਰਿਹਾ ਹੈ..." - search_placeholder_text : "ਆਪਣੀ ਖੋਜ ਦੇ ਸ਼ਬਦ ਨੂੰ ਦਰਜ ਕਰੋ..." - results_found : "ਨਤੀਜਾ ਮਿਲਿਆ/ਮਿਲੇ" - back_to_top : "ਵਾਪਸ ਚੋਟੀ 'ਤੇ ਜਾਓ" -pa-IN: - <<: *DEFAULT_PA - -# Persian (Farsi) -# -------------- -fa: &DEFAULT_FA - page : "صفحه" - pagination_previous : "قبلی" - pagination_next : "بعدی" - breadcrumb_home_label : "صفحه اصلی" - breadcrumb_separator : "/" - menu_label : "فهرست" - toc_label : "در این صفحه" - ext_link_label : "لینک مستقیم" - less_than : " " - minute_read : "(طول مطالعه (دقیقه" - share_on_label : "اشتراک گذاری در" - meta_label : - tags_label : "تگ ها: " - categories_label : "دسته بندی ها: " - date_label : "به روز شده در: " - comments_label : "ارسال نظر" - comments_title : "نظرات" - more_label : "ادامه مطلب" - related_label : "ممکن است از این مطالب نیز لذت ببرید" - follow_label : "دنبال کنید: " - feed_label : "خوراک" - powered_by : "طراحی شده توسط" - website_label : "سایت اینترنتی" - email_label : "پست الکترونیک" - recent_posts : "آخرین مطالب" - undefined_wpm : ".(words_per_minute) _config.yml متغیر اشتباه در" - comment_form_info : ".آدرس ایمیل شما منتشر نخواهد شد. فیلدهای اجباری مشخص شده اند" - comment_form_comment_label : "دیدگاه" - comment_form_md_info : ".پشتیبانی می شود Markdown" - comment_form_name_label : "نام" - comment_form_email_label : "پست الکترونیک" - comment_form_website_label : "سایت اینترنتی (اختیاری)" - comment_btn_submit : "ارسال نظر" - comment_btn_submitted : "ارسال شد" - comment_success_msg : ".باتشکر از ارسال دیدگاه! پس از تأیید، این دیدگاه در سایت نشان داده خواهد شد" - comment_error_msg : ".متاسفانه در ارسال شما خطایی بود. لطفا مطمئن شوید تمام فیلدهای مورد نیاز تکمیل شده و دوباره امتحان کنید" - loading_label : "...بارگذاری" - search_placeholder_text : "...عبارت جستجوی خود را وارد کنید" - results_found : "نتایج" - back_to_top : "بازگشت به بالا" -fa-IR: - <<: *DEFAULT_FA - - -# Malayalam -# ----------------- -ml: &DEFAULT_ML - page : "പേജ്" - pagination_previous : "തിരികെ" - pagination_next : "മുന്നോട്ട്" - breadcrumb_home_label : "ഹോം" - breadcrumb_separator : "/" - menu_label : "ടോഗിൾ മെനു" - search_label : "ടോഗിൾ സെർച്ച്" - toc_label : "ഈ പേജിൽ" - ext_link_label : "ലിങ്കിലേക് പോകാൻ" - less_than : "ഏതാണ്ട്" - minute_read : "മിനിറ്റ് ദൈർഖ്യം" - share_on_label : "ഷെയർ ചെയ്യുവാൻ " - meta_label : - tags_label : "ടാഗുകൾ:" - categories_label : "വിഭാഗങ്ങൾ:" - date_label : "അവസാന മാറ്റം:" - comments_label : "അഭിപ്രായം രേഖപ്പെടുത്തുക" - comments_title : "അഭിപ്രായങ്ങൾ" - more_label : "കൂടുതൽ അറിയുവാൻ" - related_label : "നിങ്ങൾക് ഇതും ഇഷ്ടപ്പെട്ടേക്കാം" - follow_label : "പിന്തുടരുക:" - feed_label : "ഫീഡ്" - powered_by : "പവേർഡ് ബൈ" - website_label : "വെബ്സൈറ്റ്" - email_label : "ഇ-മെയിൽ" - recent_posts : "സമീപകാല പോസ്റ്റുകൾ" - undefined_wpm : "Config.yml ലെ words_per_minute പരാമീറ്റർ നിർവചിച്ചിട്ടില്ല." - comment_form_info : "നിങ്ങളുടെ ഇമെയിൽ വിലാസം പ്രസിദ്ധീകരിക്കില്ല. ആവശ്യമായ ഫീൽഡുകൾ അടയാളപ്പെടുത്തി." - comment_form_comment_label : "കമന്റ്" - comment_form_md_info : "Markdown സപ്പോർട്ട് ചെയ്യുന്നതാണ്." - comment_form_name_label : "പേര്" - comment_form_email_label : "ഇ-മെയിൽ" - comment_form_website_label : "വെബ്സൈറ് (ഓപ്ഷണൽ)" - comment_btn_submit : "അഭിപ്രായം രേഖപ്പെടുത്തുക" - comment_btn_submitted : "രേഖപ്പെടുത്തി" - comment_success_msg : "നിങ്ങളുടെ അഭിപ്രായത്തിന് നന്ദി! ഇത് അംഗീകരിച്ചുകഴിഞ്ഞാൽ ഇത് സൈറ്റിൽ പ്രദർശിപ്പിക്കും." - comment_error_msg : "ക്ഷമിക്കണം, നിങ്ങളുടെ സമർപ്പണവുമായി ബന്ധപ്പെട്ട് ഒരു പിശകുണ്ടായിരുന്നു. ആവശ്യമായ എല്ലാ ഫീൽഡുകളും പൂർത്തിയായിട്ടുണ്ടെന്ന് ഉറപ്പുവരുത്തുക, വീണ്ടും ശ്രമിക്കുക." - loading_label : "ലോഡിംഗ്..." - search_placeholder_text : "നിങ്ങളുടെ തിരയൽ പദം നൽകുക..." - results_found : "ഫലം (കൾ) കണ്ടെത്തി" - back_to_top : "മുകളിലേയ്ക്ക്" -ml-IN: - <<: *DEFAULT_ML - - -# Another locale -# -------------- -# diff --git a/_data/advisory.yml b/_data/advisory.yml deleted file mode 100644 index c924cf8e..00000000 --- a/_data/advisory.yml +++ /dev/null @@ -1,19 +0,0 @@ -- name: Tracy Teal - sort: 1 - bio: 'Executive Director, pyOpenSci' - organization: "RStudio" - twitter: - github_username: tracykteal - github_image_id: 889238 # You can find this by right clicking on the image in your bio, and copying the link. the last part contains a 7 digit number that is your avatar image! - title: "Board chair" - board: true -# Editors -- name: Karen Cranston - sort: 2 - title: "" - bio: '' - organization: "" - twitter: kcranstn - github_username: kcranston - github_image_id: 312034 - board: true diff --git a/_data/authors.yml b/_data/authors.yml deleted file mode 100644 index 846299fd..00000000 --- a/_data/authors.yml +++ /dev/null @@ -1,205 +0,0 @@ -pyopensci: - name : "pyOpenSci" - bio : "We make it easier for scientists to create, find, maintain, and contribute to reusable code and software." - avatar : "/images/people/pyopensc-executive-council.png" - links: - - label: "Email" - icon: "fas fa-fw fa-envelope-square" - url: "mailto:media@pyopensci.org" - - label: "Website" - icon: "fas fa-fw fa-link" - url: "https://www.pyopensci.org" - - label: "GitHub" - icon: "fab fa-fw fa-github" - url: "https://github.com/pyopensci" - - label: "LinkedIn" - icon: "fab fa-fw fa-linkedin" - url: "https://www.linkedin.com/company/pyopensci/" - - label: "Mastodon" - icon: "fab fa-fw fa-mastodon" - url: "https://fosstodon.org/@pyopensci" - - -Executive Council: - name : "pyOpenSci Executive Council" - bio : "We make it easier for scientists to create, find, maintain, and contribute to reusable code and software." - avatar : "/images/people/pyopensc-executive-council.png" - links: - - label: "Email" - icon: "fas fa-fw fa-envelope-square" - url: "mailto:admin@pyopensci.org" - - label: "Website" - icon: "fas fa-fw fa-link" - url: "https://www.pyopensci.org" - - label: "GitHub" - icon: "fab fa-fw fa-github" - url: "https://github.com/pyopensci" - - label: "LinkedIn" - icon: "fab fa-fw fa-linkedin" - url: "https://www.linkedin.com/company/pyopensci/" - - label: "Mastodon" - icon: "fab fa-fw fa-mastodon" - url: "https://fosstodon.org/@pyopensci" -Leah Wasser: - name : "Leah Wasser" - bio : "Executive Director & Founder, pyOpenSci" - avatar : "/images/people/leah-wasser-pyopensci-leadership.jpg" - links: - - label: "Email" - icon: "fas fa-fw fa-envelope-square" - url: "mailto:leah@pyopensci.org" - - label: "Website" - icon: "fas fa-fw fa-link" - url: "https://www.leahwasser.com" - - label: "GitHub" - icon: "fab fa-fw fa-github" - url: "https://github.com/lwasser" - - label: "LinkedIn" - icon: "fab fa-fw fa-linkedin" - url: "https://www.linkedin.com/in/leah-wasser-0138883/fo" - - label: "Mastodon" - icon: "fab fa-fw fa-mastodon" - url: "https://fosstodon.org/@leahawasser" -Niels Bantilan: - name : "Niels Bantilan" - bio : "Data Scientist, Machine Learning Engineer" - avatar : "/images/people/niels-bantilan-pyopensci.jpg" - links: - - label: "Email" - icon: "fas fa-fw fa-envelope-square" - url: "mailto:niels.bantilan@gmail.com" - - label: "Website" - icon: "fas fa-fw fa-link" - url: "https://cosmicbboy.github.io/" -Anita Graser: - name : "Anita Graser" - bio : "Data Scientist, Spatial Data Analyst, AIT Vienna" - avatar : "/images/people/anita-graser.png" - links: - - label: "Email" - icon: "fas fa-fw fa-envelope-square" - url: "mailto:anitagraser@gmx.at" - - label: "Website" - icon: "fas fa-fw fa-link" - url: "https://anitagraser.com" -Eric J. Ma: - name : "Eric J. Ma" - bio : "Senior Principal Data Scientist, Moderna" - avatar : "/images/people/eric-ma-headshot.jpeg" - links: - - label: "Website" - icon: "fas fa-fw fa-link" - url: "https://ericmjl.github.io/" - - label: "GitHub" - icon: "fab fa-fw fa-github" - url: "https://github.com/ericmjl" - - label: "LinkedIn" - icon: "fab fa-fw fa-linkedin" - url: "https://www.linkedin.com/in/ericmjl/" -Jesse Mostipak: - name : "Jesse Mostipak" - bio : "" - avatar : "/images/people/jesse-headshot-bandw.jpg" - links: - - label: "Email" - icon: "fas fa-fw fa-envelope-square" - url: "mailto:media@pyopensci.org" - - label: "Website" - icon: "fas fa-fw fa-link" - url: "https://www.pyopensci.org" - - label: "GitHub" - icon: "fab fa-fw fa-github" - url: "https://github.com/pyopensci" - - label: "LinkedIn" - icon: "fab fa-fw fa-linkedin" - url: "https://www.linkedin.com/company/pyopensci/" - - label: "Mastodon" - icon: "fab fa-fw fa-mastodon" - url: "https://fosstodon.org/@pyopensci" -Carlos Ramos Carreño: - name : "Carlos Ramos Carreño" - bio : "Software Developer" - avatar : "/images/people/carlos-ramos.jpg" - links: - - label: "Email" - icon: "fas fa-fw fa-envelope-square" - url: "mailto:vnmabus@gmail.com" - - label: "GitHub" - icon: "fab fa-fw fa-github" - url: "https://github.com/vnmabus" - - label: "LinkedIn" - icon: "fab fa-fw fa-linkedin" - url: "https://www.linkedin.com/in/carlos-ramos-carre%C3%B1o-3745932ab/" -Patrick J. Roddy: - name : "Patrick J. Roddy" - bio : "Research Software Engineer, University College London" - avatar : "/images/people/patrick-j-roddy.jpg" - links: - - label: "GitHub" - icon: "fab fa-fw fa-github" - url: "https://github.com/paddyroddy" - - label: "LinkedIn" - icon: "fab fa-fw fa-linkedin" - url: "https://www.linkedin.com/in/patrickjamesroddy" - - label: "Website" - icon: "fas fa-fw fa-link" - url: "https://paddyroddy.github.io" -Eliot W. Robson: - name : "Eliot W. Robson" - bio : "Research Software Engineer, Narmi Inc." - avatar : "/images/people/eliot-w-robson.jpg" - links: - - label: "Email" - icon: "fas fa-fw fa-envelope-square" - url: "mailto:eliot.robson24@gmail.com" - - label: "GitHub" - icon: "fab fa-fw fa-github" - url: https://github.com/eliotwrobson - - label: LinkedIn - icon: fab fa-fw fa-linkedin - url: https://www.linkedin.com/in/eliot-robson/ -Raktim Mukhopadhyay: - name : "Raktim Mukhopadhyay" - bio : "Ph.D. Candidate, University at Buffalo" - avatar : "/images/people/raktim-mukhopadhyay.jpg" - links: - - label: "GitHub" - icon: "fab fa-fw fa-github" - url: "https://github.com/rmj3197" - - label: "LinkedIn" - icon: "fab fa-fw fa-linkedin" - url: "https://www.linkedin.com/in/raktimm/" -Mandy Moore: - name : "Mandy Moore" - bio : "Mandy Moore is the Communications and Community Lead at pyOpenSci, where she supports open science through inclusive storytelling, strategic content, and community engagement around scientific Python software." - avatar : "/images/people/mandy-moore-headshot.png" - links: - - label: "Email" - icon: "fas fa-fw fa-envelope-square" - url: "mailto:media@pyopensci.org" - - label: "Website" - icon: "fas fa-fw fa-link" - url: "https://www.pyopensci.org" - - label: "GitHub" - icon: "fab fa-fw fa-github" - url: "https://github.com/pyopensci" - - label: "LinkedIn" - icon: "fab fa-fw fa-linkedin" - url: "https://www.linkedin.com/company/pyopensci/" - - label: "Mastodon" - icon: "fab fa-fw fa-mastodon" - url: "https://fosstodon.org/@pyopensci" -Jeremiah Paige: - name : "Jeremiah Paige" - bio : "Python Speaker, Teacher, and Contributor of Open Source" - avatar : "/images/people/jeremiah-paige.jpg" - links: - - label: "GitHub" - icon: "fab fa-fw fa-github" - url: "https://github.com/ucodery" - - label: "Website" - icon: "fas fa-fw fa-link" - url: "https://blog.ucodery.com" - - label: "Mastodon" - icon: "fab fa-fw fa-mastodon" - url: "https://fosstodon.org/@ucodery" diff --git a/_data/blog_topics.yml b/_data/blog_topics.yml deleted file mode 100644 index a44659a8..00000000 --- a/_data/blog_topics.yml +++ /dev/null @@ -1,8 +0,0 @@ -community: - label: "Community" -software: - label: "Software" -education: - label: "Education" -updates: - label: "Updates" diff --git a/_data/navigation.yml b/_data/navigation.yml deleted file mode 100644 index 9f2796e3..00000000 --- a/_data/navigation.yml +++ /dev/null @@ -1,60 +0,0 @@ -# main links -main: - - title: "Peer Review" - url: - sub-nav: - - title: "About peer review" - icon: - url: "/about-peer-review/index.html" - - title: "How to submit a package" - icon: - url: "/how-to-submit-a-package-to-pyopensci.html" - - title: "Community partners" - icon: - url: "/partners.html" - - title: "Accepted packages" - icon: - url: "/python-packages.html" - - title: "Peer review guide" - url: "https://www.pyopensci.org/software-peer-review/" - icon: "fas fa-external-link-alt" - - title: "Packaging" - icon: "fas fa-fw fa-envelope-square" - url: - sub-nav: - - title: "Packaging resources" - icon: - url: "python-packaging-science.html" - - title: "Our packages" - icon: - url: "/python-packages.html" - - title: "Package Guidebook" - url: "https://www.pyopensci.org/python-package-guide/" - icon: "fas fa-external-link-alt" - - title: "Learn" - sub-nav: - - title: For Learners - url: "learn.html" - - title: For Universities & Labs - url: "/learn-universities-labs.html" - - title: Blog - url: "/blog/index.html" - - title: Events - url: "/events.html" - - title: "Community" - sub-nav: - - title: "Our Community" - url: "/our-community/index.html" - icon: "fa-brands fa-discourse" - - title: "Handbook" - icon: "fas fa-external-link-alt" - url: "https://www.pyopensci.org/handbook/" - - title: "Support Us" - sub-nav: - - title: "Ways to Give" - url: "/ways-to-give.html" - - title: "Volunteer" - url: "/volunteer.html" - - title: "Donate" - url: "https://give.communityin.org/pyopensci_2024" - is_button: true diff --git a/_data/ui-text.yml b/_data/ui-text.yml deleted file mode 100644 index 09cec56b..00000000 --- a/_data/ui-text.yml +++ /dev/null @@ -1,1304 +0,0 @@ -# User interface text and labels - -# English (default) -# ----------------- -en: &DEFAULT_EN - page : "Page" - pagination_previous : "Previous" - pagination_next : "Next" - breadcrumb_home_label : "Home" - breadcrumb_separator : "/" - menu_label : "Toggle menu" - search_label : "Toggle search" - toc_label : "On this page" - ext_link_label : "Direct link" - less_than : "less than" - minute_read : "minute read" - share_on_label : "Share on" - meta_label : - tags_label : "Tags:" - categories_label : "Categories:" - date_label : "Updated:" - comments_label : "Leave a comment" - comments_title : "Comments" - more_label : "Learn more" - related_label : "You may also enjoy" - follow_label : "" - feed_label : "Feed" - powered_by : "Powered by" - website_label : "Website" - email_label : "Email" - recent_posts : "Recent posts" - undefined_wpm : "Undefined parameter words_per_minute at _config.yml" - comment_form_info : "Your email address will not be published. Required fields are marked" - comment_form_comment_label : "Comment" - comment_form_md_info : "Markdown is supported." - comment_form_name_label : "Name" - comment_form_email_label : "Email address" - comment_form_website_label : "Website (optional)" - comment_btn_submit : "Submit comment" - comment_btn_submitted : "Submitted" - comment_success_msg : "Thanks for your comment! It will show on the site once it has been approved." - comment_error_msg : "Sorry, there was an error with your submission. Please make sure all required fields have been completed and try again." - loading_label : "Loading..." - search_placeholder_text : "Enter your search term..." - results_found : "Result(s) found" - back_to_top : "Back to top" -en-US: - <<: *DEFAULT_EN -en-CA: - <<: *DEFAULT_EN -en-GB: - <<: *DEFAULT_EN -en-AU: - <<: *DEFAULT_EN - -# Spanish -# ------- -es: &DEFAULT_ES - page : "Página" - pagination_previous : "Anterior" - pagination_next : "Siguiente" - breadcrumb_home_label : "Inicio" - breadcrumb_separator : "/" - menu_label : - search_label : - toc_label : "Contenidos" - ext_link_label : "Enlace" - less_than : "menos de" - minute_read : "minuto de lectura" - share_on_label : "Compartir" - meta_label : - tags_label : "Etiquetas:" - categories_label : "Categorías:" - date_label : "Actualizado:" - comments_label : "Dejar un comentario" - comments_title : "Comentar" - more_label : "Ver más" - related_label : "Podrías ver también" - follow_label : "Seguir:" - feed_label : "Feed" - powered_by : "Powered by" - website_label : "Sitio web" - email_label : "Email" - recent_posts : "Entradas recientes" - undefined_wpm : "Parametro words_per_minute (Palabras por minuto) no definido en _config.yml" - comment_form_info : "Su dirección de correo no será publicada. Se han resaltado los campos requeridos" - comment_form_comment_label : "Comentario" - comment_form_md_info : "Markdown está soportado." - comment_form_name_label : "Nombre" - comment_form_email_label : "Dirección de E-mail" - comment_form_website_label : "Sitio web (opcional)" - comment_btn_submit : "Enviar Commentario" - comment_btn_submitted : "Enviado" - comment_success_msg : "Gracias por su comentario!, Este se visualizará en el sitio una vez haya sido aprobado" - comment_error_msg : "Lo sentimos, ha ocurrido un error al enviar su comentario. Por favor asegurese que todos los campos han sido diligenciados e intente de nuevo" - loading_label : "Cargando..." -es-ES: - <<: *DEFAULT_ES -es-CO: - <<: *DEFAULT_ES - -# French -# ------ -fr: &DEFAULT_FR - page : "Page" - pagination_previous : "Précédent" - pagination_next : "Suivant" - breadcrumb_home_label : "Accueil" - breadcrumb_separator : "/" - menu_label : "Menu" - search_label : - toc_label : "Sur cette page" - ext_link_label : "Lien direct" - less_than : "moins de" - minute_read : "minute(s) de lecture" - share_on_label : "Partager sur" - meta_label : - tags_label : "Tags :" - categories_label : "Catégories :" - date_label : "Mis à jour :" - comments_label : "Laisser un commentaire" - comments_title : "Commentaires" - more_label : "Lire plus" - related_label : "Vous pourriez aimer aussi" - follow_label : "Contact" - feed_label : "Flux" - powered_by : "Propulsé par" - website_label : "Site" - email_label : "Email" - recent_posts : "Posts récents" - undefined_wpm : "Le paramètre words_per_minute n'est pas défini dans _config.yml" - comment_form_info : "Votre adresse email ne sera pas visible. Les champs obligatoires sont marqués" - comment_form_comment_label : "Commentaire" - comment_form_md_info : "Markdown est supporté." - comment_form_name_label : "Nom" - comment_form_email_label : "Adresse mail" - comment_form_website_label : "Site web (optionnel)" - comment_btn_submit : "Envoyer" - comment_btn_submitted : "Envoyé" - comment_success_msg : "Merci pour votre commentaire, il sera visible sur le site une fois approuvé." - comment_error_msg : "Désolé, une erreur est survenue lors de la soumission. Vérifiez que les champs obligatoires ont été remplis et réessayez." - loading_label : "Chargement..." - search_placeholder_text : "Entrez votre recherche..." - results_found : "Aucun résultat trouvé" - back_to_top : "Retour en haut" -fr-FR: - <<: *DEFAULT_FR -fr-BE: - <<: *DEFAULT_FR -fr-CH: - <<: *DEFAULT_FR - -# Turkish -# ------- -tr: &DEFAULT_TR - page : "Sayfa" - pagination_previous : "Önceki" - pagination_next : "Sonraki" - breadcrumb_home_label : "Ana Sayfa" - breadcrumb_separator : "/" - menu_label : - search_label : - toc_label : "İçindekiler" - ext_link_label : "Doğrudan Bağlantı" - less_than : "Şu süreden az: " - minute_read : "dakika tahmini okuma süresi" - share_on_label : "Paylaş" - meta_label : - tags_label : "Etiketler:" - categories_label : "Kategoriler:" - date_label : "Güncelleme tarihi:" - comments_label : "Yorum yapın" - comments_title : "Yorumlar" - more_label : "Daha fazlasını öğrenin" - related_label : "Bunlar ilginizi çekebilir:" - follow_label : "Takip et:" - feed_label : "RSS" - powered_by : "Emeği geçenler: " - website_label : "Web sayfası" - email_label : "E-posta" - recent_posts : "Son yazılar" - undefined_wpm : "_config.yml dosyasında tanımlanmamış words_per_minute parametresi" - comment_form_info : "Email adresiniz gösterilmeyecektir. Zorunlu alanlar işaretlenmiştir" - comment_form_comment_label : "Yorumunuz" - comment_form_md_info : "Markdown desteklenmektedir." - comment_form_name_label : "Adınız" - comment_form_email_label : "Email adresiniz" - comment_form_website_label : "Websiteniz (opsiyonel)" - comment_btn_submit : "Yorum Yap" - comment_btn_submitted : "Gönderildi" - comment_success_msg : "Yorumunuz için teşekkürler! Yorumunuz onaylandıktan sonra sitede gösterilecektir." - comment_error_msg : "Maalesef bir hata oluştu. Lütfen zorunlu olan tüm alanları doldurduğunuzdan emin olun ve sonrasında tekrar deneyin." - loading_label : "Yükleniyor..." -tr-TR: - <<: *DEFAULT_TR - -# Portuguese -# ---------- -pt: &DEFAULT_PT - page : "Página" - pagination_previous : "Anterior" - pagination_next : "Seguinte" - breadcrumb_home_label : "Início" - breadcrumb_separator : "/" - menu_label : - search_label : - toc_label : "Nesta Página" - ext_link_label : "Link Direto" - less_than : "menos de" - minute_read : "minutos de leitura" - share_on_label : "Partilhar no" - meta_label : - tags_label : "Etiquetas:" - categories_label : "Categorias:" - date_label : "Atualizado:" - comments_label : "Deixe um Comentário" - comments_title : "Comentários" - more_label : "Saber mais" - related_label : "Também pode gostar de" - follow_label : "Siga:" - feed_label : "Feed" - powered_by : "Feito com" - website_label : "Site" - email_label : "Email" - recent_posts : "Artigos Recentes" - undefined_wpm : "Parâmetro words_per_minute não definido em _config.yml" - comment_form_info : "O seu endereço email não será publicado. Os campos obrigatórios estão assinalados" - comment_form_comment_label : "Comentário" - comment_form_md_info : "Markdown é suportado." - comment_form_name_label : "Nome" - comment_form_email_label : "Endereço Email" - comment_form_website_label : "Site (opcional)" - comment_btn_submit : "Sumbeter Comentário" - comment_btn_submitted : "Submetido" - comment_success_msg : "Obrigado pelo seu comentário! Será visível no site logo que aprovado." - comment_error_msg : "Lamento, ocorreu um erro na sua submissão. Por favor verifique se todos os campos obrigatórios estão corretamente preenchidos e tente novamente." - loading_label : "A carregar..." -pt-PT: - <<: *DEFAULT_PT -# Brazilian Portuguese -pt-BR: - page : "Página" - pagination_previous : "Anterior" - pagination_next : "Próxima" - breadcrumb_home_label : "Home" - breadcrumb_separator : "/" - menu_label : - search_label : - toc_label : "Nesta página" - ext_link_label : "Link direto" - less_than : "menos que" - minute_read : "minuto(s) de leitura" - share_on_label : "Compartilhe em" - meta_label : - tags_label : "Tags:" - categories_label : "Categorias:" - date_label : "Atualizado em:" - comments_label : "Deixe um comentário" - comments_title : - more_label : "Aprenda mais" - related_label : "Talvez você goste também" - follow_label : "Acompanhe em" - feed_label : "Feed" - powered_by : "Feito com" - website_label : "Site" - email_label : "Email" - recent_posts : "Postagens recentes" - undefined_wpm : "Parâmetro indefinido em words_per_minute no _config.yml" - comment_form_info : "Seu email não será publicado. Os campos obrigatórios estão marcados" - comment_form_comment_label : "Comentário" - comment_form_md_info : "Markdown é suportado." - comment_form_name_label : "Nome" - comment_form_email_label : "Email" - comment_form_website_label : "Site (opcional)" - comment_btn_submit : "Enviar Comentário" - comment_btn_submitted : "Enviado" - comment_success_msg : "Obrigado pelo seu comentário! Ele aparecerá no site assim que for aprovado." - comment_error_msg : "Desculpe, ocorreu um erro no envio. Por favor verifique se todos os campos obrigatórios foram preenchidos e tente novamente." - loading_label : "Carregando..." - -# Italian -# ------- -it: &DEFAULT_IT - page : "Pagina" - pagination_previous : "Precedente" - pagination_next : "Prossima" - breadcrumb_home_label : "Home" - breadcrumb_separator : "/" - menu_label : - search_label : - toc_label : "Indice della pagina" - ext_link_label : "Link" - less_than : "meno di" - minute_read : "minuto/i di lettura" - share_on_label : "Condividi" - meta_label : - tags_label : "Tags:" - categories_label : "Categorie:" - date_label : "Aggiornato:" - comments_label : "Scrivi un commento" - comments_title : - more_label : "Scopri di più" - related_label : "Potrebbe Piacerti Anche" - follow_label : "Segui:" - feed_label : "Feed" - powered_by : "Powered by" - website_label : "Website" - email_label : "Email" - recent_posts : "Articoli Recenti" - undefined_wpm : "Parametro words_per_minute non definito in _config.yml" - comment_form_info : "Il tuo indirizzo email non sarà pubblicato. Sono segnati i campi obbligatori" - comment_form_comment_label : "Commenta" - comment_form_md_info : "Il linguaggio Markdown è supportato" - comment_form_name_label : "Nome" - comment_form_email_label : "Indirizzo email" - comment_form_website_label : "Sito Web (opzionale)" - comment_btn_submit : "Invia commento" - comment_btn_submitted : "Inviato" - comment_success_msg : "Grazie per il tuo commento! Verrà visualizzato nel sito una volta che sarà approvato." - comment_error_msg : "C'è stato un errore con il tuo invio. Assicurati che tutti i campi richiesti siano stati completati e riprova." - loading_label : "Caricamento..." - search_placeholder_text : "Inserisci termini di ricerca..." - results_found : "Risultati" - back_to_top : "Vai su" -it-IT: - <<: *DEFAULT_IT - -# Chinese (zh-CN Chinese - China) -# -------------------------------- -zh: &DEFAULT_ZH_HANS - page : "页面" - pagination_previous : "向前" - pagination_next : "向后" - breadcrumb_home_label : "首页" - breadcrumb_separator : "/" - menu_label : "切换菜单" - search_label : - toc_label : "在本页上" - ext_link_label : "直接链接" - less_than : "少于" - minute_read : "分钟读完" - share_on_label : "分享" - meta_label : - tags_label : "标签:" - categories_label : "分类:" - date_label : "更新时间:" - comments_label : "留下评论" - comments_title : "评论" - more_label : "了解更多" - related_label : "猜您还喜欢" - follow_label : "关注:" - feed_label : "Feed" - powered_by : "技术来自于" - website_label : "网站" - email_label : "电子邮箱" - recent_posts : "最新文章" - undefined_wpm : "_config.yml配置中words_per_minute字段未定义" - comment_form_info : "您的电子邮箱地址并不会被展示。请填写标记为必须的字段。" - comment_form_comment_label : "评论" - comment_form_md_info : "Markdown语法已支持。" - comment_form_name_label : "姓名" - comment_form_email_label : "电子邮箱" - comment_form_website_label : "网站(可选)" - comment_btn_submit : "提交评论" - comment_btn_submitted : "已提交" - comment_success_msg : "感谢您的评论!被批准后它会立即在此站点展示。" - comment_error_msg : "很抱歉,您的提交存在错误。请确保所有必填字段都已填写正确,然后再试一次。" - loading_label : "正在加载..." - search_placeholder_text : "输入您要搜索的关键词..." - results_found : "条记录匹配" - back_to_top : "返回顶部" -zh-CN: - <<: *DEFAULT_ZH_HANS -zh-SG: - <<: *DEFAULT_ZH_HANS -# Taiwan (Traditional Chinese) -zh-TW: &DEFAULT_ZH_HANT - page : "頁面" - pagination_previous : "較舊" - pagination_next : "較新" - breadcrumb_home_label : "首頁" - breadcrumb_separator : "/" - menu_label : "切換選單" - search_label : - toc_label : "本頁" - ext_link_label : "外部連結" - less_than : "少於" - minute_read : "分鐘閱讀" - share_on_label : "分享到" - meta_label : - tags_label : "標籤:" - categories_label : "分類:" - date_label : "更新時間:" - comments_label : "留言" - comments_title : "留言內容" - more_label : "了解更多" - related_label : "猜您有與趣" - follow_label : "追蹤:" - feed_label : "RSS Feed" - powered_by : "Powered by" - website_label : "網站" - email_label : "電子信箱" - recent_posts : "最新文章" - undefined_wpm : "_config.yml 中未定義 words_per_minute" - comment_form_info : "您的電子信箱不會被公開. 必填部份已標記" - comment_form_comment_label : "留言內容" - comment_form_md_info : "支援Markdown語法。" - comment_form_name_label : "名字" - comment_form_email_label : "電子信箱帳號" - comment_form_website_label : "網頁 (可選填)" - comment_btn_submit : "送出留言" - comment_btn_submitted : "已送出" - comment_success_msg : "感謝您的留言! 審核後將會顯示在站上。" - comment_error_msg : "抱歉,部份資料輸入有問題。請確認資料填寫正確後再試一次。" - loading_label : "載入中..." -zh-HK: - <<: *DEFAULT_ZH_HANT - -# German / Deutsch -# ---------------- -de: &DEFAULT_DE - page : "Seite" - pagination_previous : "Vorherige" - pagination_next : "Nächste" - breadcrumb_home_label : "Start" - breadcrumb_separator : "/" - menu_label : "Menü ein-/ausschalten" - search_label : - toc_label : "Auf dieser Seite" - ext_link_label : "Direkter Link" - less_than : "weniger als" - minute_read : "Minuten zum lesen" - share_on_label : "Teilen auf" - meta_label : - tags_label : "Tags:" - categories_label : "Kategorien:" - date_label : "Aktualisiert:" - comments_label : "Hinterlassen Sie einen Kommentar" - comments_title : "Kommentare" - more_label : "Mehr anzeigen" - related_label : "Ihnen gefällt vielleicht auch" - follow_label : "Folgen:" - feed_label : "Feed" - powered_by : "Möglich durch" - website_label : "Webseite" - email_label : "E-Mail" - recent_posts : "Aktuelle Beiträge" - undefined_wpm : "Undefinierter Parameter words_per_minute in _config.yml" - comment_form_info : "Ihre E-Mail Adresse wird nicht veröffentlicht. Benötigte Felder sind markiert" - comment_form_comment_label : "Kommentar" - comment_form_md_info : "Markdown wird unterstützt." - comment_form_name_label : "Name" - comment_form_email_label : "E-Mail-Addresse" - comment_form_website_label : "Webseite (optional)" - comment_btn_submit : "Kommentar absenden" - comment_btn_submitted : "Versendet" - comment_success_msg : "Danke für Ihren Kommentar! Er wird auf der Seite angezeigt, nachdem er geprüft wurde." - comment_error_msg : "Entschuldigung, es gab einen Fehler. Bitte füllen Sie alle benötigten Felder aus und versuchen Sie es erneut." - loading_label : "Lade..." - search_placeholder_text : "Suchbegriff eingeben..." - results_found : "Ergebnis(se) gefunden" -de-DE: - <<: *DEFAULT_DE -de-AT: - <<: *DEFAULT_DE -de-CH: - <<: *DEFAULT_DE -de-BE: - <<: *DEFAULT_DE -de-LI: - <<: *DEFAULT_DE -de-LU: - <<: *DEFAULT_DE - -# Nepali (Nepal) -# -------------- -ne: &DEFAULT_NE - page : "पृष्‍ठ" - pagination_previous : "अघिल्लो" - pagination_next : "अर्को" - breadcrumb_home_label : "गृह" - breadcrumb_separator : "/" - menu_label : "टगल मेनु" - search_label : - toc_label : "यो पृष्‍ठमा" - ext_link_label : "सिधा सम्पर्क" - less_than : "कम्तिमा" - minute_read : "मिनेट पढ्नुहोस्" - share_on_label : "शेयर गर्नुहोस्" - meta_label : - tags_label : "ट्यागहरू:" - categories_label : "वर्गहरु:" - date_label : "अद्यावधिक:" - comments_label : "टिप्पणी दिनुहोस्" - comments_title : "टिप्पणीहरू" - more_label : "अझै सिक्नुहोस्" - related_label : "तपाईं रुचाउन सक्नुहुन्छ " - follow_label : "पछ्याउनुहोस्:" - feed_label : "फिड" - powered_by : "Powered by" - website_label : "वेबसाइट" - email_label : "इमेल" - recent_posts : "ताजा लेखहरु" - undefined_wpm : "अपरिभाषित प्यारामिटर शब्दहरू_प्रति_मिनेट at _config.yml" - comment_form_info : "तपाइँको इमेल ठेगाना प्रकाशित गरिने छैन।आवश्यक जानकारीहरुमा चिन्ह लगाइको छ" - comment_form_comment_label : "टिप्पणी" - comment_form_md_info : "मार्कडाउन समर्थित छ।" - comment_form_name_label : "नाम" - comment_form_email_label : "इमेल ठेगाना" - comment_form_website_label : "वेबसाइट (वैकल्पिक)" - comment_btn_submit : "टिप्पणी दिनुहोस् " - comment_btn_submitted : "टिप्पणी भयो" - comment_success_msg : "तपाईंको टिप्पणीको लागि धन्यवाद! एक पटक यो अनुमोदन गरेपछी यो साइटमा देखाउनेछ।" - comment_error_msg : "माफ गर्नुहोस्, तपाईंको टिप्पणी त्रुटि थियो।सबै आवश्यक जानकारीहरु पूरा गरिएको छ भने निश्चित गर्नुहोस् र फेरि प्रयास गर्नुहोस्।" - loading_label : "लोड हुँदैछ ..." -ne-NP: - <<: *DEFAULT_NE - -# Korean -# ------ -ko: &DEFAULT_KO - page : "페이지" - pagination_previous : "이전" - pagination_next : "다음" - breadcrumb_home_label : "Home" - breadcrumb_separator : "/" - menu_label : "토글 메뉴" - search_label : - toc_label : "On This Page" - ext_link_label : "직접 링크" - less_than : "최대" - minute_read : "분 소요" - share_on_label : "공유하기" - meta_label : - tags_label : "태그:" - categories_label : "카테고리:" - date_label : "업데이트:" - comments_label : "댓글남기기" - comments_title : "댓글" - more_label : "더 보기" - related_label : "참고" - follow_label : "팔로우:" - feed_label : "피드" - powered_by : "Powered by" - website_label : "웹사이트" - email_label : "이메일" - recent_posts : "최근 포스트" - undefined_wpm : "Undefined parameter words_per_minute at _config.yml" - comment_form_info : "이메일은 공개되지 않습니다. 작성 필요 필드:" - comment_form_comment_label : "댓글" - comment_form_md_info : "마크다운을 지원합니다." - comment_form_name_label : "이름" - comment_form_email_label : "이메일" - comment_form_website_label : "웹사이트(선택사항)" - comment_btn_submit : "댓글 등록" - comment_btn_submitted : "등록됨" - comment_success_msg : "감사합니다! 댓글이 머지된 후 확인하실 수 있습니다." - comment_error_msg : "댓글 등록에 문제가 있습니다. 필요 필드를 작성했는지 확인하고 다시 시도하세요." - loading_label : "로딩중..." -ko-KR: - <<: *DEFAULT_KO - -# Russian / Русский -# ----------------- -ru: &DEFAULT_RU - page : "Страница" - pagination_previous : "Предыдущая" - pagination_next : "Следующая" - breadcrumb_home_label : "Главная" - breadcrumb_separator : "/" - menu_label : "Выпадающее меню" - search_label : - toc_label : "Содержание" - ext_link_label : "Прямая ссылка" - less_than : "менее" - minute_read : "мин на чтение" - share_on_label : "Поделиться" - meta_label : - tags_label : "Метки:" - categories_label : "Разделы:" - date_label : "Дата изменения:" - comments_label : "Оставить комментарий" - comments_title : "Комментарии" - more_label : "Читать далее" - related_label : "Вам также может понравиться" - follow_label : "Связаться со мной:" - feed_label : "RSS-лента" - powered_by : "Сайт работает на" - website_label : "Сайт" - email_label : "Электронная почта" - recent_posts : "Свежие записи" - undefined_wpm : "Не определён параметр words_per_minute в _config.yml" - comment_form_info : "Ваш адрес электронной почты не будет опубликован. Обязательные поля помечены" - comment_form_comment_label : "Комментарий" - comment_form_md_info : "Поддерживается синтаксис Markdown." - comment_form_name_label : "Имя" - comment_form_email_label : "Электронная почта" - comment_form_website_label : "Ссылка на сайт (необязательно)" - comment_btn_submit : "Оставить комментарий" - comment_btn_submitted : "Отправлено" - comment_success_msg : "Спасибо за Ваш комментарий! Он будет опубликован на сайте после проверки." - comment_error_msg : "К сожалению, произошла ошибка с отправкой комментария. Пожалуйста, убедитесь, что все обязательные поля заполнены и попытайтесь снова." - loading_label : "Отправка..." - search_placeholder_text : "Введите поисковый запрос..." - results_found : "Найдено" -ru-RU: - <<: *DEFAULT_RU - -# Lithuanian / Lietuviškai -# ------------------------ -lt: &DEFAULT_LT - page : "Puslapis" - pagination_previous : "Ankstesnis" - pagination_next : "Sekantis" - breadcrumb_home_label : "Pagrindinis" - breadcrumb_separator : "/" - menu_label : "Meniu rodymas" - search_label : - toc_label : "Turinys" - ext_link_label : "Tiesioginė nuoroda" - less_than : "mažiau nei" - minute_read : "min. skaitymo" - share_on_label : "Pasidalinti" - meta_label : - tags_label : "Žymės:" - categories_label : "Kategorijos:" - date_label : "Atnaujinta:" - comments_label : "Palikti komentarą" - comments_title : "Komentaras" - more_label : "Skaityti daugiau" - related_label : "Taip pat turėtų patikti" - follow_label : "Sekti:" - feed_label : "Šaltinis" - powered_by : "Sukurta su" - website_label : "Tinklapis" - email_label : "El. paštas" - recent_posts : "Naujausi įrašai" - undefined_wpm : "Nedeklaruotas parametras words_per_minute faile _config.yml" - comment_form_info : "El. pašto adresas nebus viešinamas. Būtini laukai pažymėti." - comment_form_comment_label : "Komentaras" - comment_form_md_info : "Markdown palaikomas." - comment_form_name_label : "Vardas" - comment_form_email_label : "El. paštas" - comment_form_website_label : "Tinklapis (nebūtina)" - comment_btn_submit : "Komentuoti" - comment_btn_submitted : "Įrašytas" - comment_success_msg : "Ačiū už komentarą! Jis bus parodytas kai bus patvirtintas." - comment_error_msg : "Atleiskite, įvyko netikėta klaida įrašant komentarą. Pasitikrinkite ar užpildėte visus būtinus laukus ir pamėginkite dar kartą." - loading_label : "Kraunama..." -lt-LT: - <<: *DEFAULT_LT - -# Greek -# ----- -gr: &DEFAULT_GR - page : "Σελίδα" - pagination_previous : "Προηγούμενo" - pagination_next : "Επόμενo" - breadcrumb_home_label : "Αρχική" - breadcrumb_separator : "/" - menu_label : "Μενού" - search_label : - toc_label : "Περιεχόμενα" - ext_link_label : "Εξωτερικός Σύνδεσμος" - less_than : "Λιγότερο από" - minute_read : "λεπτά ανάγνωσης" - share_on_label : "Μοιραστείτε το" - meta_label : - tags_label : "Ετικέτες:" - categories_label : "Κατηγορίες:" - date_label : "Ενημερώθηκε:" - comments_label : "Αφήστε ένα σχόλιο" - comments_title : "Σχόλια" - more_label : "Διάβαστε περισσότερα" - related_label : "Σχετικές αναρτήσεις" - follow_label : "Ακολουθήστε:" - feed_label : "RSS Feed" - powered_by : "Δημιουργήθηκε με" - website_label : "Ιστοσελίδα" - email_label : "Email" - recent_posts : "Τελευταίες αναρτήσεις" - undefined_wpm : "Δεν έχει οριστεί η παράμετρος words_per_minute στο αρχείο _config.yml" - comment_form_info : "Η διεύθυνση email σας δεν θα δημοσιευθεί. Τα απαιτούμενα πεδία εμφανίζονται με αστερίσκο" - comment_form_comment_label : "Σχόλιο" - comment_form_md_info : "Το πεδίο υποστηρίζει Markdown." - comment_form_name_label : "Όνομα" - comment_form_email_label : "Διεύθυνση email" - comment_form_website_label : "Ιστοσελίδα (προαιρετικό)" - comment_btn_submit : "Υπόβαλε ένα σχόλιο" - comment_btn_submitted : "Έχει υποβληθεί" - comment_success_msg : "Ευχαριστούμε για το σχόλιό σας! Θα εμφανιστεί στην ιστοσελίδα αφού εγκριθεί." - comment_error_msg : "Λυπούμαστε, παρουσιάστηκε σφάλμα με την υποβολή σας. Παρακαλούμε βεβαιωθείτε ότι έχετε όλα τα απαιτούμενα πεδία συμπληρωμένα και δοκιμάστε ξανά." - loading_label : "Φόρτωση..." - search_placeholder_text : "Εισάγετε όρο αναζήτησης..." - results_found : "Αποτελέσματα" -gr-GR: - <<: *DEFAULT_GR - -# Swedish -# ------- -sv: &DEFAULT_SV - page : "Sidan" - pagination_previous : "Föregående" - pagination_next : "Nästa" - breadcrumb_home_label : "Hem" - breadcrumb_separator : "/" - menu_label : "Växla menyläge" - search_label : "Växla sökläge" - toc_label : "På denna sida" - ext_link_label : "Direkt länk" - less_than : "mindre än" - minute_read : "minut läsning" - share_on_label : "Dela på" - meta_label : - tags_label : "Taggar:" - categories_label : "Kategorier:" - date_label : "Uppdaterades:" - comments_label : "Lämna en kommentar" - comments_title : "Kommentarer" - more_label : "Lär dig mer" - related_label : "Du kanske vill även läsa:" - follow_label : "Följ:" - feed_label : "Flöde" - powered_by : "Framställd med" - website_label : "Webbsida" - email_label : "E-post" - recent_posts : "Senaste inlägg" - undefined_wpm : "Odefinerade parametrar words_per_minute i _config.yml" - comment_form_info : "Din e-post adress kommer inte att publiceras. Obligatoriska fält är markerade." - comment_form_comment_label : "Kommentar" - comment_form_md_info : "Stöd för Markdown finns." - comment_form_name_label : "Namn" - comment_form_email_label : "E-post adress" - comment_form_website_label : "Webdsida (valfritt)" - comment_btn_submit : "Skicka en kommentar" - comment_btn_submitted : "Kommentaren har tagits emot" - comment_success_msg : "Tack för din kommentar! Den kommer att visas på sidan så fort den har godkännts." - comment_error_msg : "Tyvärr det har blivit något fel i ett av fälten, se till att du fyllt i alla obligatoriska fält och försök igen." - loading_label : "Laddar..." - search_placeholder_text : "Fyll i sökterm..." - results_found : "Resultat funna" - back_to_top : "Tillbaka till toppen" -sv-SE: - <<: *DEFAULT_SV -sv-FI: - <<: *DEFAULT_SV - -# Dutch -# ----- -nl: &DEFAULT_NL - page : "Pagina" - pagination_previous : "Vorige" - pagination_next : "Volgende" - breadcrumb_home_label : "Home" - breadcrumb_separator : "/" - menu_label : "Wissel Menu" - search_label : - toc_label : "Op deze pagina" - ext_link_label : "Directe Link" - less_than : "minder dan" - minute_read : "minuut gelezen" - share_on_label : "Deel op" - meta_label : - tags_label : "Labels:" - categories_label : "Categorieën:" - date_label : "Bijgewerkt:" - comments_label : "Laat een reactie achter" - comments_title : "Commentaren" - more_label : "Meer informatie" - related_label : "Bekijk ook eens" - follow_label : "Volg:" - feed_label : "Feed" - powered_by : "Aangedreven door" - website_label : "Website" - email_label : "Email" - recent_posts : "Recente berichten" - undefined_wpm : "Niet gedefinieerde parameter words_per_minute bij _config.yml" - comment_form_info : "Uw e-mailadres wordt niet gepubliceerd. Verplichte velden zijn gemarkeerd" - comment_form_comment_label : "Commentaar" - comment_form_md_info : "Markdown wordt ondersteund." - comment_form_name_label : "Naam" - comment_form_email_label : "E-mailadres" - comment_form_website_label : "Website (optioneel)" - comment_btn_submit : "Commentaar toevoegen" - comment_btn_submitted : "Toegevoegd" - comment_success_msg : "Bedankt voor uw reactie! Het zal op de site worden weergegeven zodra het is goedgekeurd." - comment_error_msg : "Sorry, er is een fout opgetreden bij uw inzending. Zorg ervoor dat alle vereiste velden zijn voltooid en probeer het opnieuw." - loading_label : "Laden..." -nl-BE: - <<: *DEFAULT_NL -nl-NL: - <<: *DEFAULT_NL - -# Indonesian -# ---------- -id: &DEFAULT_ID - page : "Halaman" - pagination_previous : "Kembali" - pagination_next : "Maju" - breadcrumb_home_label : "Home" - breadcrumb_separator : "/" - menu_label : "Menu Toggle" - search_label : - toc_label : "Pada Halaman Ini" - ext_link_label : "Link langsung" - less_than : "Kurang dari" - minute_read : "Waktu baca" - share_on_label : "Berbagi di" - meta_label : - tags_label : "Golongan:" - categories_label : "Kategori:" - date_label : "Diupdate:" - comments_label : "Tinggalkan komentar" - comments_title : "Komentar" - more_label : "Pelajari lagi" - related_label : "Anda juga akan suka" - follow_label : "Ikuti:" - feed_label : "Feed" - powered_by : "Didukung oleh" - website_label : "Website" - email_label : "Email" - recent_posts : "Posting terbaru" - undefined_wpm : "Parameter terdeskripsi words_per_minute di _config.yml" - comment_form_info : "Email Anda tidak akan dipublish. Kolom yang diperlukan ditandai" - comment_form_comment_label : "Komentar" - comment_form_md_info : "Markdown disupport." - comment_form_name_label : "Nama" - comment_form_email_label : "Alamat email" - comment_form_website_label : "Website (opsional)" - comment_btn_submit : "Submit Komentar" - comment_btn_submitted : "Telah disubmit" - comment_success_msg : "Terimakasih atas komentar Anda! Komentar ini akan tampil setelah disetujui." - comment_error_msg : "Maaf, ada kesalahan pada submisi Anda. Pastikan seluruh kolom sudah dilengkapi dan coba kembali." - loading_label : "Sedang meload..." -id-ID: - <<: *DEFAULT_ID - -# Vietnamese -# ---------- -vi: &DEFAULT_VI - page : "Trang" - pagination_previous : "Trước" - pagination_next : "Sau" - breadcrumb_home_label : "Trang chủ" - breadcrumb_separator : "/" - menu_label : "Menu" - search_label : - toc_label : "Tại trang này" - ext_link_label : "Đường dẫn trực tiếp" - less_than : "nhỏ hơn" - minute_read : "phút đọc" - share_on_label : "Chia sẻ tại" - meta_label : - tags_label : "Nhãn:" - categories_label : "Chủ đề:" - date_label : "Cập nhật:" - comments_label : "Để lại bình luận" - comments_title : "Bình luận" - more_label : "Mở rộng" - related_label : "Có thể bạn cũng thích" - follow_label : "Theo dõi:" - feed_label : "Feed" - powered_by : "Được hỗ trợ bởi" - website_label : "Website" - email_label : "Email" - recent_posts : "Bài viết mới" - undefined_wpm : "Chưa định nghĩa thông số words_per_minute tại _config.yml" - comment_form_info : "Email của bạn sẽ được giữ bí mật. Các phần bắt buộc được đánh dấu." - comment_form_comment_label : "Bình luận" - comment_form_md_info : "Hỗ trợ Markdown." - comment_form_name_label : "Tên" - comment_form_email_label : "Địa chỉ email" - comment_form_website_label : "Website (không bắt buộc)" - comment_btn_submit : "Gửi bình luận" - comment_btn_submitted : "Đã được gửi" - comment_success_msg : "Cảm ơn bạn đã bình luận! Bình luận sẽ xuất hiện sau khi được duyệt." - comment_error_msg : "Rất tiếc, có lỗi trong việc gửi bình luận. Hãy đảm bảo toàn bộ các phần bắt buộc đã được điền đầy đủ và thử lại." - loading_label : "Đang tải..." -vi-VN: - <<: *DEFAULT_VI - -# Danish -# ------ -da: &DEFAULT_DA - page : "Side" - pagination_previous : "Forrige" - pagination_next : "Næste" - breadcrumb_home_label : "Home" - breadcrumb_separator : "/" - menu_label : "Vis/skjul menu" - search_label : - toc_label : "På denne side" - ext_link_label : "Direkte link" - less_than : "mindre end" - minute_read : "minutters læsning" - share_on_label : "Del på" - meta_label : - tags_label : "Nøgleord:" - categories_label : "Kategorier:" - date_label : "Opdateret:" - comments_label : "Skriv en kommentar" - comments_title : "Kommentarer" - more_label : "Lær mere" - related_label : "Måske kan du også lide" - follow_label : "Følg:" - feed_label : "Feed" - powered_by : "Drives af" - website_label : "Website" - email_label : "E-mail" - recent_posts : "Seneste indlæg" - undefined_wpm : "Parameteren words_per_minute er ikke defineret i _config.yml" - comment_form_info : "Din e-mail bliver ikke offentliggjort. Obligatoriske felter er markeret" - comment_form_comment_label : "Kommentar" - comment_form_md_info : "Markdown er understøttet." - comment_form_name_label : "Navn" - comment_form_email_label : "E-mail" - comment_form_website_label : "Website (frivillig)" - comment_btn_submit : "Send kommentar" - comment_btn_submitted : "Sendt" - comment_success_msg : "Tak for din kommentar! Den bliver vist på siden, så snart den er godkendt." - comment_error_msg : "Desværre skete der en fejl. Prøv igen, mens du sørger for at alle obligatoriske felter er udfyldt." - loading_label : "Indlæser..." - search_placeholder_text : "Hvad leder du efter..." - results_found : "Resultat(er) fundet" - back_to_top : "Tilbage til toppen" -da-DK: - <<: *DEFAULT_DA - -# Polish -# ------ -pl: &DEFAULT_PL - page : "Strona" - pagination_previous : "Poprzednia" - pagination_next : "Następna" - breadcrumb_home_label : "Strona główna" - breadcrumb_separator : "/" - menu_label : "Przełącz menu" - search_label : - toc_label : "Spis treści" - ext_link_label : "Link bezpośredni" - less_than : "mniej niż" - minute_read : "minut(y)" - share_on_label : "Udostępnij" - meta_label : - tags_label : "Tagi:" - categories_label : "Kategorie:" - date_label : "Ostatnia aktualizacja:" - comments_label : "Zostaw komentarz" - comments_title : "Komentarze" - more_label : "Dowiedz się więcej" - related_label : "Także może Ci się spodobać" - follow_label : "Śledź:" - feed_label : "Feed" - powered_by : "Powstało dzięki" - website_label : "Strona" - email_label : "Email" - recent_posts : "Najnowsze wpisy" - undefined_wpm : "Parametr words_per_minute nie został zdefiniowany w _config.yml." - comment_form_info : "Twój adres email nie będzie udostępiony. Wymagane pola są oznaczone." - comment_form_comment_label : "Skomentuj" - comment_form_md_info : "Markdown jest wspierany" - comment_form_name_label : "Imię" - comment_form_email_label : "Adres email" - comment_form_website_label : "Strona www (opcjonalna)" - comment_btn_submit : "Skomentuj" - comment_btn_submitted : "Komentarz dodany" - comment_success_msg : "Dziękuję za Twój komentarz! Zostanie dodany po akceptacji." - comment_error_msg : "Niestety wystąpił błąd. Proszę upewnij się, że wszystkie wymagane pola zostały wypełnione i spróbuj ponownie." - loading_label : "Trwa ładowanie strony..." -pl-PL: - <<: *DEFAULT_PL - -# Japanese -# -------- -ja: &DEFAULT_JA - page : "ページ" - pagination_previous : "前へ" - pagination_next : "次へ" - breadcrumb_home_label : "ホーム" - breadcrumb_separator : "/" - menu_label : "メニュー" - search_label : - toc_label : "目次" - ext_link_label : "リンク" - less_than : - minute_read : - share_on_label : "共有" - meta_label : - tags_label : "タグ:" - categories_label : "カテゴリー:" - date_label : "更新日時:" - comments_label : "コメントする" - comments_title : "コメント" - more_label : "さらに詳しく" - related_label : "関連記事" - follow_label : "フォロー" - feed_label : - powered_by : - website_label : - email_label : - recent_posts : "最近の投稿" - undefined_wpm : "パラメータ words_per_minute が _config.yml で定義されていません" - comment_form_info : "メールアドレスが公開されることはありません。次の印のある項目は必ず入力してください:" - comment_form_comment_label : "コメント" - comment_form_md_info : "Markdown を使用できます" - comment_form_name_label : "名前" - comment_form_email_label : "メールアドレス" - comment_form_website_label : "URL (任意)" - comment_btn_submit : "コメントを送信する" - comment_btn_submitted : "送信しました" - comment_success_msg : "コメントありがとうございます! コメントは承認されるとページに表示されます。" - comment_error_msg : "送信エラーです。必須項目がすべて入力されていることを確認して再送信してください。" - loading_label : "読み込み中..." - search_placeholder_text : "検索キーワードを入力してください..." - results_found : "件" -ja-JP: - <<: *DEFAULT_JA - -# Slovak -# ----------------- -sk: &DEFAULT_SK - page : "Stránka" - pagination_previous : "Predošlá" - pagination_next : "Ďalšia" - breadcrumb_home_label : "Domov" - breadcrumb_separator : "/" - menu_label : "Menu" - search_label : - toc_label : "Obsah" - ext_link_label : "Priamy odkaz" - less_than : "menej ako" - minute_read : "minút" - share_on_label : "Zdieľaj na" - meta_label : - tags_label : "Tagy:" - categories_label : "Kategórie:" - date_label : "Aktualizované:" - comments_label : "Zanechaj odkaz" - comments_title : "Komentáre" - more_label : "Dozvedieť sa viac" - related_label : "Podobné články" - follow_label : "Sleduj:" - feed_label : "Zoznam" - powered_by : "Stránka vytvorená pomocou" - website_label : "Web stránka" - email_label : "Email" - recent_posts : "Najnovšie príspevky" - undefined_wpm : "Nedefinovaný parameter words_per_minute v _config.yml" - comment_form_info : "Tvoja emailová adresa nebude publikovaná. Požadované polia sú označené" - comment_form_comment_label : "Komentár" - comment_form_md_info : "Markdown je podporovaný." - comment_form_name_label : "Meno" - comment_form_email_label : "Emailová adresa" - comment_form_website_label : "Webstránka (voliteľné)" - comment_btn_submit : "Vlož komentár" - comment_btn_submitted : "Vložený" - comment_success_msg : "Ďakujem za tvoj komentár! Po schválení bude zobrazený na stránke." - comment_error_msg : "Prepáč, pri ukladaní nastala chyba. Ubezpeč sa prosím, že si vyplnil všetky požadované polia a skús znova." - loading_label : "Načítava sa..." - search_placeholder_text : "Zadaj hľadaný výraz..." - results_found : "Nájdených výsledkov" - back_to_top : "Na začiatok stránky" -sk-SK: - <<: *DEFAULT_SK - -# Hungarian -# ----------------- -hu: &DEFAULT_HU - page : "Oldal" - pagination_previous : "Előző" - pagination_next : "Következő" - breadcrumb_home_label : "Kezdőlap" - breadcrumb_separator : "/" - menu_label : "Menü nyit/zár" - search_label : - toc_label : "Ezen az oldalon" - ext_link_label : "Közvetlen Link" - less_than : "kevesebb mint" - minute_read : "eltöltött percek" - share_on_label : "Megosztás" - meta_label : - tags_label : "Tagek:" - categories_label : "Kategóriák:" - date_label : "Frissítve:" - comments_label : "Szólj hozzá!" - comments_title : "Hozzászólások" - more_label : "Tovább" - related_label : "Ajánlások" - follow_label : "Követés:" - feed_label : "Folyam" - powered_by : "Powered by" - website_label : "Honlap" - email_label : "Email" - recent_posts : "Friss cikkek" - undefined_wpm : "Ismeretlen paraméter words_per_minute : _config.yml" - comment_form_info : "Az e-mail címed nem lesz publikus. A csillagozott mezők kitöltése kötelező." - comment_form_comment_label : "Hozzászólás" - comment_form_md_info : "Támogatott formázási mód: Markdown" - comment_form_name_label : "Név" - comment_form_email_label : "Email cím" - comment_form_website_label : "Honlap (nem kötelező):" - comment_btn_submit : "Hozzászólás elküldése" - comment_btn_submitted : "Hozzászólás elküldve" - comment_success_msg : "Köszönjük a Hozzászólást! A Hozzászólások csak előzetes moderáció után lesznek publikusak." - comment_error_msg : "Hoppá, hiba történt a beküldés közben. Kérlek ellenőrizd hogy minden kötelező mező ki van-e töltve." - loading_label : "Betöltés..." - search_placeholder_text : "Keresendő szöveg..." - results_found : "Találatok:" - back_to_top : "Oldal tetejére" -hu-HU: - <<: *DEFAULT_HU - -# Romanian -# ----------------- -ro: &DEFAULT_RO - page : "Pagina" - pagination_previous : "Anterior" - pagination_next : "Următor" - breadcrumb_home_label : "Acasă" - breadcrumb_separator : "/" - menu_label : "Comută meniul" - search_label : - toc_label : "Pe această pagină" - ext_link_label : "Link direct" - less_than : "mai puțin de" - minute_read : "minute de citit" - share_on_label : "Distribuie pe" - meta_label : - tags_label : "Etichete:" - categories_label : "Categorii:" - date_label : "Actualizat:" - comments_label : "Lasă un comentariu" - comments_title : "Comentarii" - more_label : "Citește mai departe" - related_label : "S-ar putea să-ți placă" - follow_label : "Urmărește:" - feed_label : "Feed RSS" - powered_by : "Cu sprijinul" - website_label : "Site" - email_label : "Email" - recent_posts : "Articole recente" - undefined_wpm : "Parametru words_per_minute nedefinit în _config.yml" - comment_form_info : "Adresa ta de email nu va fi făcută publică. Câmpurile marcate sunt obligatorii" - comment_form_comment_label : "Comentariu" - comment_form_md_info : "Markdown este suportat." - comment_form_name_label : "Nume" - comment_form_email_label : "Adresă de email" - comment_form_website_label : "Site (opțional)" - comment_btn_submit : "Trimite comentariul" - comment_btn_submitted : "Trimis" - comment_success_msg : "Mulțumesc pentru comentariu! Va apărea pe site în momentul în care va fi aprobat." - comment_error_msg : "Scuze, este o problemă cu comentariul tău. Asigură-te că toate câmpurile obligatorii au fost completate și încearcă din nou." - loading_label : "Se încarcă..." - search_placeholder_text : "Caută ceva..." - results_found : "Rezultate găsite" - back_to_top : "Înapoi în susul paginii" -ro-RO: - <<: *DEFAULT_RO - -# Punjabi -# ----------------- -pa: &DEFAULT_PA - page : "ਸਫ਼ਾ" - pagination_previous : "ਪਿਛਲਾ" - pagination_next : "ਅਗਲਾ " - breadcrumb_home_label : "ਘਰ" - breadcrumb_separator : "/" - menu_label : "ਟੌਗਲ ਮੀਨੂ" - search_label : - toc_label : "ਇਸ ਸਫ਼ੇ 'ਤੇ" - ext_link_label : "ਸਿੱਧਾ ਸੰਪਰਕ" - less_than : "ਤੋਂ ਘੱਟ" - minute_read : "ਮਿੰਟ ਵਿੱਚ ਪੜਿਆ ਜਾ ਸਕਦਾ ਹੈ" - share_on_label : "ਸਾਂਝਾ ਕਰੋ" - meta_label : - tags_label : "ਟੈਗ" - categories_label : "ਵਰਗ" - date_label : "ਅਪਡੇਟ ਕੀਤਾ:" - comments_label : "ਇੱਕ ਟਿੱਪਣੀ ਛੱਡੋ" - comments_title : "ਟਿੱਪਣੀਆਂ" - more_label : "ਹੋਰ ਜਾਣੋ" - related_label : "ਤੁਸੀਂ ਇਸਦਾ ਆਨੰਦ ਵੀ ਲੈ ਸਕਦੇ ਹੋ" - follow_label : "ਫਾਲੋ ਅੱਪ ਕਰੋ:" - feed_label : "ਫੀਡ" - powered_by : "ਦੁਆਰਾ ਸੰਚਾਲਿਤ" - website_label : "ਵੈੱਬਸਾਇਟ" - email_label : "ਈਮੇਲ" - recent_posts : "ਹਾਲ ਹੀ ਦੇ ਪੋਸਟ" - undefined_wpm : "_config.yml ਤੇ ਅਣ-ਪ੍ਰਭਾਸ਼ਿਤ ਪੈਰਾਮੀਟਰ words_per_minute" - comment_form_info : "ਤੁਹਾਡਾ ਈਮੇਲ ਪਤਾ ਪ੍ਰਕਾਸ਼ਿਤ ਨਹੀਂ ਕੀਤਾ ਜਾਵੇਗਾ। ਅਨੁਮਾਨਿਤ ਸਥਾਨਾਂ ਨੂੰ ਅੰਡਰਲਾਈਨ ਕੀਤਾ ਗਿਆ ਹੈ" - comment_form_comment_label : "ਟਿੱਪਣੀ" - comment_form_md_info : "ਮਾਰਕਡਾਊਨ ਵਰਤ ਸਕਦੇ ਹੋ।" - comment_form_name_label : "ਨਾਮ" - comment_form_email_label : "ਈਮੇਲ ਪਤਾ" - comment_form_website_label : "ਵੈਬਸਾਈਟ (ਵਿਕਲਪਿਕ)" - comment_btn_submit : "ਕੋਈ ਟਿੱਪਣੀ ਭੇਜੋ" - comment_btn_submitted : "ਪੇਸ਼ ਕੀਤਾ" - comment_success_msg : "ਤੁਹਾਡੀਆਂ ਟਿੱਪਣੀਆਂ ਲਈ ਧੰਨਵਾਦ! ਇਹ ਮਨਜ਼ੂਰੀ ਮਿਲਣ ਦੇ ਬਾਅਦ ਸਾਈਟ 'ਤੇ ਦਿਖਾਇਆ ਜਾਵੇਗਾ।" - comment_error_msg : "ਮੁਆਫ ਕਰਨਾ, ਤੁਹਾਡੀ ਅਧੀਨਗੀ ਵਿੱਚ ਕੋਈ ਗਲਤੀ ਹੋਈ ਸੀ ਕਿਰਪਾ ਕਰਕੇ ਯਕੀਨੀ ਬਣਾਓ ਕਿ ਸਾਰੇ ਲੋੜੀਂਦੇ ਖੇਤਰ ਪੂਰੇ ਹੋ ਗਏ ਹਨ ਅਤੇ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।" - loading_label : "ਲੋਡ ਹੋ ਰਿਹਾ ਹੈ..." - search_placeholder_text : "ਆਪਣੀ ਖੋਜ ਦੇ ਸ਼ਬਦ ਨੂੰ ਦਰਜ ਕਰੋ..." - results_found : "ਨਤੀਜਾ ਮਿਲਿਆ/ਮਿਲੇ" - back_to_top : "ਵਾਪਸ ਚੋਟੀ 'ਤੇ ਜਾਓ" -pa-IN: - <<: *DEFAULT_PA - -# Persian (Farsi) -# -------------- -fa: &DEFAULT_FA - page : "صفحه" - pagination_previous : "قبلی" - pagination_next : "بعدی" - breadcrumb_home_label : "صفحه اصلی" - breadcrumb_separator : "/" - menu_label : "فهرست" - toc_label : "در این صفحه" - ext_link_label : "لینک مستقیم" - less_than : " " - minute_read : "(طول مطالعه (دقیقه" - share_on_label : "اشتراک گذاری در" - meta_label : - tags_label : "تگ ها: " - categories_label : "دسته بندی ها: " - date_label : "به روز شده در: " - comments_label : "ارسال نظر" - comments_title : "نظرات" - more_label : "ادامه مطلب" - related_label : "ممکن است از این مطالب نیز لذت ببرید" - follow_label : "دنبال کنید: " - feed_label : "خوراک" - powered_by : "طراحی شده توسط" - website_label : "سایت اینترنتی" - email_label : "پست الکترونیک" - recent_posts : "آخرین مطالب" - undefined_wpm : ".(words_per_minute) _config.yml متغیر اشتباه در" - comment_form_info : ".آدرس ایمیل شما منتشر نخواهد شد. فیلدهای اجباری مشخص شده اند" - comment_form_comment_label : "دیدگاه" - comment_form_md_info : ".پشتیبانی می شود Markdown" - comment_form_name_label : "نام" - comment_form_email_label : "پست الکترونیک" - comment_form_website_label : "سایت اینترنتی (اختیاری)" - comment_btn_submit : "ارسال نظر" - comment_btn_submitted : "ارسال شد" - comment_success_msg : ".باتشکر از ارسال دیدگاه! پس از تأیید، این دیدگاه در سایت نشان داده خواهد شد" - comment_error_msg : ".متاسفانه در ارسال شما خطایی بود. لطفا مطمئن شوید تمام فیلدهای مورد نیاز تکمیل شده و دوباره امتحان کنید" - loading_label : "...بارگذاری" - search_placeholder_text : "...عبارت جستجوی خود را وارد کنید" - results_found : "نتایج" - back_to_top : "بازگشت به بالا" -fa-IR: - <<: *DEFAULT_FA - - -# Malayalam -# ----------------- -ml: &DEFAULT_ML - page : "പേജ്" - pagination_previous : "തിരികെ" - pagination_next : "മുന്നോട്ട്" - breadcrumb_home_label : "ഹോം" - breadcrumb_separator : "/" - menu_label : "ടോഗിൾ മെനു" - search_label : "ടോഗിൾ സെർച്ച്" - toc_label : "ഈ പേജിൽ" - ext_link_label : "ലിങ്കിലേക് പോകാൻ" - less_than : "ഏതാണ്ട്" - minute_read : "മിനിറ്റ് ദൈർഖ്യം" - share_on_label : "ഷെയർ ചെയ്യുവാൻ " - meta_label : - tags_label : "ടാഗുകൾ:" - categories_label : "വിഭാഗങ്ങൾ:" - date_label : "അവസാന മാറ്റം:" - comments_label : "അഭിപ്രായം രേഖപ്പെടുത്തുക" - comments_title : "അഭിപ്രായങ്ങൾ" - more_label : "കൂടുതൽ അറിയുവാൻ" - related_label : "നിങ്ങൾക് ഇതും ഇഷ്ടപ്പെട്ടേക്കാം" - follow_label : "പിന്തുടരുക:" - feed_label : "ഫീഡ്" - powered_by : "പവേർഡ് ബൈ" - website_label : "വെബ്സൈറ്റ്" - email_label : "ഇ-മെയിൽ" - recent_posts : "സമീപകാല പോസ്റ്റുകൾ" - undefined_wpm : "Config.yml ലെ words_per_minute പരാമീറ്റർ നിർവചിച്ചിട്ടില്ല." - comment_form_info : "നിങ്ങളുടെ ഇമെയിൽ വിലാസം പ്രസിദ്ധീകരിക്കില്ല. ആവശ്യമായ ഫീൽഡുകൾ അടയാളപ്പെടുത്തി." - comment_form_comment_label : "കമന്റ്" - comment_form_md_info : "Markdown സപ്പോർട്ട് ചെയ്യുന്നതാണ്." - comment_form_name_label : "പേര്" - comment_form_email_label : "ഇ-മെയിൽ" - comment_form_website_label : "വെബ്സൈറ് (ഓപ്ഷണൽ)" - comment_btn_submit : "അഭിപ്രായം രേഖപ്പെടുത്തുക" - comment_btn_submitted : "രേഖപ്പെടുത്തി" - comment_success_msg : "നിങ്ങളുടെ അഭിപ്രായത്തിന് നന്ദി! ഇത് അംഗീകരിച്ചുകഴിഞ്ഞാൽ ഇത് സൈറ്റിൽ പ്രദർശിപ്പിക്കും." - comment_error_msg : "ക്ഷമിക്കണം, നിങ്ങളുടെ സമർപ്പണവുമായി ബന്ധപ്പെട്ട് ഒരു പിശകുണ്ടായിരുന്നു. ആവശ്യമായ എല്ലാ ഫീൽഡുകളും പൂർത്തിയായിട്ടുണ്ടെന്ന് ഉറപ്പുവരുത്തുക, വീണ്ടും ശ്രമിക്കുക." - loading_label : "ലോഡിംഗ്..." - search_placeholder_text : "നിങ്ങളുടെ തിരയൽ പദം നൽകുക..." - results_found : "ഫലം (കൾ) കണ്ടെത്തി" - back_to_top : "മുകളിലേയ്ക്ക്" -ml-IN: - <<: *DEFAULT_ML - - -# Another locale -# -------------- -# diff --git a/_includes/analytics-providers/custom.html b/_includes/analytics-providers/custom.html deleted file mode 100644 index 58448f77..00000000 --- a/_includes/analytics-providers/custom.html +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/_includes/analytics-providers/google-gtag.html b/_includes/analytics-providers/google-gtag.html deleted file mode 100644 index 16d0cf17..00000000 --- a/_includes/analytics-providers/google-gtag.html +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/_includes/analytics-providers/google-universal.html b/_includes/analytics-providers/google-universal.html deleted file mode 100644 index 68c2674b..00000000 --- a/_includes/analytics-providers/google-universal.html +++ /dev/null @@ -1,7 +0,0 @@ - - diff --git a/_includes/analytics-providers/google.html b/_includes/analytics-providers/google.html deleted file mode 100644 index c5742b98..00000000 --- a/_includes/analytics-providers/google.html +++ /dev/null @@ -1,14 +0,0 @@ - diff --git a/_includes/analytics.html b/_includes/analytics.html deleted file mode 100644 index 5c852361..00000000 --- a/_includes/analytics.html +++ /dev/null @@ -1,14 +0,0 @@ -{% if jekyll.environment == 'production' and site.analytics.provider and page.analytics != false %} - -{% case site.analytics.provider %} -{% when "google" %} - {% include /analytics-providers/google.html %} -{% when "google-universal" %} - {% include /analytics-providers/google-universal.html %} -{% when "google-gtag" %} - {% include /analytics-providers/google-gtag.html %} -{% when "custom" %} - {% include /analytics-providers/custom.html %} -{% endcase %} - -{% endif %} diff --git a/_includes/archive-cards.html b/_includes/archive-cards.html deleted file mode 100644 index 96c83aeb..00000000 --- a/_includes/archive-cards.html +++ /dev/null @@ -1,27 +0,0 @@ - diff --git a/_includes/archive-single.html b/_includes/archive-single.html deleted file mode 100644 index 1a66146d..00000000 --- a/_includes/archive-single.html +++ /dev/null @@ -1,30 +0,0 @@ -{% if post.header.teaser %} - {% capture teaser %}{{ post.header.teaser }}{% endcapture %} -{% else %} - {% assign teaser = site.teaser %} -{% endif %} - -{% if post.id %} - {% assign title = post.title | markdownify | remove: "

" | remove: "

" %} -{% else %} - {% assign title = post.title %} -{% endif %} - -
-
- {% if include.type == "grid" and teaser %} -
- -
- {% endif %} -

- {% if post.link %} - {{ title }} Permalink - {% else %} - {{ title }} - {% endif %} -

- {% include page__meta.html type=include.type %} - {% if post.excerpt %}

{{ post.excerpt | markdownify | strip_html | truncate: 160 }}

{% endif %} -
-
diff --git a/_includes/author-profile-custom-links.html b/_includes/author-profile-custom-links.html deleted file mode 100644 index 081442e7..00000000 --- a/_includes/author-profile-custom-links.html +++ /dev/null @@ -1,7 +0,0 @@ - diff --git a/_includes/author-profile.html b/_includes/author-profile.html deleted file mode 100644 index 64efd406..00000000 --- a/_includes/author-profile.html +++ /dev/null @@ -1,206 +0,0 @@ -{% assign author = page.author | default: page.authors[0] | default: site.author %} -{% assign author = site.data.authors[author] | default: author %} - -
- - {% if author.avatar %} -
- - {{ author.name }} - -
- {% endif %} - -
-

- -

- {% if author.bio %} -
- {{ author.bio | markdownify }} -
- {% endif %} -
- -
- - -
-
diff --git a/_includes/base_path b/_includes/base_path deleted file mode 100644 index 0b5ac0b1..00000000 --- a/_includes/base_path +++ /dev/null @@ -1,5 +0,0 @@ -{% if site.url %} - {% assign base_path = site.url | append: site.baseurl %} -{% else %} - {% assign base_path = site.github.url %} -{% endif %} diff --git a/_includes/blog-archive-card.html b/_includes/blog-archive-card.html deleted file mode 100644 index 263a0627..00000000 --- a/_includes/blog-archive-card.html +++ /dev/null @@ -1,41 +0,0 @@ -{% comment %} - Text-only card for the blog archives section. - Parameters: post (required) -{% endcomment %} - -{% assign topic_slug = include.post.blog_topic %} -{% if topic_slug %} - {% assign topic_label = site.data.blog_topics[topic_slug].label | default: topic_slug %} -{% else %} - {% assign topic_label = nil %} - {% for cat in include.post.categories %} - {% unless cat == "blog-post" %} - {% assign topic_label = cat | replace: "-", " " %} - {% break %} - {% endunless %} - {% endfor %} -{% endif %} - -{% assign card_title = include.post.title | markdownify | strip_html %} -{% assign post_url = include.post.url | relative_url %} - -
-
- - {% if topic_label %} - {{ topic_label }} - {% endif %} -
- -

- {{ card_title }} -

- - {% if include.post.excerpt %} -

{{ include.post.excerpt | strip_html | truncate: 160 }}

- {% endif %} - - Read more → -
diff --git a/_includes/blog-card.html b/_includes/blog-card.html deleted file mode 100644 index 367d4e7a..00000000 --- a/_includes/blog-card.html +++ /dev/null @@ -1,91 +0,0 @@ -{% comment %} - Blog card partial. - Parameters: - post (required) - size: featured | compact | grid (default: grid) - color_counter: fallback header color index (optional) - banner_only: when true, use header.overlay_image only (posts 4+ in the index) - - Post front matter (optional): - blog_image: path to listing card photo (top 3 featured posts) - blog_image_alt: alt text for blog_image - blog_topic: community | software | education | updates -{% endcomment %} - -{% assign card_size = include.size | default: "grid" %} -{% assign topic_slug = include.post.blog_topic %} -{% if topic_slug %} - {% assign topic_label = site.data.blog_topics[topic_slug].label | default: topic_slug %} -{% else %} - {% assign topic_label = nil %} - {% for cat in include.post.categories %} - {% unless cat == "blog-post" %} - {% assign topic_label = cat | replace: "-", " " %} - {% break %} - {% endunless %} - {% endfor %} -{% endif %} - -{% assign card_title = include.post.title | markdownify | strip_html %} -{% assign fallback_color = include.color_counter | default: 0 %} -{% assign use_banner_only = include.banner_only | default: false %} -{% if use_banner_only %} - {% assign card_image = include.post.header.overlay_image %} - {% assign card_image_alt = card_title %} -{% else %} - {% assign card_image = include.post.blog_image %} - {% if card_image == nil and include.post.header.overlay_image %} - {% assign card_image = include.post.header.overlay_image %} - {% endif %} - {% assign card_image_alt = include.post.blog_image_alt | default: include.post.feature_alt | default: card_title %} -{% endif %} - -
- -
- {% if card_image %} - {{ card_image_alt | escape }} - {% else %} - - {% endif %} - - {% if card_size == "grid" %} - -

{{ card_title }}

- {% endif %} -
- -
- {% if topic_label %} - {{ topic_label }} - {% endif %} - - {% unless card_size == "grid" %} -

{{ card_title }}

- {% endunless %} - - {% if card_size == "grid" and include.post.excerpt %} -

{{ include.post.excerpt | strip_html | truncate: 130 }}

- {% endif %} - -

- - {% if include.post.author and card_size != "grid" %} - {{ include.post.author }} - {% endif %} -

- - {% if card_size == "featured" and include.post.excerpt %} -

{{ include.post.excerpt | strip_html }}

- {% endif %} -
-
-
diff --git a/_includes/blog-featured.html b/_includes/blog-featured.html deleted file mode 100644 index 5209ee12..00000000 --- a/_includes/blog-featured.html +++ /dev/null @@ -1,23 +0,0 @@ -{% if include.posts.size > 0 %} - -{% endif %} diff --git a/_includes/breadcrumbs.html b/_includes/breadcrumbs.html deleted file mode 100644 index 75c032a4..00000000 --- a/_includes/breadcrumbs.html +++ /dev/null @@ -1,40 +0,0 @@ -{% case site.category_archive.type %} - {% when "liquid" %} - {% assign path_type = "#" %} - {% when "jekyll-archives" %} - {% assign path_type = nil %} -{% endcase %} - -{% if page.collection != 'posts' %} - {% assign path_type = nil %} - {% assign crumb_path = '/' %} -{% else %} - {% assign crumb_path = site.category_archive.path %} -{% endif %} - - diff --git a/_includes/category-list.html b/_includes/category-list.html deleted file mode 100644 index 39f5f3f9..00000000 --- a/_includes/category-list.html +++ /dev/null @@ -1,25 +0,0 @@ -{% case site.category_archive.type %} - {% when "liquid" %} - {% assign path_type = "#" %} - {% when "jekyll-archives" %} - {% assign path_type = nil %} -{% endcase %} - -{% if site.category_archive.path %} - {% assign categories_sorted = page.categories | sort_natural %} - -

- {{ site.data.ui-text[site.locale].categories_label | default: "Categories:" }} - - {% for category_word in categories_sorted %} - - - {{ category_word }} - - {% unless forloop.last %} - - , {% endunless %} - {% endfor %} - -

-{% endif %} diff --git a/_includes/comment.html b/_includes/comment.html deleted file mode 100644 index 2e3013ee..00000000 --- a/_includes/comment.html +++ /dev/null @@ -1,22 +0,0 @@ - diff --git a/_includes/comments-providers/custom.html b/_includes/comments-providers/custom.html deleted file mode 100644 index 600b9f71..00000000 --- a/_includes/comments-providers/custom.html +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/_includes/comments-providers/custom_scripts.html b/_includes/comments-providers/custom_scripts.html deleted file mode 100644 index a38725d7..00000000 --- a/_includes/comments-providers/custom_scripts.html +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/_includes/comments-providers/discourse.html b/_includes/comments-providers/discourse.html deleted file mode 100644 index aca62cc8..00000000 --- a/_includes/comments-providers/discourse.html +++ /dev/null @@ -1,13 +0,0 @@ -{% if site.comments.discourse.server %} -{% capture canonical %}{% if site.permalink contains '.html' %}{{ page.url | absolute_url }}{% else %}{{ page.url | absolute_url | remove:'index.html' | strip_slash }}{% endif %}{% endcapture %} - - -{% endif %} diff --git a/_includes/comments-providers/disqus.html b/_includes/comments-providers/disqus.html deleted file mode 100644 index 16a6027c..00000000 --- a/_includes/comments-providers/disqus.html +++ /dev/null @@ -1,15 +0,0 @@ -{% if site.comments.disqus.shortname %} - - -{% endif %} diff --git a/_includes/comments-providers/facebook.html b/_includes/comments-providers/facebook.html deleted file mode 100644 index 56a6f2d1..00000000 --- a/_includes/comments-providers/facebook.html +++ /dev/null @@ -1,8 +0,0 @@ -
- diff --git a/_includes/comments-providers/giscus.html b/_includes/comments-providers/giscus.html deleted file mode 100644 index 10bb8579..00000000 --- a/_includes/comments-providers/giscus.html +++ /dev/null @@ -1,24 +0,0 @@ - diff --git a/_includes/comments-providers/scripts.html b/_includes/comments-providers/scripts.html deleted file mode 100644 index 7984547d..00000000 --- a/_includes/comments-providers/scripts.html +++ /dev/null @@ -1,20 +0,0 @@ -{% if site.comments.provider and page.comments %} -{% case site.comments.provider %} - {% when "disqus" %} - {% include /comments-providers/disqus.html %} - {% when "discourse" %} - {% include /comments-providers/discourse.html %} - {% when "facebook" %} - {% include /comments-providers/facebook.html %} - {% when "staticman" %} - {% include /comments-providers/staticman.html %} - {% when "staticman_v2" %} - {% include /comments-providers/staticman_v2.html %} - {% when "utterances" %} - {% include /comments-providers/utterances.html %} - {% when "giscus" %} - {% include /comments-providers/giscus.html %} - {% when "custom" %} - {% include /comments-providers/custom_scripts.html %} -{% endcase %} -{% endif %} diff --git a/_includes/comments-providers/staticman.html b/_includes/comments-providers/staticman.html deleted file mode 100644 index ae3991d9..00000000 --- a/_includes/comments-providers/staticman.html +++ /dev/null @@ -1,40 +0,0 @@ -{% if site.repository and site.staticman.branch %} - -{% endif %} diff --git a/_includes/comments-providers/staticman_v2.html b/_includes/comments-providers/staticman_v2.html deleted file mode 100644 index 3d8ba111..00000000 --- a/_includes/comments-providers/staticman_v2.html +++ /dev/null @@ -1,40 +0,0 @@ -{% if site.repository and site.comments.staticman.branch %} - -{% endif %} diff --git a/_includes/comments-providers/utterances.html b/_includes/comments-providers/utterances.html deleted file mode 100644 index 5cf6c5cc..00000000 --- a/_includes/comments-providers/utterances.html +++ /dev/null @@ -1,21 +0,0 @@ - diff --git a/_includes/comments.html b/_includes/comments.html deleted file mode 100644 index b27c893b..00000000 --- a/_includes/comments.html +++ /dev/null @@ -1,180 +0,0 @@ -
- {% capture comments_label %}{{ site.data.ui-text[site.locale].comments_label | default: "Comments" }}{% endcapture %} - {% case site.comments.provider %} - {% when "discourse" %} -

{{ comments_label }}

-
- {% when "disqus" %} -

{{ comments_label }}

-
- {% when "facebook" %} -

{{ comments_label }}

-
- {% when "staticman_v2" %} -
- {% if site.repository and site.comments.staticman.branch %} - -
- {% if site.data.comments[page.slug] %} -

{{ site.data.ui-text[site.locale].comments_title | default: "Comments" }}

- {% assign comments = site.data.comments[page.slug] %} - - - {% assign commentObjects = '' | split: '' %} - {% for comment in comments %} - {% assign commentObject = comment[1] %} - {% assign commentObjects = commentObjects | push: commentObject %} - {% endfor %} - {% assign comments = commentObjects | sort: "date" %} - - {% for comment in comments %} - {% assign email = comment.email %} - {% assign name = comment.name %} - {% assign url = comment.url %} - {% assign date = comment.date %} - {% assign message = comment.message %} - {% include comment.html index=forloop.index email=email name=name url=url date=date message=message %} - {% endfor %} - {% endif %} -
- - - -
-

{{ site.data.ui-text[site.locale].comments_label | default: "Leave a Comment" }}

-

{{ site.data.ui-text[site.locale].comment_form_info | default: "Your email address will not be published. Required fields are marked" }} *

-
-
- - {{ site.data.ui-text[site.locale].loading_label | default: "Loading..." }} -
- -
- - - -
-
- - -
-
- - -
-
- - -
- - - - - {% if site.reCaptcha.siteKey %} -
-
-
- {% endif %} -
- -
-
-
- - {% if site.reCaptcha.siteKey %}{% endif %} - {% endif %} -
- {% when "staticman" %} -
- {% if site.repository and site.staticman.branch %} - -
- {% if site.data.comments[page.slug] %} -

{{ site.data.ui-text[site.locale].comments_title | default: "Comments" }}

- {% assign comments = site.data.comments[page.slug] %} - - - {% assign commentObjects = '' | split: '' %} - {% for comment in comments %} - {% assign commentObject = comment[1] %} - {% assign commentObjects = commentObjects | push: commentObject %} - {% endfor %} - {% assign comments = commentObjects | sort: "date" %} - - {% for comment in comments %} - {% assign email = comment.email %} - {% assign name = comment.name %} - {% assign url = comment.url %} - {% assign date = comment.date %} - {% assign message = comment.message %} - {% include comment.html index=forloop.index email=email name=name url=url date=date message=message %} - {% endfor %} - {% endif %} -
- - - -
-

{{ site.data.ui-text[site.locale].comments_label | default: "Leave a Comment" }}

-

{{ site.data.ui-text[site.locale].comment_form_info | default: "Your email address will not be published. Required fields are marked" }} *

-
-
- - {{ site.data.ui-text[site.locale].loading_label | default: "Loading..." }} -
- -
- - - -
-
- - -
-
- - -
-
- - -
- - - - -
- -
-
-
- - {% endif %} -
- {% when "utterances" %} -

{{ comments_label }}

-
- {% when "giscus" %} -

{{ comments_label }}

-
- {% when "custom" %} - {% include /comments-providers/custom.html %} - {% endcase %} -
diff --git a/_includes/connect-with-pyos.html b/_includes/connect-with-pyos.html deleted file mode 100644 index 2f4e08c8..00000000 --- a/_includes/connect-with-pyos.html +++ /dev/null @@ -1,27 +0,0 @@ -
- - Connect with us! -{: .header } - -There are many ways to get involved if you're interested! - -* If you read through our lessons and want to suggest changes, open an issue in our [lessons repository here](https://github.com/pyOpenSci/lessons) -* [Volunteer to be a reviewer for pyOpenSci's software review process](https://forms.gle/GHfxvmS47nQFDcBM6) -* [Submit a Python package to pyOpenSci for peer review](https://www.pyopensci.org/software-peer-review/how-to/author-guide.html#submit-your-package-for-peer-review) -* [Donate to pyOpenSci](https://give.communityin.org/pyopensci_2024) to support scholarships for future training events and the development of new learning content. -* Check out our [volunteer page](/volunteer.html) for other ways to get involved -* Explore our [Python Package Guide](https://www.pyopensci.org/python-package-guide/index.html) for comprehensive packaging guidance -* Keep an eye on our [events page](/events.html) for upcoming training events - -Follow us: - -{: .connect-with-pyos__social } -* [ Mastodon]({{ site.social.mastodon }}) -* [ Bluesky]({{ site.social.bluesky }}) -* [ LinkedIn]({{ site.social.linkedin }}) -* [ GitHub]({{ site.social.github }}) -* [ YouTube]({{ site.social.youtube }}) - -If you are on LinkedIn, check out and [subscribe to our newsletter](https://www.linkedin.com/newsletters/7179551305344933888/?displayConfirmation=true). - -
diff --git a/_includes/cookie-consent.html b/_includes/cookie-consent.html deleted file mode 100644 index 6853a6a8..00000000 --- a/_includes/cookie-consent.html +++ /dev/null @@ -1,134 +0,0 @@ - - - - diff --git a/_includes/div_purple_bottom.html b/_includes/div_purple_bottom.html deleted file mode 100644 index 08ba9c0f..00000000 --- a/_includes/div_purple_bottom.html +++ /dev/null @@ -1,11 +0,0 @@ -{% comment %} - Wave divider placed *below* a purple band (purple → wave → next section). - Pairs with div_purple_top.html (wave above the next purple band). - Asset source: images/swoosh/swoosh-illustrator-bottom.svg -{% endcomment %} - diff --git a/_includes/div_purple_top.html b/_includes/div_purple_top.html deleted file mode 100644 index 6875a8bd..00000000 --- a/_includes/div_purple_top.html +++ /dev/null @@ -1,30 +0,0 @@ -{% comment %} - Wave divider placed *above* a purple band (light section → wave → purple). - Pairs with div_purple_bottom.html, which sits *below* a purple band. -{% endcomment %} - diff --git a/_includes/documents-collection.html b/_includes/documents-collection.html deleted file mode 100644 index e88d8c4c..00000000 --- a/_includes/documents-collection.html +++ /dev/null @@ -1,15 +0,0 @@ -{% assign entries = site[include.collection] %} - -{% if include.sort_by %} - {% assign entries = entries | sort: include.sort_by %} -{% endif %} - -{% if include.sort_order == 'reverse' %} - {% assign entries = entries | reverse %} -{% endif %} - -{%- for post in entries -%} - {%- unless post.hidden -%} - {% include archive-single.html %} - {%- endunless -%} -{%- endfor -%} diff --git a/_includes/event-cards.html b/_includes/event-cards.html deleted file mode 100644 index ef24d0e9..00000000 --- a/_includes/event-cards.html +++ /dev/null @@ -1,73 +0,0 @@ -{% assign event_type = post.event.event_type | downcase %} -{% assign event_type_label = "" %} -{% if event_type == "training" %} - {% assign event_type_label = "Training" %} -{% elsif event_type == "sprint" %} - {% assign event_type_label = "Sprint" %} -{% elsif event_type == "talk" %} - {% assign event_type_label = "Talk" %} -{% elsif event_type == "community" %} - {% assign event_type_label = "Community event" %} -{% endif %} - -{% if include.compact %} -
-
-
- {% if event_type_label != "" %} - - {{ event_type_label }} - - {% endif %} - -

{{ post.title | markdownify | strip_html }}

-

{{ post.event.location }}

-

{{ post.excerpt }}

- {% if include.show_button != false %} - Learn more - {% endif %} -
-
-
-{% else %} -
- - - -
-{% endif %} diff --git a/_includes/event_instructors.html b/_includes/event_instructors.html deleted file mode 100644 index 84a522aa..00000000 --- a/_includes/event_instructors.html +++ /dev/null @@ -1,36 +0,0 @@ -{% if page.instructors %} -
-
- {% for person in page.instructors %} -
-
-
- GitHub profile photo of {{ person.name }} -
-
-

- - {{ person.name }} - - - -

-

{{ person.bio | strip_newlines | strip }}

-
-
-
- {% endfor %} -
-
-{% endif %} diff --git a/_includes/feature_row b/_includes/feature_row deleted file mode 100644 index e3ec715f..00000000 --- a/_includes/feature_row +++ /dev/null @@ -1,45 +0,0 @@ -{% if include.id %} - {% assign feature_row = page[include.id] %} -{% else %} - {% assign feature_row = page.feature_row %} -{% endif %} - -
- - {% for f in feature_row %} -
-
- {% if f.title %} -

{{ f.title }}

- {% endif %} - {% if f.image_path %} -
- - - {% if f.alt %}{{ f.alt }}{% endif %} - - {% if f.image_caption %} - {{ f.image_caption | markdownify | remove: "

" | remove: "

" }}
- {% endif %} -
- {% endif %} - -
- - - {% if f.excerpt %} -
- {{ f.excerpt | markdownify }} -
- {% endif %} - - {% if f.url %} -

{{ f.btn_label | default: site.data.ui-text[site.locale].more_label | default: "Learn More" }}

- {% endif %} -
-
-
- {% endfor %} - -
diff --git a/_includes/feature_row_pyos b/_includes/feature_row_pyos deleted file mode 100644 index a1096823..00000000 --- a/_includes/feature_row_pyos +++ /dev/null @@ -1,45 +0,0 @@ -{% if include.id %} - {% assign feature_row = page[include.id] %} -{% else %} - {% assign feature_row = page.feature_row %} -{% endif %} - -
- - {% for f in feature_row %} -
-
- {% if f.image_path %} -
- - - {% if f.alt %}{{ f.alt }}{% endif %} - {% if f.image_caption %} -
- {{ f.image_caption | markdownify | remove: "

" | remove: "

" }}
-
- {% endif %} -
-
- {% endif %} -
- {% if f.title %} -

{{ f.title }}

- {% endif %} - - {% if f.excerpt %} -
- {{ f.excerpt | markdownify }} -
- {% endif %} - -
-
-
- {% endfor %} -
diff --git a/_includes/figure b/_includes/figure deleted file mode 100644 index dacc668d..00000000 --- a/_includes/figure +++ /dev/null @@ -1,9 +0,0 @@ -
- {% if include.alt %}{{ include.alt }}{% endif %} - {%- if include.caption -%} -
- {{ include.caption | markdownify | remove: "

" | remove: "

" }} -
- {%- endif -%} -
diff --git a/_includes/footer.html b/_includes/footer.html deleted file mode 100644 index 036b2ba0..00000000 --- a/_includes/footer.html +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - diff --git a/_includes/footer/custom.html b/_includes/footer/custom.html deleted file mode 100644 index 92638830..00000000 --- a/_includes/footer/custom.html +++ /dev/null @@ -1,9 +0,0 @@ - -{% if page.url == '/' %} - -{% endif %} - diff --git a/_includes/gallery b/_includes/gallery deleted file mode 100644 index 71a9e1e1..00000000 --- a/_includes/gallery +++ /dev/null @@ -1,35 +0,0 @@ -{% if include.id %} - {% assign gallery = page[include.id] %} -{% else %} - {% assign gallery = page.gallery %} -{% endif %} - -{% if include.layout %} - {% assign gallery_layout = include.layout %} -{% else %} - {% if gallery.size == 2 %} - {% assign gallery_layout = 'half' %} - {% elsif gallery.size >= 3 %} - {% assign gallery_layout = 'third' %} - {% else %} - {% assign gallery_layout = '' %} - {% endif %} -{% endif %} - - diff --git a/_includes/group-by-array b/_includes/group-by-array deleted file mode 100644 index 528e40b1..00000000 --- a/_includes/group-by-array +++ /dev/null @@ -1,47 +0,0 @@ - - - -{% assign __empty_array = '' | split: ',' %} -{% assign group_names = __empty_array %} -{% assign group_items = __empty_array %} - - -{% assign __names = include.collection | map: include.field %} - - -{% assign __names = __names | join: ',' | join: ',' | split: ',' %} - - -{% assign __names = __names | sort %} -{% for name in __names %} - - -{% unless name == previous %} - - -{% assign group_names = group_names | push: name %} -{% endunless %} - -{% assign previous = name %} -{% endfor %} - - - -{% for name in group_names %} - - -{% assign __item = __empty_array %} -{% for __element in include.collection %} -{% if __element[include.field] contains name %} -{% assign __item = __item | push: __element %} -{% endif %} -{% endfor %} - - -{% assign group_items = group_items | push: __item %} -{% endfor %} diff --git a/_includes/head.html b/_includes/head.html deleted file mode 100644 index 7c021442..00000000 --- a/_includes/head.html +++ /dev/null @@ -1,22 +0,0 @@ - - -{% include seo.html %} - -{% unless site.atom_feed.hide %} - -{% endunless %} - - - - - - - - -{% if site.head_scripts %} - {% for script in site.head_scripts %} - - {% endfor %} -{% endif %} diff --git a/_includes/head/custom.html b/_includes/head/custom.html deleted file mode 100644 index e2357239..00000000 --- a/_includes/head/custom.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - diff --git a/_includes/home-training-feature.html b/_includes/home-training-feature.html deleted file mode 100644 index add1e296..00000000 --- a/_includes/home-training-feature.html +++ /dev/null @@ -1,39 +0,0 @@ -{% if page.training_feature and page.training_feature.enabled %} -
- {% if page.training_feature.eyebrow %} -

{{ page.training_feature.eyebrow }}

- {% endif %} - -
-
- {% if page.training_feature.badge %} -

{{ page.training_feature.badge }}

- {% endif %} - - {% if page.training_feature.title %} -

{{ page.training_feature.title }}

- {% endif %} - - {% if page.training_feature.excerpt %} -

{{ page.training_feature.excerpt }}

- {% endif %} - - {% if page.training_feature.button_url and page.training_feature.button_label %} - - {{ page.training_feature.button_label }} - - {% endif %} -
- - {% if page.training_feature.image_path %} -
- {{ page.training_feature.image_alt | default: page.training_feature.title }} -
- {% endif %} -
-
-{% endif %} diff --git a/_includes/masthead.html b/_includes/masthead.html deleted file mode 100644 index 5c161c9e..00000000 --- a/_includes/masthead.html +++ /dev/null @@ -1,69 +0,0 @@ -{% capture logo_path %}{{ site.logo }}{% endcapture %} - -
- -
diff --git a/_includes/nav_list b/_includes/nav_list deleted file mode 100644 index a035a5bd..00000000 --- a/_includes/nav_list +++ /dev/null @@ -1,26 +0,0 @@ -{% assign navigation = site.data.navigation[include.nav] %} - - diff --git a/_includes/package-grid.html b/_includes/package-grid.html deleted file mode 100644 index dd6bc3d1..00000000 --- a/_includes/package-grid.html +++ /dev/null @@ -1,77 +0,0 @@ -{% assign use_isotope = include.isotope | default: false %} -{% assign is_feature = include.feature %} - -{% if use_isotope %} -
-{% else %} -
-{% endif %} -
-

- {{ apackage.package_name }} -

- -
- - - {% if apackage.all_current_maintainers %} - {% for maintainer in apackage.all_current_maintainers %} - - {% if maintainer.name %} - {{ maintainer.name }}{% if forloop.last == false %},{% endif %} - {% else %} - {{ maintainer.github_username }}{% if forloop.last == false %},{% endif %} - {% endif %} - - {% endfor %} - {% elsif apackage.submitting_author.name != 'Name' %} - - {{ apackage.submitting_author.name }} - - {% else %} - - {{ apackage.submitting_author.github_username }} - - {% endif %} - -
- -
- {{ apackage.package_description | markdownify }} -
- - {% if apackage.date_accepted != "missing" %} -
- - Date Accepted: {{ apackage.date_accepted }} -
- {% endif %} - - {% unless is_feature %} - - {% endunless %} -
-
diff --git a/_includes/page__date.html b/_includes/page__date.html deleted file mode 100644 index 6d0f1f67..00000000 --- a/_includes/page__date.html +++ /dev/null @@ -1,24 +0,0 @@ - - -{% assign date_format = site.date_format | default: "%B %-d, %Y" %} -{% if page.last_modified %} -

- - - {{ site.data.ui-text[site.locale].date_label | default: "Updated:" }} - - -

-{% elsif page.date %} -

- - - {{ site.data.ui-text[site.locale].date_label | default: "Updated:" }} - - -

-{% endif %} diff --git a/_includes/page__hero.html b/_includes/page__hero.html deleted file mode 100644 index 537b018e..00000000 --- a/_includes/page__hero.html +++ /dev/null @@ -1,92 +0,0 @@ -{% assign overlay_img_path = nil %} -{% if page.header.overlay_image %} - {% assign overlay_img_path = page.header.overlay_image | relative_url %} -{% endif %} - -{% if page.header.image_description %} - {% assign image_description = page.header.image_description %} -{% else %} - {% assign image_description = page.title %} -{% endif %} - -{% assign image_description = image_description | markdownify | strip_html | strip_newlines | escape_once %} -{% assign rotating_hero_path = nil %} -{% assign hero_images = site.static_files | where_exp: "f", "f.path contains '/images/hero-images/' and f.extname != '.webp'" | sort: "path" %} -{% if page.header.hero_rotate_images == true and hero_images and hero_images.size > 0 %} - {% assign day_index = site.time | date: "%j" | plus: 0 | modulo: hero_images.size %} - {% for hero_file in hero_images %} - {% if forloop.index0 == day_index %} - {% assign rotating_hero_path = hero_file.path %} - {% endif %} - {% endfor %} -{% endif %} -{% assign selected_hero_path = rotating_hero_path | default: page.header.hero_image_path %} -{% assign selected_hero_webp_path = selected_hero_path | replace: '.png', '.webp' | replace: '.jpg', '.webp' | replace: '.jpeg', '.webp' %} -{% assign has_selected_hero_webp = false %} -{% assign selected_hero_ext = selected_hero_path | default: "" | split: '.' | last | downcase %} -{% if selected_hero_ext == 'webp' %} - {% assign has_selected_hero_webp = true %} -{% else %} - {% assign matched_webp = site.static_files | where_exp: "f", "f.path == selected_hero_webp_path" %} - {% if matched_webp and matched_webp.size > 0 %} - {% assign has_selected_hero_webp = true %} - {% endif %} -{% endif %} -
- {% if page.header.overlay_color or page.header.overlay_image %} -
-
-

- {% if paginator and site.paginate_show_page_num %} - {{ site.title }}{% unless paginator.page == 1 %} {{ site.data.ui-text[site.locale].page | default: "Page" }} {{ paginator.page }}{% endunless %} - {% else %} - {{ page.title | default: site.title | markdownify | remove: "

" | remove: "

" }} - {% endif %} -

- {% if page.tagline %} -

{{ page.tagline | markdownify | remove: "

" | remove: "

" }}

- {% elsif page.header.show_overlay_excerpt != false and page.excerpt %} -

{{ page.excerpt | markdownify | remove: "

" | remove: "

" }}

- {% endif %} - {% include page__meta.html %} - {% if page.header.cta_url %} -

{{ page.header.cta_label | default: site.data.ui-text[site.locale].more_label | default: "Learn More" }}

- {% endif %} - {% if page.header.actions %} -

- {% for action in page.header.actions %} - {{ action.label | default: site.data.ui-text[site.locale].more_label | default: "Learn More" }} - {% endfor %} -

- {% endif %} -
- {% if selected_hero_path %} -
- - {% if has_selected_hero_webp %} - - {% endif %} - {{ page.header.hero_image_alt | default: page.title }} - -
- {% endif %} -
- {% else %} - {{ image_description }} - {% endif %} - {% if page.header.caption %} - {{ page.header.caption | markdownify | remove: "

" | remove: "

" }}
- {% endif %} -
diff --git a/_includes/page__hero_video.html b/_includes/page__hero_video.html deleted file mode 100644 index a313a23d..00000000 --- a/_includes/page__hero_video.html +++ /dev/null @@ -1,2 +0,0 @@ -{% assign video = page.header.video %} -{% include video id=video.id provider=video.provider danmaku=video.danmaku %} diff --git a/_includes/page__meta.html b/_includes/page__meta.html deleted file mode 100644 index 3d228c92..00000000 --- a/_includes/page__meta.html +++ /dev/null @@ -1,31 +0,0 @@ -{% assign document = post | default: page %} -{% if document.read_time or document.show_date %} -

- {% if document.show_date and document.date %} - {% assign date = document.date %} - - - {% assign date_format = site.date_format | default: "%B %-d, %Y" %} - - - {% endif %} - - {% if document.read_time and document.show_date %}{% endif %} - - {% if document.read_time %} - {% assign words_per_minute = document.words_per_minute | default: site.words_per_minute | default: 200 %} - {% assign words = document.content | strip_html | number_of_words %} - - - - {% if words < words_per_minute %} - {{ site.data.ui-text[site.locale].less_than | default: "less than" }} 1 {{ site.data.ui-text[site.locale].minute_read | default: "minute read" }} - {% elsif words == words_per_minute %} - 1 {{ site.data.ui-text[site.locale].minute_read | default: "minute read" }} - {% else %} - {{ words | divided_by: words_per_minute }} {{ site.data.ui-text[site.locale].minute_read | default: "minute read" }} - {% endif %} - - {% endif %} -

-{% endif %} diff --git a/_includes/page__taxonomy.html b/_includes/page__taxonomy.html deleted file mode 100644 index f10b2026..00000000 --- a/_includes/page__taxonomy.html +++ /dev/null @@ -1,7 +0,0 @@ -{% if site.tag_archive.type and page.tags[0] %} - {% include tag-list.html %} -{% endif %} - -{% if site.category_archive.type and page.categories[0] %} - {% include category-list.html %} -{% endif %} diff --git a/_includes/paginator.html b/_includes/paginator.html deleted file mode 100644 index bffa0794..00000000 --- a/_includes/paginator.html +++ /dev/null @@ -1,69 +0,0 @@ -{% if paginator.total_pages > 1 %} - -{% endif %} diff --git a/_includes/people-grid.html b/_includes/people-grid.html deleted file mode 100644 index 33f82169..00000000 --- a/_includes/people-grid.html +++ /dev/null @@ -1,74 +0,0 @@ - -{% assign use_isotope = include.isotope | default: false %} - -{% if use_isotope == true %} -
-{% else %} -
-{% endif %} -
- {% if aperson.github_image_id %} -
- GitHub photo of {{ aperson.name }} -
- {% endif %} - -

- - {% if aperson.name %} - {{ aperson.name }} - {% else %} - @{{ aperson.github_username }} - {% endif %} - -

- -

- {% if aperson.partners %} - - {{ aperson.partners | join: ', ' }} - - {% endif %} -

- -

- {% if aperson.title %} - - {{ aperson.title | join: ', ' }} - - {% endif %} -

- -

{{ aperson.organization }}

- -
- {% if aperson.twitter %} - - - - {% endif %} - {% if aperson.github_username %} - - - - {% endif %} - {% if aperson.website %} - - - - {% endif %} - {% if aperson.orcidid %} - - - - {% endif %} - {% if aperson.mastodon %} - - - - {% endif %} -
-
-
diff --git a/_includes/post_pagination.html b/_includes/post_pagination.html deleted file mode 100644 index c09dd29f..00000000 --- a/_includes/post_pagination.html +++ /dev/null @@ -1,14 +0,0 @@ -{% if page.previous or page.next %} - -{% endif %} diff --git a/_includes/posts-category.html b/_includes/posts-category.html deleted file mode 100644 index b364f30e..00000000 --- a/_includes/posts-category.html +++ /dev/null @@ -1,5 +0,0 @@ -{%- for post in site.categories[include.taxonomy] -%} - {%- unless post.hidden -%} - {% include archive-single.html %} - {%- endunless -%} -{%- endfor -%} diff --git a/_includes/posts-tag.html b/_includes/posts-tag.html deleted file mode 100644 index 46fade02..00000000 --- a/_includes/posts-tag.html +++ /dev/null @@ -1,5 +0,0 @@ -{%- for post in site.tags[include.taxonomy] -%} - {%- unless post.hidden -%} - {% include archive-single.html %} - {%- endunless -%} -{%- endfor -%} diff --git a/_includes/pyos-blockquote.html b/_includes/pyos-blockquote.html deleted file mode 100644 index 3067fab5..00000000 --- a/_includes/pyos-blockquote.html +++ /dev/null @@ -1,13 +0,0 @@ -
-

{{ include.quote }}

- {% if include.author or include.event %} -
- - {%- if include.author -%}{{ include.author }}{%- endif -%} - {%- if include.author and include.event -%}, {{ include.event }}{%- elsif include.event -%}{{ include.event }}{%- endif -%} - -
- {% endif %} -
diff --git a/_includes/pyos-flow-feature.html b/_includes/pyos-flow-feature.html deleted file mode 100644 index 9c27398c..00000000 --- a/_includes/pyos-flow-feature.html +++ /dev/null @@ -1,7 +0,0 @@ -{% comment %} - One wrapper for a feature_row inside the flowing layout (purple band optional). - Usage: {% include pyos-flow-feature.html id="development" type="left" purple=true %} -{% endcomment %} -
-{% include feature_row id=include.id type=include.type %} -
diff --git a/_includes/pyos-testimonials-universities-labs.html b/_includes/pyos-testimonials-universities-labs.html deleted file mode 100644 index 15f35241..00000000 --- a/_includes/pyos-testimonials-universities-labs.html +++ /dev/null @@ -1,52 +0,0 @@ -
- - -
-
- -
-

- My favorite part of the course has been the step-by-step guidance and the hands-on experience of building a package. I also really liked the videos where the mentor shares the challenges they faced — it made the process feel more real and relatable. Testing classmates' packages was another highlight, as it helped me learn from others and see different approaches. -

-
-
Course participant · Ship It cohort
-
- -
- -
-

- I attended a conference during this course (full day for two days) and was still able to complete the course once the conference ended. I appreciate the structure of the course, and the flexibility was also needed. -

-
-
Course participant · Ship It cohort
-
-
-
diff --git a/_includes/scripts.html b/_includes/scripts.html deleted file mode 100644 index bbdaddff..00000000 --- a/_includes/scripts.html +++ /dev/null @@ -1,28 +0,0 @@ -{% if site.footer_scripts %} - {% for script in site.footer_scripts %} - - {% endfor %} -{% else %} - -{% endif %} - -{% if site.search == true or page.layout == "search" %} - {%- assign search_provider = site.search_provider | default: "lunr" -%} - {%- case search_provider -%} - {%- when "lunr" -%} - {% include_cached search/lunr-search-scripts.html %} - {%- when "google" -%} - {% include_cached search/google-search-scripts.html %} - {%- when "algolia" -%} - {% include_cached search/algolia-search-scripts.html %} - {%- endcase -%} -{% endif %} - -{% include analytics.html %} -{% include /comments-providers/scripts.html %} - -{% if site.after_footer_scripts %} - {% for script in site.after_footer_scripts %} - - {% endfor %} -{% endif %} diff --git a/_includes/search/algolia-search-scripts.html b/_includes/search/algolia-search-scripts.html deleted file mode 100644 index 37fa7d35..00000000 --- a/_includes/search/algolia-search-scripts.html +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - diff --git a/_includes/search/google-search-scripts.html b/_includes/search/google-search-scripts.html deleted file mode 100644 index 85faca16..00000000 --- a/_includes/search/google-search-scripts.html +++ /dev/null @@ -1,30 +0,0 @@ - diff --git a/_includes/search/lunr-search-scripts.html b/_includes/search/lunr-search-scripts.html deleted file mode 100644 index 4f11775e..00000000 --- a/_includes/search/lunr-search-scripts.html +++ /dev/null @@ -1,10 +0,0 @@ -{% assign lang = site.locale | slice: 0,2 | default: "en" %} -{% case lang %} -{% when "gr" %} - {% assign lang = "gr" %} -{% else %} - {% assign lang = "en" %} -{% endcase %} - - - diff --git a/_includes/search/search_form.html b/_includes/search/search_form.html deleted file mode 100644 index b9de365c..00000000 --- a/_includes/search/search_form.html +++ /dev/null @@ -1,26 +0,0 @@ -
- {%- assign search_provider = site.search_provider | default: "lunr" -%} - {%- case search_provider -%} - {%- when "lunr" -%} - -
- {%- when "google" -%} - -
- -
- {%- when "algolia" -%} - -
- {%- endcase -%} -
diff --git a/_includes/seo.html b/_includes/seo.html deleted file mode 100644 index c9d01e94..00000000 --- a/_includes/seo.html +++ /dev/null @@ -1,158 +0,0 @@ - -{%- if site.url -%} - {%- assign seo_url = site.url | append: site.baseurl -%} -{%- endif -%} -{%- assign seo_url = seo_url | default: site.github.url -%} - -{% assign title_separator = site.title_separator | default: '-' | replace: '|', '|' %} - -{%- if page.title -%} - {%- assign seo_title = page.title | append: " " | append: title_separator | append: " " | append: site.title -%} -{%- endif -%} - -{%- if seo_title -%} - {%- assign seo_title = seo_title | markdownify | strip_html | strip_newlines | escape_once -%} -{%- endif -%} - -{% if page.canonical_url %} - {%- assign canonical_url = page.canonical_url %} -{% else %} - {%- assign canonical_url = page.url | replace: "index.html", "" | absolute_url %} -{% endif %} - -{%- assign seo_description = page.description | default: page.excerpt | default: site.description -%} -{%- if seo_description -%} - {%- assign seo_description = seo_description | markdownify | strip_html | newline_to_br | strip_newlines | replace: '
', ' ' | escape_once | strip -%} -{%- endif -%} - -{%- assign author = page.author | default: page.authors[0] | default: site.author -%} -{%- assign author = site.data.authors[author] | default: author -%} - -{%- if author.twitter -%} - {%- assign author_twitter = author.twitter | replace: "@", "" -%} -{%- endif -%} - -{%- assign page_large_image = page.header.og_image | default: page.header.overlay_image | default: page.header.image | absolute_url -%} -{%- assign page_large_image = page_large_image | escape -%} - -{%- assign page_teaser_image = page.header.teaser | default: site.og_image | absolute_url -%} -{%- assign page_teaser_image = page_teaser_image | escape -%} - -{%- assign site_og_image = site.og_image | absolute_url -%} -{%- assign site_og_image = site_og_image | escape -%} - -{%- if page.date -%} - {%- assign og_type = "article" -%} -{%- else -%} - {%- assign og_type = "website" -%} -{%- endif -%} - -{{ seo_title | default: site.title }}{% if paginator %}{% unless paginator.page == 1 %} {{ title_separator }} {{ site.data.ui-text[site.locale].page | default: "Page" }} {{ paginator.page }}{% endunless %}{% endif %} - - -{% if author.name %} - - {% if og_type == "article" %} - - {% endif %} -{% endif %} - - - - - - - -{% if seo_description %} - -{% endif %} - -{% if page_large_image %} - -{% elsif page_teaser_image %} - -{% endif %} - -{% if site.twitter.username %} - - - - - - {% if page_large_image %} - - - {% else %} - - {% if page_teaser_image %} - - {% endif %} - {% endif %} - - {% if author_twitter %} - - {% endif %} -{% endif %} - -{% if page.date %} - -{% endif %} - -{% if og_type == "article" and page.last_modified_at %} - -{% endif %} - -{% if site.facebook %} - {% if site.facebook.publisher %} - - {% endif %} - - {% if site.facebook.app_id %} - - {% endif %} -{% endif %} - - - -{% if paginator.previous_page %} - -{% endif %} -{% if paginator.next_page %} - -{% endif %} - - - -{% if site.google_site_verification %} - -{% endif %} -{% if site.bing_site_verification %} - -{% endif %} -{% if site.alexa_site_verification %} - -{% endif %} -{% if site.yandex_site_verification %} - -{% endif %} -{% if site.naver_site_verification %} - -{% endif %} -{% if site.baidu_site_verification %} - -{% endif %} - diff --git a/_includes/sidebar.html b/_includes/sidebar.html deleted file mode 100644 index a4ca1ca7..00000000 --- a/_includes/sidebar.html +++ /dev/null @@ -1,19 +0,0 @@ -{% if page.author_profile or layout.author_profile or page.sidebar %} - -{% endif %} diff --git a/_includes/skip-links.html b/_includes/skip-links.html deleted file mode 100644 index c2d52235..00000000 --- a/_includes/skip-links.html +++ /dev/null @@ -1,7 +0,0 @@ - diff --git a/_includes/social-share.html b/_includes/social-share.html deleted file mode 100644 index dbe008d4..00000000 --- a/_includes/social-share.html +++ /dev/null @@ -1,9 +0,0 @@ - diff --git a/_includes/tag-list.html b/_includes/tag-list.html deleted file mode 100644 index f72d6bd7..00000000 --- a/_includes/tag-list.html +++ /dev/null @@ -1,19 +0,0 @@ -{% case site.tag_archive.type %} - {% when "liquid" %} - {% assign path_type = "#" %} - {% when "jekyll-archives" %} - {% assign path_type = nil %} -{% endcase %} - -{% if site.tag_archive.path %} - {% assign tags_sorted = page.tags | sort_natural %} - -

- {{ site.data.ui-text[site.locale].tags_label | default: "Tags:" }} - - {% for tag_word in tags_sorted %} - {% unless forloop.last %}, {% endunless %} - {% endfor %} - -

-{% endif %} diff --git a/_includes/toc b/_includes/toc deleted file mode 100644 index a234afaf..00000000 --- a/_includes/toc +++ /dev/null @@ -1,7 +0,0 @@ - diff --git a/_includes/toc.html b/_includes/toc.html deleted file mode 100644 index 8c710072..00000000 --- a/_includes/toc.html +++ /dev/null @@ -1,182 +0,0 @@ -{% capture tocWorkspace %} - {% comment %} - Copyright (c) 2017 Vladimir "allejo" Jimenez - - Permission is hereby granted, free of charge, to any person - obtaining a copy of this software and associated documentation - files (the "Software"), to deal in the Software without - restriction, including without limitation the rights to use, - copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following - conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - OTHER DEALINGS IN THE SOFTWARE. - {% endcomment %} - {% comment %} - Version 1.1.0 - https://github.com/allejo/jekyll-toc - - "...like all things liquid - where there's a will, and ~36 hours to spare, there's usually a/some way" ~jaybe - - Usage: - {% include toc.html html=content sanitize=true class="inline_toc" id="my_toc" h_min=2 h_max=3 %} - - Parameters: - * html (string) - the HTML of compiled markdown generated by kramdown in Jekyll - - Optional Parameters: - * sanitize (bool) : false - when set to true, the headers will be stripped of any HTML in the TOC - * class (string) : '' - a CSS class assigned to the TOC - * id (string) : '' - an ID to assigned to the TOC - * h_min (int) : 1 - the minimum TOC header level to use; any header lower than this value will be ignored - * h_max (int) : 6 - the maximum TOC header level to use; any header greater than this value will be ignored - * ordered (bool) : false - when set to true, an ordered list will be outputted instead of an unordered list - * item_class (string) : '' - add custom class(es) for each list item; has support for '%level%' placeholder, which is the current heading level - * submenu_class (string) : '' - add custom class(es) for each child group of headings; has support for '%level%' placeholder which is the current "submenu" heading level - * base_url (string) : '' - add a base url to the TOC links for when your TOC is on another page than the actual content - * anchor_class (string) : '' - add custom class(es) for each anchor element - * skip_no_ids (bool) : false - skip headers that do not have an `id` attribute - - Output: - An ordered or unordered list representing the table of contents of a markdown block. This snippet will only - generate the table of contents and will NOT output the markdown given to it - {% endcomment %} - - {% capture newline %} - {% endcapture %} - {% assign newline = newline | rstrip %} - - {% capture deprecation_warnings %}{% endcapture %} - - {% if include.baseurl %} - {% capture deprecation_warnings %}{{ deprecation_warnings }}{{ newline }}{% endcapture %} - {% endif %} - - {% if include.skipNoIDs %} - {% capture deprecation_warnings %}{{ deprecation_warnings }}{{ newline }}{% endcapture %} - {% endif %} - - {% capture jekyll_toc %}{% endcapture %} - {% assign orderedList = include.ordered | default: false %} - {% assign baseURL = include.base_url | default: include.baseurl | default: '' %} - {% assign skipNoIDs = include.skip_no_ids | default: include.skipNoIDs | default: false %} - {% assign minHeader = include.h_min | default: 1 %} - {% assign maxHeader = include.h_max | default: 6 %} - {% assign nodes = include.html | strip | split: ' maxHeader %} - {% continue %} - {% endif %} - - {% assign _workspace = node | split: '' | first }}>{% endcapture %} - {% assign header = _workspace[0] | replace: _hAttrToStrip, '' %} - - {% if include.item_class and include.item_class != blank %} - {% capture listItemClass %} class="{{ include.item_class | replace: '%level%', currLevel | split: '.' | join: ' ' }}"{% endcapture %} - {% endif %} - - {% if include.submenu_class and include.submenu_class != blank %} - {% assign subMenuLevel = currLevel | minus: 1 %} - {% capture subMenuClass %} class="{{ include.submenu_class | replace: '%level%', subMenuLevel | split: '.' | join: ' ' }}"{% endcapture %} - {% endif %} - - {% capture anchorBody %}{% if include.sanitize %}{{ header | strip_html }}{% else %}{{ header }}{% endif %}{% endcapture %} - - {% if htmlID %} - {% capture anchorAttributes %} href="{% if baseURL %}{{ baseURL }}{% endif %}#{{ htmlID }}"{% endcapture %} - - {% if include.anchor_class %} - {% capture anchorAttributes %}{{ anchorAttributes }} class="{{ include.anchor_class | split: '.' | join: ' ' }}"{% endcapture %} - {% endif %} - - {% capture listItem %}{{ anchorBody }}{% endcapture %} - {% elsif skipNoIDs == true %} - {% continue %} - {% else %} - {% capture listItem %}{{ anchorBody }}{% endcapture %} - {% endif %} - - {% if currLevel > lastLevel %} - {% capture jekyll_toc %}{{ jekyll_toc }}<{{ listModifier }}{{ subMenuClass }}>{% endcapture %} - {% elsif currLevel < lastLevel %} - {% assign repeatCount = lastLevel | minus: currLevel %} - - {% for i in (1..repeatCount) %} - {% capture jekyll_toc %}{{ jekyll_toc }}{% endcapture %} - {% endfor %} - - {% capture jekyll_toc %}{{ jekyll_toc }}{% endcapture %} - {% else %} - {% capture jekyll_toc %}{{ jekyll_toc }}{% endcapture %} - {% endif %} - - {% capture jekyll_toc %}{{ jekyll_toc }}{{ listItem }}{% endcapture %} - - {% assign lastLevel = currLevel %} - {% assign firstHeader = false %} - {% endfor %} - - {% assign repeatCount = minHeader | minus: 1 %} - {% assign repeatCount = lastLevel | minus: repeatCount %} - {% for i in (1..repeatCount) %} - {% capture jekyll_toc %}{{ jekyll_toc }}{% endcapture %} - {% endfor %} - - {% if jekyll_toc != '' %} - {% assign rootAttributes = '' %} - {% if include.class and include.class != blank %} - {% capture rootAttributes %} class="{{ include.class | split: '.' | join: ' ' }}"{% endcapture %} - {% endif %} - - {% if include.id and include.id != blank %} - {% capture rootAttributes %}{{ rootAttributes }} id="{{ include.id }}"{% endcapture %} - {% endif %} - - {% if rootAttributes %} - {% assign nodes = jekyll_toc | split: '>' %} - {% capture jekyll_toc %}<{{ listModifier }}{{ rootAttributes }}>{{ nodes | shift | join: '>' }}>{% endcapture %} - {% endif %} - {% endif %} -{% endcapture %}{% assign tocWorkspace = '' %}{{ deprecation_warnings }}{{ jekyll_toc }} diff --git a/_includes/tutorial-grid.html b/_includes/tutorial-grid.html deleted file mode 100644 index e8cfb614..00000000 --- a/_includes/tutorial-grid.html +++ /dev/null @@ -1,27 +0,0 @@ - -
-
-
-

{{ atutorial.title }}

-
- {% if atutorial.excerpt %} -

- {{ atutorial.excerpt }} -

- {% endif %} - - {% if atutorial.subpages %} -
    - {% for page in atutorial.subpages %} -
  • - {{ page.name }} -
  • - {% endfor %} -
- - {% endif %} -
- {% if atutorial.link %} - {{ atutorial.btn_label }} - {% endif %} -
diff --git a/_includes/upcoming-events.html b/_includes/upcoming-events.html deleted file mode 100644 index 165725ac..00000000 --- a/_includes/upcoming-events.html +++ /dev/null @@ -1,43 +0,0 @@ -{% comment %} -This include shows upcoming events on the home page. -It only displays if there are upcoming events. -{% endcomment %} - -{% assign all_events = site.posts - | where_exp: "item", "item.hidden != true" - | where_exp: "item", "item.categories contains 'events'" - | sort: "event.start_date" -%} - -{% capture nowunix %}{{'now' | date: '%s'}}{% endcapture %} -{% assign has_upcoming_events = false %} - -{% comment %}First pass: check if there are any upcoming events{% endcomment %} -{% for post in all_events %} - {% assign start_date = post.event.start_date | date: "%Y-%m-%d" %} - {% capture posttime %}{{ start_date | date: '%s'}}{% endcapture %} - {% if posttime > nowunix %} - {% assign has_upcoming_events = true %} - {% break %} - {% endif %} -{% endfor %} - -{% comment %}Only render the section if there are upcoming events{% endcomment %} -{% if has_upcoming_events == true %} -
-

Upcoming Events

-
- {% assign shown_events = 0 %} - {% for post in all_events %} - {% assign start_date = post.event.start_date | date: "%Y-%m-%d" %} - {% capture posttime %}{{ start_date | date: '%s'}}{% endcapture %} - {% if posttime > nowunix and shown_events < 2 %} -
- {% include event-cards.html compact=true show_button=true %} -
- {% assign shown_events = shown_events | plus: 1 %} - {% endif %} - {% endfor %} -
-
-{% endif %} diff --git a/_includes/video b/_includes/video deleted file mode 100644 index c85a868c..00000000 --- a/_includes/video +++ /dev/null @@ -1,24 +0,0 @@ -{% capture video_id %}{{ include.id }}{% endcapture %} -{% capture video_provider %}{{ include.provider }}{% endcapture %} -{% capture video_danmaku %}{{ include.danmaku | default: 0 }}{% endcapture %} - -{% capture video_src %} - {% case video_provider %} - {% when "vimeo" %} - https://player.vimeo.com/video/{{ video_id }}?dnt=true - {% when "youtube" %} - https://www.youtube-nocookie.com/embed/{{ video_id }} - {% when "google-drive" %} - https://drive.google.com/file/d/{{ video_id }}/preview - {% when "bilibili" %} - https://player.bilibili.com/player.html?bvid={{ video_id }}&page=1&as_wide=1&high_quality=1&danmaku={{ video_danmaku }} - {% endcase %} -{% endcapture %} -{% assign video_src = video_src | strip %} - - -{% unless video_src == "" %} -
- -
-{% endunless %} diff --git a/_layouts/archive-taxonomy.html b/_layouts/archive-taxonomy.html deleted file mode 100644 index eb62a874..00000000 --- a/_layouts/archive-taxonomy.html +++ /dev/null @@ -1,29 +0,0 @@ ---- -layout: default -author_profile: false ---- - -{% if page.header.overlay_color or page.header.overlay_image or page.header.image %} - {% include page__hero.html %} -{% elsif page.header.video.id and page.header.video.provider %} - {% include page__hero_video.html %} -{% endif %} - -{% if page.url != "/" and site.breadcrumbs %} - {% unless paginator %} - {% include breadcrumbs.html %} - {% endunless %} -{% endif %} - -
- {% include sidebar.html %} - -
- {% unless page.header.overlay_color or page.header.overlay_image %} -

{{ page.title }}

- {% endunless %} - {% for post in page.posts %} - {% include archive-single.html %} - {% endfor %} -
-
diff --git a/_layouts/archive.html b/_layouts/archive.html deleted file mode 100644 index 84c285bc..00000000 --- a/_layouts/archive.html +++ /dev/null @@ -1,26 +0,0 @@ ---- -layout: default ---- - -{% if page.header.overlay_color or page.header.overlay_image or page.header.image %} - {% include page__hero.html %} -{% elsif page.header.video.id and page.header.video.provider %} - {% include page__hero_video.html %} -{% endif %} - -{% if page.url != "/" and site.breadcrumbs %} - {% unless paginator %} - {% include breadcrumbs.html %} - {% endunless %} -{% endif %} - -
- {% include sidebar.html %} - -
- {% unless page.header.overlay_color or page.header.overlay_image %} -

{{ page.title }}

- {% endunless %} - {{ content }} -
-
diff --git a/_layouts/blog-index.html b/_layouts/blog-index.html deleted file mode 100644 index f252aad6..00000000 --- a/_layouts/blog-index.html +++ /dev/null @@ -1,85 +0,0 @@ ---- -layout: archive ---- - -{{ content }} - -{% assign blog_posts = site.posts - | where_exp: "item", "item.hidden != true" - | where_exp: "item", "item.categories contains 'blog-post'" - | where_exp: "item", "item.type != 'event'" -%} - -{% assign archive_age_days = site.blog_archive_age_days | default: 548 %} -{% assign archive_age_seconds = archive_age_days | times: 86400 %} -{% capture archive_cutoff %}{{ "now" | date: "%s" | minus: archive_age_seconds }}{% endcapture %} - -{% assign featured_posts = blog_posts | slice: 0, 3 %} - -
- {% include blog-featured.html posts=featured_posts %} - - {% assign has_recent_posts = false %} - {% for post in blog_posts offset: 3 %} - {% capture post_unix %}{{ post.date | date: "%s" }}{% endcapture %} - {% if post_unix > archive_cutoff %} - {% assign has_recent_posts = true %} - {% break %} - {% endif %} - {% endfor %} - - {% if has_recent_posts %} -
-

All posts

- -
- {% assign color_counter = 0 %} - {% assign color_max = 4 %} - - {% for post in blog_posts offset: 3 %} - {% capture post_unix %}{{ post.date | date: "%s" }}{% endcapture %} - {% if post_unix > archive_cutoff %} - {% unless post.header.overlay_image or post.blog_image %} - {% assign color_counter = color_counter | plus: 1 %} - {% if color_counter == color_max %} - {% assign color_counter = 0 %} - {% endif %} - {% endunless %} - - {% include blog-card.html post=post size="grid" color_counter=color_counter banner_only=true %} - {% endif %} - {% endfor %} -
-
- {% endif %} - - {% assign has_archive_posts = false %} - {% for post in blog_posts %} - {% if forloop.index > 3 %} - {% capture post_unix %}{{ post.date | date: "%s" }}{% endcapture %} - {% if post_unix <= archive_cutoff %} - {% assign has_archive_posts = true %} - {% break %} - {% endif %} - {% endif %} - {% endfor %} - - {% if has_archive_posts %} -
-
-

From the archives

- -
- {% for post in blog_posts %} - {% if forloop.index > 3 %} - {% capture post_unix %}{{ post.date | date: "%s" }}{% endcapture %} - {% if post_unix <= archive_cutoff %} - {% include blog-archive-card.html post=post %} - {% endif %} - {% endif %} - {% endfor %} -
-
-
- {% endif %} -
diff --git a/_layouts/categories.html b/_layouts/categories.html deleted file mode 100644 index f5448a29..00000000 --- a/_layouts/categories.html +++ /dev/null @@ -1,43 +0,0 @@ ---- -layout: archive ---- - -{{ content }} - -{% assign categories_max = 0 %} -{% for category in site.categories %} - {% if category[1].size > categories_max %} - {% assign categories_max = category[1].size %} - {% endif %} -{% endfor %} - -
    - {% for i in (1..categories_max) reversed %} - {% for category in site.categories %} - {% if category[1].size == i %} -
  • - - {{ category[0] }} {{ i }} - -
  • - {% endif %} - {% endfor %} - {% endfor %} -
- -{% assign entries_layout = page.entries_layout | default: 'list' %} -{% for i in (1..categories_max) reversed %} - {% for category in site.categories %} - {% if category[1].size == i %} -
-

{{ category[0] }}

-
- {% for post in category.last %} - {% include archive-single.html type=entries_layout %} - {% endfor %} -
- {{ site.data.ui-text[site.locale].back_to_top | default: 'Back to Top' }} ↑ -
- {% endif %} - {% endfor %} -{% endfor %} diff --git a/_layouts/category.html b/_layouts/category.html deleted file mode 100644 index b281c856..00000000 --- a/_layouts/category.html +++ /dev/null @@ -1,10 +0,0 @@ ---- -layout: archive ---- - -{{ content }} - -{% assign entries_layout = page.entries_layout | default: 'list' %} -
- {% include posts-category.html taxonomy=page.taxonomy type=entries_layout %} -
diff --git a/_layouts/collection.html b/_layouts/collection.html deleted file mode 100644 index d23d0c72..00000000 --- a/_layouts/collection.html +++ /dev/null @@ -1,10 +0,0 @@ ---- -layout: archive ---- - -{{ content }} - -{% assign entries_layout = page.entries_layout | default: 'list' %} -
- {% include documents-collection.html collection=page.collection sort_by=page.sort_by sort_order=page.sort_order type=entries_layout %} -
diff --git a/_layouts/compress.html b/_layouts/compress.html deleted file mode 100644 index bb34487d..00000000 --- a/_layouts/compress.html +++ /dev/null @@ -1,10 +0,0 @@ ---- -# Jekyll layout that compresses HTML -# v3.1.0 -# http://jch.penibelst.de/ -# © 2014–2015 Anatol Broder -# MIT License ---- - -{% capture _LINE_FEED %} -{% endcapture %}{% if site.compress_html.ignore.envs contains jekyll.environment or site.compress_html.ignore.envs == "all" %}{{ content }}{% else %}{% capture _content %}{{ content }}{% endcapture %}{% assign _profile = site.compress_html.profile %}{% if site.compress_html.endings == "all" %}{% assign _endings = "html head body li dt dd optgroup option colgroup caption thead tbody tfoot tr td th" | split: " " %}{% else %}{% assign _endings = site.compress_html.endings %}{% endif %}{% for _element in _endings %}{% capture _end %}{% endcapture %}{% assign _content = _content | remove: _end %}{% endfor %}{% if _profile and _endings %}{% assign _profile_endings = _content | size | plus: 1 %}{% endif %}{% for _element in site.compress_html.startings %}{% capture _start %}<{{ _element }}>{% endcapture %}{% assign _content = _content | remove: _start %}{% endfor %}{% if _profile and site.compress_html.startings %}{% assign _profile_startings = _content | size | plus: 1 %}{% endif %}{% if site.compress_html.comments == "all" %}{% assign _comments = "" | split: " " %}{% else %}{% assign _comments = site.compress_html.comments %}{% endif %}{% if _comments.size == 2 %}{% capture _comment_befores %}.{{ _content }}{% endcapture %}{% assign _comment_befores = _comment_befores | split: _comments.first %}{% for _comment_before in _comment_befores %}{% if forloop.first %}{% continue %}{% endif %}{% capture _comment_outside %}{% if _carry %}{{ _comments.first }}{% endif %}{{ _comment_before }}{% endcapture %}{% capture _comment %}{% unless _carry %}{{ _comments.first }}{% endunless %}{{ _comment_outside | split: _comments.last | first }}{% if _comment_outside contains _comments.last %}{{ _comments.last }}{% assign _carry = false %}{% else %}{% assign _carry = true %}{% endif %}{% endcapture %}{% assign _content = _content | remove_first: _comment %}{% endfor %}{% if _profile %}{% assign _profile_comments = _content | size | plus: 1 %}{% endif %}{% endif %}{% assign _pre_befores = _content | split: "" %}{% assign _pres_after = "" %}{% if _pres.size != 0 %}{% if site.compress_html.blanklines %}{% assign _lines = _pres.last | split: _LINE_FEED %}{% capture _pres_after %}{% for _line in _lines %}{% assign _trimmed = _line | split: " " | join: " " %}{% if _trimmed != empty or forloop.last %}{% unless forloop.first %}{{ _LINE_FEED }}{% endunless %}{{ _line }}{% endif %}{% endfor %}{% endcapture %}{% else %}{% assign _pres_after = _pres.last | split: " " | join: " " %}{% endif %}{% endif %}{% capture _content %}{{ _content }}{% if _pre_before contains "" %}{% endif %}{% unless _pre_before contains "" and _pres.size == 1 %}{{ _pres_after }}{% endunless %}{% endcapture %}{% endfor %}{% if _profile %}{% assign _profile_collapse = _content | size | plus: 1 %}{% endif %}{% if site.compress_html.clippings == "all" %}{% assign _clippings = "html head title base link meta style body article section nav aside h1 h2 h3 h4 h5 h6 hgroup header footer address p hr blockquote ol ul li dl dt dd figure figcaption main div table caption colgroup col tbody thead tfoot tr td th" | split: " " %}{% else %}{% assign _clippings = site.compress_html.clippings %}{% endif %}{% for _element in _clippings %}{% assign _edges = " ;; ;" | replace: "e", _element | split: ";" %}{% assign _content = _content | replace: _edges[0], _edges[1] | replace: _edges[2], _edges[3] | replace: _edges[4], _edges[5] %}{% endfor %}{% if _profile and _clippings %}{% assign _profile_clippings = _content | size | plus: 1 %}{% endif %}{{ _content }}{% if _profile %}
Step Bytes
raw {{ content | size }}{% if _profile_endings %}
endings {{ _profile_endings }}{% endif %}{% if _profile_startings %}
startings {{ _profile_startings }}{% endif %}{% if _profile_comments %}
comments {{ _profile_comments }}{% endif %}{% if _profile_collapse %}
collapse {{ _profile_collapse }}{% endif %}{% if _profile_clippings %}
clippings {{ _profile_clippings }}{% endif %}
{% endif %}{% endif %} diff --git a/_layouts/default.html b/_layouts/default.html deleted file mode 100644 index c8725323..00000000 --- a/_layouts/default.html +++ /dev/null @@ -1,47 +0,0 @@ ---- ---- - - - - - - - {% include head.html %} - {% include head/custom.html %} - - - - {% include_cached skip-links.html %} - {% include_cached masthead.html %} - -
- {{ content }} -
- - {% if site.search == true %} -
- {% include_cached search/search_form.html %} -
- {% endif %} - - - - {% include scripts.html %} - - - {% include cookie-consent.html %} - - - - diff --git a/_layouts/home.html b/_layouts/home.html deleted file mode 100644 index 02e96eb8..00000000 --- a/_layouts/home.html +++ /dev/null @@ -1,22 +0,0 @@ ---- -layout: archive ---- - -{{ content }} - -

{{ site.data.ui-text[site.locale].recent_posts | default: "Recent Posts" }}

- -{% if paginator %} - {% assign posts = paginator.posts %} -{% else %} - {% assign posts = site.posts %} -{% endif %} - -{% assign entries_layout = page.entries_layout | default: 'list' %} -
- {% for post in posts %} - {% include archive-single.html type=entries_layout %} - {% endfor %} -
- -{% include paginator.html %} diff --git a/_layouts/posts.html b/_layouts/posts.html deleted file mode 100644 index 13fc707c..00000000 --- a/_layouts/posts.html +++ /dev/null @@ -1,30 +0,0 @@ ---- -layout: archive ---- - -{{ content }} - -
    - {% assign postsInYear = site.posts | where_exp: "item", "item.hidden != true" | group_by_exp: 'post', 'post.date | date: "%Y"' %} - {% for year in postsInYear %} -
  • - - {{ year.name }} {{ year.items | size }} - -
  • - {% endfor %} -
- -{% assign entries_layout = page.entries_layout | default: 'list' %} -{% assign postsByYear = site.posts | where_exp: "item", "item.hidden != true" | group_by_exp: 'post', 'post.date | date: "%Y"' %} -{% for year in postsByYear %} -
-

{{ year.name }}

-
- {% for post in year.items %} - {% include archive-single.html type=entries_layout %} - {% endfor %} -
- {{ site.data.ui-text[site.locale].back_to_top | default: 'Back to Top' }} ↑ -
-{% endfor %} diff --git a/_layouts/posts_events.html b/_layouts/posts_events.html deleted file mode 100644 index 00992a51..00000000 --- a/_layouts/posts_events.html +++ /dev/null @@ -1,79 +0,0 @@ ---- -layout: archive ---- - - -{{ content }} - - -
-

Upcoming events

- - {% assign all_events = site.posts - | where_exp: "item", "item.hidden != true" - | where_exp: "item", "item.categories contains 'events'" - | sort: "event.start_date" - %} - - {% capture nowunix %}{{'now' | date: '%s'}}{% endcapture %} - {% assign has_upcoming_events = false %} - {% for post in all_events %} - - {% assign start_date = post.event.start_date | date: "%Y-%m-%d" %} - {% capture posttime %}{{ start_date | date: '%s'}}{% endcapture %} - - {% if posttime > nowunix %} - {% assign has_upcoming_events = true %} -
- {% include event-cards.html %} -
- {% endif %} - - {% endfor %} - - {% if has_upcoming_events == false %} -

pyOpenSci doesn't have any events coming up right now. However, check back - to see what we are planning!

- {% endif %} -
- -
-

Past events

- -
    - {% assign eventsInYear = site.posts - | where_exp: "item", "item.hidden != true" - | where_exp: "item", "item.categories contains 'events'" - | group_by_exp: 'post', 'post.event.start_date | date: "%Y"' - %} - {% for year in eventsInYear %} -
  • - - {{ year.name }} - -
  • - {% endfor %} -
- - {% assign entries_layout = page.entries_layout | default: 'list' %} - {% assign eventsByYear = site.posts - | where_exp: "item", "item.hidden != true" - | where_exp: "item", "item.categories contains 'events'" - | group_by_exp: 'post', 'post.event.start_date | date: "%Y"' - %} - - {% for year in eventsByYear %} -
-

{{ year.name }}

-
- {% for post in year.items %} - {% capture posttime %}{{ post.event.start_date | date: '%s'}}{% endcapture %} - {% if posttime < nowunix %} - {% include event-cards.html compact=true %} - {% endif %} - {% endfor %} -
- {{ site.data.ui-text[site.locale].back_to_top | default: 'Back to Top' }} ↑ -
- {% endfor %} -
diff --git a/_layouts/search.html b/_layouts/search.html deleted file mode 100644 index 9e661a36..00000000 --- a/_layouts/search.html +++ /dev/null @@ -1,42 +0,0 @@ ---- -layout: default ---- - -{% if page.header.overlay_color or page.header.overlay_image or page.header.image %} - {% include page__hero.html %} -{% endif %} - -{% if page.url != "/" and site.breadcrumbs %} - {% unless paginator %} - {% include breadcrumbs.html %} - {% endunless %} -{% endif %} - -
- {% include sidebar.html %} - -
- {% unless page.header.overlay_color or page.header.overlay_image %} -

{{ page.title }}

- {% endunless %} - - {{ content }} - - {%- assign search_provider = site.search_provider | default: "lunr" -%} - {%- case search_provider -%} - {%- when "lunr" -%} - -
- {%- when "google" -%} -
- -
-
- -
- {%- when "algolia" -%} - -
- {%- endcase -%} -
-
diff --git a/_layouts/single.html b/_layouts/single.html deleted file mode 100644 index 64da122f..00000000 --- a/_layouts/single.html +++ /dev/null @@ -1,118 +0,0 @@ ---- -layout: default ---- - -{% if page.header.overlay_color or page.header.overlay_image or page.header.image %} - {% include page__hero.html %} -{% elsif page.header.video.id and page.header.video.provider %} - {% include page__hero_video.html %} -{% endif %} - -{% assign breadcrumbs_enabled = site.breadcrumbs %} -{% if page.breadcrumbs != null %} - {% assign breadcrumbs_enabled = page.breadcrumbs %} -{% endif %} -{% if page.url != "/" and breadcrumbs_enabled %} - {% unless paginator %} - {% include breadcrumbs.html %} - {% endunless %} -{% endif %} - -
- {% include sidebar.html %} - -
- {% if page.title %}{% endif %} - {% if page.excerpt %}{% endif %} - {% if page.date %}{% endif %} - {% if page.last_modified_at %}{% endif %} - -
- {% unless page.header.overlay_color or page.header.overlay_image %} -
- {% if page.title %}

- -

{% endif %} - {% include page__meta.html %} -
- {% endunless %} - -
- {% if page.toc %} - - {% endif %} -
- {% if page.h2 %} -

{{ page.h2 }}

- {% if page.authors %} -

- {% for author in page.authors %} - {{ author }}{% if forloop.last == false %}, {% endif %} - {% endfor %} - {% if page.date %}{{ page.date | date: "%-d %B %Y" }}{% endif %} -

- - {% else %} - {% if page.date %}

{{ page.date | date: "%-d %B %Y" }}

{% endif %} - {% endif %} - {% endif %} -
- {{ content }} - - {% if page.path contains '_posts/' %} - {% capture connect_with_pyos %}{% include connect-with-pyos.html %}{% endcapture %} - {{ connect_with_pyos | markdownify }} - {% endif %} - - {% if page.link %}{% endif %} - -
- -
- {% if site.data.ui-text[site.locale].meta_label %} -

{{ site.data.ui-text[site.locale].meta_label }}

- {% endif %} - {% include page__taxonomy.html %} - {% include page__date.html %} -
- - {% if page.share %}{% include social-share.html %}{% endif %} - - {% include post_pagination.html %} -
- - {% if jekyll.environment == 'production' and site.comments.provider and page.comments %} - {% include comments.html %} - {% endif %} -
- - {% comment %}{% endcomment %} - {% if page.id and page.related and site.related_posts.size > 0 %} - - {% comment %}{% endcomment %} - {% elsif page.id and page.related %} - - {% endif %} -
diff --git a/_layouts/splash.html b/_layouts/splash.html deleted file mode 100644 index 21165024..00000000 --- a/_layouts/splash.html +++ /dev/null @@ -1,22 +0,0 @@ ---- -layout: default ---- - -{% if page.header.overlay_color or page.header.overlay_image or page.header.image %} - {% include page__hero.html %} -{% elsif page.header.video.id and page.header.video.provider %} - {% include page__hero_video.html %} -{% endif %} - -
-
- {% if page.title %}{% endif %} - {% if page.excerpt %}{% endif %} - {% if page.date %}{% endif %} - {% if page.last_modified_at %}{% endif %} - -
- {{ content }} -
-
-
diff --git a/_layouts/tag.html b/_layouts/tag.html deleted file mode 100644 index 8b1c1885..00000000 --- a/_layouts/tag.html +++ /dev/null @@ -1,10 +0,0 @@ ---- -layout: archive ---- - -{{ content }} - -{% assign entries_layout = page.entries_layout | default: 'list' %} -
- {% include posts-tag.html taxonomy=page.taxonomy type=entries_layout %} -
diff --git a/_layouts/tags.html b/_layouts/tags.html deleted file mode 100644 index daa11828..00000000 --- a/_layouts/tags.html +++ /dev/null @@ -1,43 +0,0 @@ ---- -layout: archive ---- - -{{ content }} - -{% assign tags_max = 0 %} -{% for tag in site.tags %} - {% if tag[1].size > tags_max %} - {% assign tags_max = tag[1].size %} - {% endif %} -{% endfor %} - -
    - {% for i in (1..tags_max) reversed %} - {% for tag in site.tags %} - {% if tag[1].size == i %} -
  • - - {{ tag[0] }} {{ i }} - -
  • - {% endif %} - {% endfor %} - {% endfor %} -
- -{% assign entries_layout = page.entries_layout | default: 'list' %} -{% for i in (1..tags_max) reversed %} - {% for tag in site.tags %} - {% if tag[1].size == i %} -
-

{{ tag[0] }}

-
- {% for post in tag.last %} - {% include archive-single.html type=entries_layout %} - {% endfor %} -
- {{ site.data.ui-text[site.locale].back_to_top | default: 'Back to Top' }} ↑ -
- {% endif %} - {% endfor %} -{% endfor %} diff --git a/_packaging/documentation.md b/_packaging/documentation.md deleted file mode 100644 index 3c65c2c0..00000000 --- a/_packaging/documentation.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -title: "Document your Python package" -excerpt: "Learn about best practices for developing documentation for your Python package." -learning_objectives: -subpages: - - name: Documentation Overview - url: https://www.pyopensci.org/python-package-guide/documentation/index.html# - - name: Create your docs - url: https://www.pyopensci.org/python-package-guide/documentation/write-user-documentation/get-started.html - - name: Create package tutorials - url: https://www.pyopensci.org/python-package-guide/documentation/write-user-documentation/create-package-tutorials.html - - name: Document your code (API docs) - url: https://www.pyopensci.org/python-package-guide/documentation/write-user-documentation/document-your-code-api-docstrings.html - -url: ---- diff --git a/_packaging/packaging.md b/_packaging/packaging.md deleted file mode 100644 index 5b331741..00000000 --- a/_packaging/packaging.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: "Create Python package structure" -excerpt: "Learn about the best way to create your Python package." -subpages: - - name: The structure of a Python package - url: https://www.pyopensci.org/python-package-guide/package-structure-code/python-package-structure.html - - name: Add metadata - pyproject.toml file - url: https://www.pyopensci.org/python-package-guide/package-structure-code/pyproject-toml-python-package-metadata.html - - name: Declare package dependencies - url: https://www.pyopensci.org/python-package-guide/package-structure-code/declare-dependencies.html - - name: Get to know the ecosystem of Python packaging tools - url: https://www.pyopensci.org/python-package-guide/package-structure-code/python-package-build-tools.html - - name: Complete package builds - url: https://www.pyopensci.org/python-package-guide/package-structure-code/complex-python-package-builds.html ---- diff --git a/_packaging/publish-python-package.md b/_packaging/publish-python-package.md deleted file mode 100644 index e480ee5d..00000000 --- a/_packaging/publish-python-package.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -title: "Publish your Python package" -excerpt: " - Learn more about the publishing options for your Python package including publishing on PyPI and the conda-forge channel of conda." -learning_objectives: -subpages: - - name: Build your package for PyPI publication - url: https://www.pyopensci.org/python-package-guide/package-structure-code/python-package-distribution-files-sdist-wheel.html - - name: Publish to PyPI and Conda - url: https://www.pyopensci.org/python-package-guide/package-structure-code/publish-python-package-pypi-conda.html# - - name: Versioning your package - url: https://www.pyopensci.org/python-package-guide/package-structure-code/python-package-versions.html - - name: Code style and format - url: https://www.pyopensci.org/python-package-guide/package-structure-code/code-style-linting-format.html -url: ---- diff --git a/_packaging/tests.md b/_packaging/tests.md deleted file mode 100644 index f8fe2a25..00000000 --- a/_packaging/tests.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -title: "Python package tests" -excerpt: " - Learn more about the importance of writing tests for your Python package and how you can setup infrastructure to run your tests both locally and on GitHub." -learning_objectives: -subpages: - - name: Introduction to writing tests - url: https://www.pyopensci.org/python-package-guide/tests/index.html - - name: Write tests for your Python package - url: https://www.pyopensci.org/python-package-guide/tests/write-tests.html - - name: Three types of types for your Python package - url: https://www.pyopensci.org/python-package-guide/tests/test-types.html - - name: Run tests locally - url: https://www.pyopensci.org/python-package-guide/tests/run-tests.html - - name: Run tests online using Continuous Integration - url: https://www.pyopensci.org/python-package-guide/tests/tests-ci.html -url: ---- diff --git a/_pages/about-peer-review.md b/_pages/about-peer-review.md deleted file mode 100644 index 2e46fd5e..00000000 --- a/_pages/about-peer-review.md +++ /dev/null @@ -1,171 +0,0 @@ ---- -layout: splash -permalink: /about-peer-review/ -title: "pyOpenSci Makes Python Software Better and Easier to Find Through Peer Review" -header: - overlay_image: images/headers/pyos-peer-review-header.png -intro: - - excerpt: "Our open peer review process makes scientific software better and easier to discover. [Check out our accepted packages.](https://www.pyopensci.org/python-packages.html)" -benefits_scientists: - - image_path: images/landing-pages/trusted-python-software.png - alt: "A pencil sketch of a round table with people sitting around it from different backgrounds working on laptops and also writing together." - excerpt: "Through our partnerships with domain specific communities our catalog of trusted tools for scientists across domains continues to grow." - title: "Scientists need trusted and vetted software" - url: /partners.html - btn_label: Learn more about scientific Python community partnerships - btn_class: btn--primary -benefits_maintainers: - - image_path: images/landing-pages/peer-review-supports-maintainers.png - alt: "A pencil sketch of a round table with people sitting around it from different backgrounds working on laptops and also writing together." - excerpt: "The pyOpenSci peer review process multiplies shared knowledge, making it easier for Pythonistas of all levels to accomplish challenging tasks, such as navigating the Python packaging ecosystem, with relative ease. And our diverse community supports scientific package maintainers in their efforts to develop and build robust software." - title: "Peer review benefits open source maintainers" - url: https://www.pyopensci.org/software-peer-review/about/benefits.html - btn_label: Learn more about the benefits of peer review - btn_class: btn--primary -peer_review: - - image_path: /images/landing-pages/peer-review-people.png - alt: "A pencil sketch of a round table with people sitting around it from different backgrounds working on laptops and also writing together." - excerpt: "Software peer review, similar to the review of scientific papers, is a process where scientists vet software code, documentation and infrastructure. pyOpenSci leads an [open peer review process](https://www.pyopensci.org/software-peer-review/our-process/how-review-works.html) run by a community of dedicated volunteers. Reviews are supportive and fully transparent with the shared goal of improving the quality, usability and maintainability of the software that is driving open science. - - * Diverse teams lead each review, enhancing the overall feedback quality." - title: "How Python software peer review works" - url: https://www.pyopensci.org/software-peer-review/our-process/review-timeline.html - btn_label: Learn more about the peer review timeline and roles - btn_class: btn--primary - - image_path: images/landing-pages/pyopensci-joss-partners.png - alt: "A pencil sketch of a round table with people sitting around it from different backgrounds working on laptops and also writing together." - excerpt: "Our partnership with JOSS means that you don't have to choose between pyOpenSci and JOSS. Simply submit your package to pyOS for review. If your package is accepted and in scope for JOSS, it will be fast-tracked through JOSS' review process. " - title: "Get a fast-track JOSS publication" - url: https://www.pyopensci.org/software-peer-review/partners/joss.html - btn_label: Learn more about our JOSS partnership - btn_class: btn--primary -get-involved: - - image_path: - title: "Become a pyOpenSci reviewer" - alt: - excerpt: "We could use your help! Fill out our reviewer form. We will contact you if we have a package that we need reviewers for. It's OK if you've never reviewed a package before! We'll walk you through it." - url: https://forms.gle/GHfxvmS47nQFDcBM6 - btn_label: "> Sign-up Now (Google Form)" - btn_class: btn--inverse - - image_path: - title: "See Our Review Process in Action" - alt: - excerpt: "Our software review process is run using GitHub issues. This means that anyone can check in on any part of any review and read all of the conversation. Check it out." - url: https://github.com/pyOpenSci/software-submission - btn_label: "> See Open Reviews" - btn_class: btn--inverse - - image_path: - title: "Ready to Submit a Package for Review?" - alt: - excerpt: "To submit a package to us, you need to [open an issue in our peer review GitHub repository](https://github.com/pyOpenSci/software-submission/issues/new/choose). Learn about the steps to submit a package for open peer review in our guidebook." - url: https://www.pyopensci.org/software-peer-review/how-to/author-guide.html - btn_label: "> View our Author Guide" - btn_class: btn--inverse -toc: false -classes: flowing ---- - - -{% include feature_row id="intro" type="center" %} - - -
-
- -{% include feature_row id="peer_review" type="left" %} - -
-
- -{% include div_purple_bottom.html %} - - -
-
- -{% include feature_row id="benefits_scientists" type="right" %} -{% include feature_row id="benefits_maintainers" type="right" %} - - -
-
- - - -
-
- -## Get involved with software peer review - -{% include feature_row_pyos id="get-involved" %} - - -
-
- -{% include div_purple_bottom.html %} -{: .clearall } - -
- -## Meet our editorial board - -The pyOpenSci software peer review process is led by a volunteer team of -editors from the scientific Python community. Editors do the following things: - -* They find reviewers from diverse backgrounds who have a mixture of scientific domain and Python experience. -* They oversee the entire review process for a package ensuring it runs in a timely and efficient manner -* They support the submitting authors and reviewers in answering questions related to the review -* They determine whether that package should be accepted into the pyOpenSci ecosystem once the review has wrapped up. - -Learn more about the -editor role at pyOpenSci [in our peer review guide.](https://www.pyopensci.org/software-peer-review/how-to/editors-guide.html) - -{% assign editors = site.data.contributors | where: 'editorial_board', true %} -{% assign editors = editors | sort: 'sort' %} - -
-{% for aperson in editors %} - {% include people-grid.html %} -{% endfor %} - - -
- -
- -## Emeritus & Guest Editors - -We are deeply grateful for those served on our editorial board previously! - -{% assign emeritus = site.data.contributors | where: 'emeritus_editor', true %} - -
-{% for aperson in emeritus %} - {% include people-grid.html %} -{% endfor %} -
- -
-
- - -
-
-

Recently accepted Python packages

- -{% assign packages_sorted = site.data.packages | sort_natural: 'date_accepted' | reverse %} - -
- {% for apackage in packages_sorted limit:3 %} - {% include package-grid.html feature=true %} - {% endfor %} -
- -
- -View All Accepted Packages - -
- -
diff --git a/_pages/blog.md b/_pages/blog.md deleted file mode 100644 index adceeb44..00000000 --- a/_pages/blog.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -layout: blog-index -permalink: /blog/ -classes: wide -title: "pyOpenSci Blog" -excerpt: "Updates from across pyOpenSci — accepted packages, peer review, training, and the community behind the code." -header: - overlay_image: images/blog/headers/pycon-us-2026-header.png -author_profile: false ---- diff --git a/_pages/contributors.md b/_pages/contributors.md deleted file mode 100644 index f7580d55..00000000 --- a/_pages/contributors.md +++ /dev/null @@ -1,122 +0,0 @@ ---- -layout: splash -permalink: /our-community/ -title: "The pyOpenSci Team & Contributors" -excerpt: "pyOpenSci is a diverse community of people interested in building a community of practice around scientific software written in Python." -classes: -header: - overlay_image: images/headers/pyopensci-learn-header.png - overlay_filter: 0.6 -redirect_from: - - /contributors.html ---- - -## Our pyOpenSci Community - -pyOpenSci has one core paid staff member who leads the organization. We are supported -by an expert team of volunteer advisory members who help steer the direction of the organization. - -## Executive council, leadership & staff - -{: .clearall } - -{% assign advisory_sorted = site.data.contributors | where:"board",true | sort: 'sort' %} - -
-{% for aperson in advisory_sorted %} - {% include people-grid.html %} -{% endfor %} -
- -{: .clearall } - - -## pyOpenSci advisory council - -{: .clearall } - -pyOpenSci advisory council members are volunteer experts in the scientific -Python open source space who provide high-level guidance on the development of -the organization. - -{% assign advisory_unsorted = site.data.contributors | where:"advisory",true %} -{% assign advisory_with_sort = advisory_unsorted | where_exp:"item","item.sort != nil" | sort: 'sort' %} -{% assign advisory_without_sort = advisory_unsorted | where_exp:"item","item.sort == nil" %} -{% assign advisory_working = advisory_with_sort | concat: advisory_without_sort %} - -
-{% for aperson in advisory_working %} - {% include people-grid.html %} -{% endfor %} -
- -{: .clearall } - -## Emeritus advisory and executive council - -{: .clearall } - -We are grateful for the time that these community members spent on our -leadership councils. - -{% assign emeritus_advisory = site.data.contributors | where:"emeritus_advisory", true %} - -
-{% for aperson in emeritus_advisory %} - {{ aperson.person_name }} - {% include people-grid.html %} -{% endfor %} -
- -{: .clearall } - - - -## pyOpenSci editorial board - -{: .clearall } -{: .clearall } - -{% assign editorial = site.data.contributors | where:"editorial_board", true | sort: 'sort' %} - -
-{% for aperson in editorial %} - {% include people-grid.html %} -{% endfor %} -
- -{: .clearall } -{: .clearall } - -## pyOpenSci community contributors - -{: .clearall } -{: .clearall } - -{% assign ppl_sorted = site.data.contributors | reverse %} -{% assign total_people = ppl_sorted | size %} - -pyOpenSci has a diverse and vibrant community of pythonistas! To date, -**{{ total_people }}** wonderful people have contributed to pyOpenSci. - -

- -
- - - - - - - - - - -
- -
-
-{% for aperson in ppl_sorted %} - {% include people-grid.html isotope=true %} -{% endfor %} -
diff --git a/_pages/events.md b/_pages/events.md deleted file mode 100644 index 933dae5b..00000000 --- a/_pages/events.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -layout: posts_events -permalink: /events.html -classes: wide -title: "pyOpenSci Events" -excerpt: "pyOpenSci holds events that support scientists developing open science skills." -header: - overlay_image: images/headers/pyopensci-learn-header.png - overlay_filter: rgba(20, 13, 36, 0.8) -author_profile: false ---- - -pyOpenSci runs free and paid training events that teach skills that scientists need -to make their science more open and collaborative. diff --git a/_pages/home.md b/_pages/home.md deleted file mode 100644 index 950549eb..00000000 --- a/_pages/home.md +++ /dev/null @@ -1,166 +0,0 @@ ---- -layout: splash -title: "Strengthening Scientific Open Source, Together" -excerpt: "We support the open source contributors and developers who fuel scientific discovery." -author_profile: false -published: true -site-map: true -permalink: / -header: - #overlay_image: images/headers/pyopensci-sprints-2025.png - overlay_color: "#33205c" - hero_rotate_images: true - hero_image_path: "/images/landing-pages/scipy-sprint-working.png" - hero_image_alt: "Three people collaborating during a pyOpenSci sprint." - actions: - - label: "Learn to Create a Python Package" - url: "https://www.pyopensci.org/python-package-guide/tutorials/create-python-package.html" - class: "btn--header-outline" - - label: "Submit a Package For Review" - url: "https://www.pyopensci.org/how-to-submit-a-package-to-pyopensci.html" - class: "btn--header-outline" -training_feature: - enabled: true - eyebrow: "Training" - badge: "New cohort coming fall 2026" - title: "Ship It: Python Packaging in the Era of AI" - excerpt: "A 10-day online course for researchers, academics, and RSEs - from working code to a published package." - button_label: "Learn more" - button_url: "/events/shipit-python-package-gen-ai-april-2026.html" - image_path: "/images/landing-pages/scipy-sprint-2026.png" - image_alt: "Python packaging guide graphic with a laptop and hands over keyboard." -mission: - - excerpt: "pyOpenSci broadens participation in scientific open source by breaking down social and technical barriers. Join our global community." -peer-review: - - image_path: /images/landing-pages/scipy-sprint-working.png - alt: "Light purple image that says software Peer Review. On the image is a woman at a laptop with a pyOpenSci logo on it and a cup of coffee next to her. There is a very light flower in the bottom right hand corner. " - title: "We Run Software Peer Review" - excerpt: "We review Python packages and software with the goal of helping scientists build better, discoverable and usable software.

- - Your package can also be published in JOSS through our review process.
- - Submit a package for review today.
- Apply to become a reviewer.
- " - - image_path: images/landing-pages/scipy-bof-working.png - alt: "Light purple image with a bunch from different backgrounds of stick figure people in a slightly darker color. The text on the image at the top says Community Partnerships" - title: "We Connect Researchers, Contributors, and Developers" - excerpt: " - pyOpenSci brings together researchers, core Python and conda - developers, and data scientists from organizations like NVIDIA - and Microsoft — alongside university partners like Stanford — - to collectively strengthen scientific open source. - - We partner with open source communities to share resources, knowledge and processes like peer review. - " - - image_path: images/landing-pages/scipy-sprint-2026.png - title: "We Break Down Python Packaging Pain Points" - alt: Light purple image that says python packaging guide and below it says simplifying python packaging. The background is a grey laptop with a hand looking down at the laptop the above. - excerpt: " - - Check out our beginner-friendly:
- - [Python Package Tutorials](https://www.pyopensci.org/python-package-guide/tutorials/intro.html)
- - [Python Package Guide](https://www.pyopensci.org/python-package-guide)

- - All of our resources are co-developed with the broader Python community and reviewed by beginner to expert Pythonistas to ensure the material is accessible for all. - " ---- - - - -{% include upcoming-events.html %} - -## Our programs are community powered - -{% include feature_row_pyos id="peer-review"%} - -## Broadening participation in scientific open source - -
-
-
-
- Image showing 3 people working at 2 computers during a spring at pyCon USA 2023. -
-
-

You don't need to be an expert to get involved

-
-

- Are you new to software peer review but you want to get involved? We've got you! - - We offer support and mentorship to new reviewers completing their first review. - - Reviewers do not need to be Python packaging experts. We welcome reviewers who focus on software accessibility and usability. -

-

- Are you new to peer review? - We offer a mentorship program for anyone interested in participating in peer review but who might like a bit of support. -

-
-
-
- -
-
- -
- -{% include home-training-feature.html %} - -{% assign new_ppl = site.data.contributors | reverse %} - -## New pyOpenSci contributors - -
-{% for aperson in new_ppl limit:5 %} - {% include people-grid.html %} -{% endfor %} -
- -
- - -{% assign blog_posts = site.posts | where_exp: "item", "item.categories contains 'blog-post'" %} - -
- -## Recent blog posts & updates - -{% assign packages_sorted = site.data.packages | sort_natural: 'date_accepted' | reverse %} - -{% assign color_counter = 0 %} -{% assign color_max = 4 %} - - -
- {% for post in blog_posts limit:3 %} - {% unless post.header.overlay_image %} - {% assign color_counter = color_counter | plus: 1 %} - {% if color_counter == color_max %} - {% assign color_counter = 0 %} - {% endif %} - {% endunless %} - {% include archive-cards.html color_counter=color_counter %} - {% endfor %} - -
-View more - -
- - -## Recently Accepted Python Packages - -{% assign packages_sorted = site.data.packages | sort_natural: 'date_accepted' | reverse %} - -
- {% for apackage in packages_sorted limit:3 %} - {% include package-grid.html feature=true %} - {% endfor %} -
- -
- -View All Accepted Packages diff --git a/_pages/how-to-submit-package.md b/_pages/how-to-submit-package.md deleted file mode 100644 index 33e80aad..00000000 --- a/_pages/how-to-submit-package.md +++ /dev/null @@ -1,237 +0,0 @@ ---- -layout: single -title: "How to submit a Python package to pyOpenSci for review" -excerpt: "Our open review process happens on GitHub and is open and community lead. Learn how to submit a Python package to pyOpenSci for peer review." -header: - overlay_image: images/headers/pyos-peer-review-header.png -author_profile: false -published: true -site-map: true -permalink: /how-to-submit-a-package-to-pyopensci.html -classes: wide -toc: true ---- - -## Submit your Python package for review - -Awesome, you’ve built a Python package! How do you ensure it follows best practices and gets seen by the broader Python community? - -**pyOpenSci’s peer review process** connects you with community reviewers and editors who will help you refine your package's structure, documentation, maintainability, and usability. pyOpenSci accepted packages: -* Join a [vetted ecosystem]({{ '/python-packages.html' | relative_url }}) of high-quality scientific tools -* Benefit from increased community visibility -* Can be fast-tracked for publication in [Journal of Open Source Software (JOSS)](https://www.pyopensci.org/software-peer-review/partners/joss.html) through our end-to-end review. - - **[ Learn more about the benefits of our peer review process](https://www.pyopensci.org/software-peer-review/about/benefits.html)**{: .btn .btn--success} - - -
- -Need help with your Python package? -{: .header } - -If you need help getting your package ready for review, you can submit a help request in our software-submission repository. - -You can also check out the [pyOpenSci Package Guide](https://www.pyopensci.org/python-package-guide/) which contains both - -* [Beginning-to-end Python packaging tutorials](https://www.pyopensci.org/python-package-guide/tutorials/intro.html) - -and overviews of: - -* [continuous integration](https://www.pyopensci.org/python-package-guide/tests/tests-ci.html#run-tests-with-continuous-integration), -* [testing](https://www.pyopensci.org/python-package-guide/#tests), and -* [documentation](https://www.pyopensci.org/python-package-guide/#documentation), all of which are required for a package to be accepted into [the pyOpenSci ecosystem](https://www.pyopensci.org/python-packages.html). - -
- -## Step-by-step guide to submitting your package for review - -Our **open peer review process** happens on GitHub using structured issues in the [**pyOpenSci software submission repository**](https://github.com/pyopensci/software-submission). Our review process is **open, constructive, and focused on improving your software for others to use**. - - - **[ Read our Author Guide for an overview of the process](https://www.pyopensci.org/software-peer-review/how-to/author-guide.html)**{: .btn .btn--success} - -### What happens after you submit your package? - -Once you submit your package, our **Editor-in-Chief** will review it to confirm: -- It fits within **pyOpenSci’s scope** -- It meets **infrastructure and documentation requirements** - - -### Expected timeline -The timeline of your review depends upon a few things: - -* Whether we have an editor on board (or need to find one) to lead a review of your package -* How long it takes your editor to find reviewers -* How much work your package needs before the review begins. - -However, once we find editors and reviewers for your package, you can expect a timeline like this: - -- **Initial Pre-review checks:** ~2 weeks (varies based on submission volume) -- **Finding editors and reviews to lead the review peer review:** ~2-4 weeks (depends on package complexity and availability) -- **Running the review:** 4-8+ weeks. This depends on the amount of reviewer feedback you get and how long it takes for you to respond to that feedback. - -📖 **Read the full review process in our** [Peer Review Guide](https://www.pyopensci.org/software-peer-review/how-to/author-guide.html). - -### 1. Check if your package is in pyOpenSci's scope - -Before submitting your package for review, make sure that it fits within [**pyOpenSci’s peer review scope**](https://www.pyopensci.org/software-peer-review/about/package-scope.html). -Our package scope focuses on the scientific domains and areas in which we review. - -If you are unsure if your package is in scope, [**🔗 submit a pre-submission inquiry**](#submission-type) to get feedback from our editorial team and ask questions. - -### 2. Check that your package meets our pre-review requirements - -It's also a good idea to check that your package meets [**pyOpenSci’s minimum Python package requirements**](https://www.pyopensci.org/software-peer-review/how-to/editor-in-chief-guide.html#editor-checklist-template). So check that your package meets those criteria before you submit. -The Editor in Chief will check your package against these criteria before the review begins. - -If you have any questions or need help getting your package up to pyOpenSci standards, we are here to help. Submit a help-request issue in our software-submission repo or ask a question in our [GitHub Discussions](https://github.com/orgs/pyOpenSci/discussions). - -
-Our packaging guide covers the core criteria which include: -* **Your package is installable:** through [PyPI](https://www.pyopensci.org/python-package-guide/package-structure-code/publish-python-package-pypi-conda.html#what-is-pypi) (preferred) and/or a [Conda channel](https://www.pyopensci.org/python-package-guide/package-structure-code/publish-python-package-pypi-conda.html#what-is-conda-and-anaconda-org) (e.g., conda-forge, bioconda). -* **It has clear documentation:** including user guides, tutorials, and API documentation. -* **It has automated [testing](https://www.pyopensci.org/python-package-guide/tests/write-tests.html) & [Continuous Integration (CI)](https://www.pyopensci.org/python-package-guide/tests/tests-ci.html) setup:** -* **It has core [documentation files](https://www.pyopensci.org/python-package-guide/documentation/repository-files/intro.html):** Including `README.md`, `LICENSE`, `CONTRIBUTING.md`, and a Code of Conduct file. -
- - -### 3. Submit your package for review - -Once you have determined that your package is in scope and meets our core packaging requirements, it's time to submit it for review: - -1. **Go to our software submission GitHub repository**: [pyOpenSci/software-submission](https://github.com/pyOpenSci/software-submission/issues/new/choose) -2. **Click on the Issues tab** -3. **Select the appropriate issue template** -4. **Complete the issue form** and submit it - -** [Submit your package here](https://github.com/pyOpenSci/software-submission/issues/new/choose)**{: .btn .btn--success} - -
-There are **three templates in our Issue Submission repo**: - -- ** Help request:** Need guidance on testing, docs, or packaging? Submit a help request, and our editorial team will assist. -- [** Pre-submission inquiry:**](https://github.com/pyOpenSci/software-submission/issues/new?template=presubmission-inquiry.md) Unsure if your package [fits our scope](https://www.pyopensci.org/software-peer-review/about/package-scope.html)? Submit an inquiry for feedback before review. -- ** Full submission:** Ready for peer review? Submit your package to kick off the process. - - -
- - Screenshot of the pyOpenSci issue selection screen on GitHub, with three options: Help Request, Presubmission Inquiry, and Submit Software for Review. -
- Above are the three issue templates that you can select from when submitting a package for review to pyOpenSci. Select the issue that best fits your current situation. -
-
-
- - -
- Animated gif showing how to find the GitHub issue submission process for peer review. -
- How to open a new review submission in our GitHub repository. -
-
- - -#### Guidelines for filling out the review issue template - -When you fill out the review template, please consider the following: - -##### Basic template criteria -* **Complete all sections of the template.** If you have questions about fields, you can ask about them in the review issue. -* **Do not modify existing formatting:** Please do not modify the template structure by adding elements to the template fields such as bold, italics, etc. -* **Submit your issue only when fully completed:** If you can, try to avoid submitting an issue and then continuously editing it. If you'd like to work on the issue over time, consider forking our repository and working on the issue in your fork before submitting it as an option. -* **If you made a pre-submission inquiry**, paste the link to the corresponding issue in your issue submission to link your submission request to the pre-submission discussion. - -##### Long term maintenance and code of conduct - -To be considered for review, you must agree to the following when filling our the review submission: -1. **Follow our Code of Conduct:** Ensure respectful and constructive communication during and after the review. -2. **Commit to maintaining your package for at least 2 years maintenance:** Package maintenance is on area that distinguishes pyOpenSci from JOSS. If your primary goal is publication rather than ongoing package maintenance, consider submitting directly to [JOSS](https://joss.theoj.org/) instead. - -```markdown -## Code of Conduct & Commitment to Maintain Package - -- [ ] I agree to abide by [pyOpenSci's Code of Conduct][PyOpenSciCodeOfConduct] during the review process and in maintaining my package after should it be accepted. -- [ ] I have read and will commit to package maintenance after the review as per the [pyOpenSci Policies Guidelines][Commitment]. -``` - -##### Our partnerships - -Becoming published or affiliated with one of our partners is optional. - -
- - A flowchart showing how a package submitted to pyOpenSci can also be fast-tracked for JOSS publication. -
- Packages accepted into pyOpenSci can also be published in JOSS through a streamlined process. - -
-
- -* **If you are interested in a fast track review through JOSS, be sure to check the JOSS box** JOSS will accept our review as theirs and only review your paper.md file. -* **Community Affiliation:** If you are interested in becoming affiliated with a partner community like [Astropy](https://www.pyopensci.org/software-peer-review/partners/astropy.html), be sure to click the community partner. Learn more about [community affiliation through pyOpenSci's peer review process here](https://www.pyopensci.org/software-peer-review/partners/scientific-communities.html). - -```markdown -## Community Partnerships - -If your package is associated with an existing community please check below: - -- [ ] Astropy:[My package adheres to Astropy community standards](https://www.pyopensci.org/software-peer-review/partners/astropy.html) -- [ ] Pangeo: My package adheres to the [Pangeo standards listed in the pyOpenSci peer review guidebook][PangeoCollaboration] -``` - -##### Please fill out our survey - -Completing the [pyOpenSci pre-review survey](https://forms.gle/F9mou7S3jhe8DMJ16) is a huge help to our team of volunteers, future package authors, and pyOpenSci as an organization. It helps us track submissions as well as continuously improve our peer review process. - -##### Template sections that you can ignore - -Some template sections are **for editors only** and should be left blank. These include: - -- **Editorial assignments:** "EiC," "Editor," "Reviewer 1," "Reviewer 2" -- **JOSS-specific fields:** "JOSS DOI," "Version accepted," "Date accepted" - -```yaml -# All of the fields below will be filled out by out editorial team -EiC: TBD -Editor: TBD -Reviewer 1: TBD -Reviewer 2: TBD -Archive: TBD -JOSS DOI: TBD -Version accepted: TBD -Date accepted (month/day/year): TBD -``` - - - -
- - Need help? -{: .header } - -Check out our [** Python packaging guide**](https://www.pyopensci.org/python-package-guide/) for best practices on packaging, testing, and infrastructure setup. - -
- -Once you're ready, **submit your issue**, and our editors will take it from there! 🚀 - -
-## Connect with us! - -If you found this how-to guide useful, or if you have suggestions for improving it, we'd love to hear from you! Reach out to us on: - -* [BlueSky](https://bsky.app/profile/pyopensci.org) -* [Fosstodon](https://fosstodon.org/@pyOpenSci) -* [LinkedIn](https://www.linkedin.com/company/pyopensci) -
diff --git a/_pages/learn-universities-labs.md b/_pages/learn-universities-labs.md deleted file mode 100644 index e50a0e0d..00000000 --- a/_pages/learn-universities-labs.md +++ /dev/null @@ -1,293 +0,0 @@ ---- -layout: splash -classes: flowing -title: "Open source training for universities & research labs" -author_profile: false -published: true -site-map: true -permalink: /learn-universities-labs.html -header: - overlay_color: "#33205c" - overlay_filter: 0.3 ---- - -
-## Open source training for university research communities - -
- - - A group of contributors sitting at a table at a pyOpenSci sprint working on their laptops and talking to each other. - -
- -Introduce your research community to the world of open source scientific -software through pyOpenSci’s training and community programs. - -Our programs help researchers develop sustainable, reusable code and research -software while connecting them to the global scientific Python ecosystem. -Participants gain access to top tier asynchronous training sessions led by -Python experts, interactive hands on training workshops, and direct support -from open source leaders. - -### Benefits for universities and research programs - -
    -
  • - - Enhance grant competitiveness and compliance. - In the age of open science, funders increasingly require researchers to - publish code, data, and other artifacts openly to accelerate discovery. We - help your researchers learn practices that make scientific open source - reusable, maintainable, and easier for others to build upon, improving the - longevity and impact of research outputs. -
  • -
  • - - Support the shift to responsible AI adoption. - Researchers increasingly rely on generative AI for scientific software - development, but many use these tools as workhorses instead of thoughtful - junior collaborators. Our courses help your researchers accelerate open - source innovation while preserving community trust and maintainer - sustainability. By collaborating with pyOpenSci, your institution stands - alongside industry experts to define best practices for generative AI in - open science. -
  • -
  • - - Learn alongside a global expert network. - Connect your community to a global network of experienced developers and - maintainers who build and maintain scientific Python packages, as well as - the researchers and professionals who use these tools every day. -
  • -
  • - - Get real world, cross disciplinary support. - Receive guidance from experienced maintainers who regularly build and - maintain research software. Our training includes regular office hours that - facilitate hands on support so learners can ask questions and get help with - the problems that matter most to their work. -
  • -
  • - - Complete a trusted curriculum. - Our programs have already supported more than 200 researchers with - practical open source development, contribution, and software development - skills through hands on, beginner friendly, and inclusive workshops. -
  • -
-
- -
-
- -{% include pyos-blockquote.html quote="pyOpenSci has proven to be a valuable partner in developing our competencies -in code peer review and open science as a whole community, instead of best -practices being confined to particular labs. For any research university that -does significant work in Python (which is all universities!) I highly -recommend getting involved with pyOpenSci.." author="Zach Chandler" event="Director, Stanford Open Source Program Office" class="highlight purple wide" %} - -
-
- -
-
- -## Research software accelerator programs - -Supporting you in building a thriving Open Source community at your institution. -Guide your researchers from a working script to a published Python package using industry-standard tools like Hatch, pytest, uv, and Sphinx. - -
-
-
-
-

AcceleratorESSENTIALS

-

- Get your researchers trained in open source workflows and Python packaging with expert-backed course materials and train-the-trainer materials. -

-
-

What's Included

- -
    -
  • 10-day On-Demand Asynchronous Course Access: Ship It: Python Packaging in the GenAI Era
  • -
  • 6 Months On Demand Course Access
  • -
  • ~150 course seats
  • -
  • Train the trainer materials
  • -
  • Foundational workflows for sustainable software
  • -
  • 1 Peer Review Webinar
  • -
  • Content sponsored by Open Source at Stanford University
  • -
-
-

Course details

-
    -
  • Asynchronous training access for research teams
  • -
  • 27 videos across 10 modules
  • -
  • Python expert insights and videos
  • -
  • Course Certificate upon completion
  • -
  • GitHub badge upon completion
  • -
-
-

$2,500

-
- Contact us -
- - - -
-
-

AcceleratorPRO

-

- Everything in Community Partner, plus a pyOpenSci curated course learning experience with hands-on expert support during the course, gamified course activities, and curriculum advisory. Course will be branded to include your institution's name. -

-
-

What's Included

- -
    -
  • 10-day On-Demand Asynchronous Course Access: Ship It: Python Packaging in the GenAI Era
  • -
  • 1 Year On Demand Course Access
  • -
  • 2 expert-supported course cohorts
  • -
  • Gamified activities facilitated by open source experts
  • -
  • Unlimited course seats
  • -
  • Train the trainer materials
  • -
  • Foundational workflows for sustainable software
  • -
  • 2 Peer Review Webinars
  • -
  • Expert-lead packaging and open source webinars (up to 3)
  • -
  • Community access
  • -
  • Curriculum Advisory
  • -
  • Customized course branding for your institution
  • -
  • Content sponsored by Open Source at Stanford University
  • -
-
-

Course details

-
    -
  • Asynchronous training access for research teams
  • -
  • 27 videos across 10 modules
  • -
  • Python expert insights and videos
  • -
  • Course Certificate upon completion
  • -
  • GitHub badge upon completion
  • -
-
    -
  • Leadership programming for research software champions
  • -
  • Institution-wide strategy sessions and advisory support
  • -
  • Visibility across pyOpenSci community channels
  • -
  • Long-term partnership planning and impact tracking
  • -
-
- Contact us -
-
- -*Note: Topics are confirmed each cohort cycle as new content is released.* - -### Complex, technical concepts made easy - -Researchers shouldn't have to struggle with sharing code and software on their own. Connect your students and researchers with a thriving, inclusive, and vibrant community of beginner-to-expert Pythonistas who support scientific open source. Open doors for your researchers to engage with other Python developers, troubleshoot challenges together, and gain ongoing support as they develop their scientific open source skills. - -
-
- -
-
- -## Don't take our word for it: hear from previous course learners - -{% include pyos-testimonials-universities-labs.html %} - -*Our current content is the 10-day Ship It: Python Packaging in the GenAI Era course.* - -
-
- -
-
- -### Beta Partnership packages - -We are currently piloting our partnership program to find the best combination of resources and events that best support our University partners. - -
-
- -{% include div_purple_top.html %} - -
-
- -## Ready to explore a pyOpenSci membership? - -From emerging open source communities to established research software -programs, pyOpenSci offers training and partnership opportunities at every -stage. - -Request program details - -### Did you know? - -pyOpenSci also offers a wide variety of free training resources for individual -learners. Explore our workshops, webinars, and more on our -[events page](/events.html). - -
-
- -{% include div_purple_bottom.html %} - -
-
- -## Why partnering with pyOpenSci matters - -pyOpenSci is a nonprofit organization that supports open source scientific -Python research software through software peer review, training, and community -building. Revenue generated through partnerships directly supports the -development, maintenance, and sustainability of open source research software. - -### Bottom line - -Researchers should not have to struggle with sharing code and software on -their own. Connect your students and researchers with a thriving, inclusive, -and vibrant community of beginner to expert Pythonistas who support scientific -open source. - -Open doors for your researchers to engage with other Python developers, -troubleshoot challenges together, and gain ongoing support as they develop -their scientific open source skills. - -
-
diff --git a/_pages/learn.md b/_pages/learn.md deleted file mode 100644 index 6ed09e9f..00000000 --- a/_pages/learn.md +++ /dev/null @@ -1,112 +0,0 @@ ---- -layout: splash -permalink: /learn.html -title: "Python Tutorials & Learning Resources for Scientists" -classes: flowing -header: - overlay_image: images/headers/pyopensci-learn-header.png - overlay_filter: 0.3 -scientists: - - image_path: images/landing-pages/peer-review-people.png - alt: "A pencil sketch of a round table with people sitting around it from different backgrounds working on laptops and also writing together." - excerpt: "Our catalog of vetted open source tools makes it easier for scientists to find the trusted tools that they need to develop their open science workflows." - title: "Find the open science tools that you need for your research" - url: https://www.pyopensci.org/python-packages.html - btn_label: View our growing list of accepted scientific Python packages - btn_class: btn--primary -development: - - image_path: images/learn-graphics/community-created.png - alt: "An image showing a bunch of hands holding up a sign that says Community Developed. the background is dark purple with a few green decorative items." - title: "Our process: Community-developed Python tutorials" - excerpt: "Our tutorials are created through a multi-stage community review process. - - * Tutorials are developed by the pyOpenSci team or community members. - - * Tutorials are reviewed by tool maintainers to ensure ideas and concepts are accurate. - - * Before publication, tutorials then go through several rounds of community review for accuracy, usability and accessibility." -whats_next: - - image_path: images/learn-graphics/github-collaboration.png - alt: "A graphic with a light yellow background that says essential collaboration skills for scientists - using GitHub. On the right there is a man and a woman sitting at a tall table with laptops working." - title: "Lessons: Collaborative GitHub for Scientists" - excerpt: | - Support for this track comes in part from the [Better Scientific Software Fellowship](https://bssw.io/pages/bssw-fellowship-program). These lessons live in the [pyOpenSci lessons book](https://www.pyopensci.org/lessons/) and teach **GitHub collaboration** for open source and team science. - - Read [social etiquette in open source](https://www.pyopensci.org/lessons/contribute-open-source/social-open-source.html), then work through the contribute path: - - 1. [Your contributing path](https://www.pyopensci.org/lessons/contribute-open-source/your-first-contribution.html) - 2. [Get to know the repo](https://www.pyopensci.org/lessons/contribute-open-source/get-to-know-repo.html) - 3. [Find an issue](https://www.pyopensci.org/lessons/contribute-open-source/identify-issue.html) - 4. [Fork a GitHub repository](https://www.pyopensci.org/lessons/contribute-open-source/fork-repo.html) - 5. [Edit and commit files](https://www.pyopensci.org/lessons/contribute-open-source/edit-commit-files.html) - 6. [Submit a pull request](https://www.pyopensci.org/lessons/contribute-open-source/pull-request.html) - - **Work locally:** [Clone a GitHub repository](https://www.pyopensci.org/lessons/contribute-open-source/clone-repo.html) - - **Background:** [What is Git/GitHub?](https://www.pyopensci.org/lessons/contribute-open-source/what-is-git-github.html), [GitHub Codespaces](https://www.pyopensci.org/lessons/contribute-open-source/github-codespaces.html), [Ways to contribute](https://www.pyopensci.org/lessons/contribute-open-source/ways-to-contribute.html) - url: https://www.pyopensci.org/lessons/contribute-open-source/index.html - btn_label: Open the collaboration lessons - btn_class: btn--primary btn--large -toc: false ---- - -
- -## A tools and resources hub for Python packaging and collaborative open science - -pyOpenSci is committed to supporting scientists as they leverage open source to -solve the world’s most complex challenges. From beginners who hope to build an -installable Python package, contributors ready to master core GitHub -collaboration skills to experts looking to streamline packaging maintenance -workflows, our educational hub is a trusted resource for gaining the skills -needed to scale your project. - - -Are you interested in cohort learning options? Visit our [university research communities and labs accelerator programs](/learn-universities-labs.html) page to learn more. -{: .notice .notice--measure .notice--success} - -
- -{% include pyos-flow-feature.html id="development" type="left" purple=true %} - -{% include div_purple_bottom.html %} - -
- -## Start learning: beginner-friendly Python packaging tutorials - -Beginner-friendly Python packaging tutorials guide you through creating a Python package, following modern best practices. It's best to follow the tutorials in order, but you can always pick a specific topic if you wish to jump around. - -
-{% for atutorial in site.tutorials %} - {% include tutorial-grid.html %} -{% endfor %} -
- -
- -{% include div_purple_top.html %} - -{% include pyos-flow-feature.html id="whats_next" type="left" purple=true %} - -{% include div_purple_bottom.html %} - -
- -## Expert-Led Training & Community Events - -pyOpenSci runs comprehensive training events that equip scientists with the critical skills needed to make their research software more open, reliable, and collaborative. - -When you attend our events, you experience the value of learning directly from leaders in the open-source space. Over the years, we have fostered a welcoming, supportive and diverse community where you can collaborate alongside cross-disciplinary peers facing similar challenges. From expert-led workshops to our highly collaborative sprint events, the connections you make and the skills you build here will transform your workflow. - -
- - - An image of people sitting in a conference room and smiling. - -
- -[Explore upcoming events](/events.html){: .btn .btn--success} -
- -{% include pyos-flow-feature.html id="scientists" type="center" %} diff --git a/_pages/packaging-resources.md b/_pages/packaging-resources.md deleted file mode 100644 index b0237150..00000000 --- a/_pages/packaging-resources.md +++ /dev/null @@ -1,116 +0,0 @@ ---- -layout: splash -classes: flowing -permalink: /python-packaging-science.html -title: "Python packaging resources for scientists" -excerpt: "We make the Python package ecosystem easier to navigate through peer review and packaging resources." -header: - overlay_image: images/headers/pyopensci-software.png - overlay_filter: 0.8 -intro: - - excerpt: "There are many tools and approaches that can be used to create a Python package. We build resources that help you both understand the tool ecosystem and also learn how to create a Python package using modern best practices." -peer-review: - - image_path: images/pyopensci-people-at-computer.jpg - alt: "A pencil sketch of a round table with people from different backgrounds sitting around it, working on laptops and also writing together." - excerpt: "We run an open peer review process for Python software. Peer review helps maintainers improve the quality, usability and long-term maintainability of scientific software." - title: Raising the bar for Python software" - url: https://www.pyopensci.org/about-peer-review/index.html - btn_label: Learn more and get involved with peer review today. - btn_class: btn--primary -packaging-easier: - - image_path: images/landing-pages/pyopensci-working-together.png - alt: "A black and grey sketch of a group of people sitting at a desk in front of a monitor smiling." - title: "Get involved: Help us improve our Python packaging resources" - excerpt: " - We need your help! Our packaging content is community-created and reviewed through an open review process on GitHub. The more feedback that we get, the more useful our resources are to the community. Get involved by: - - * Opening an issue about problems that you find in our guidebook - - * Submitting a pull request that fixes a typo or mistake in the guide. - - * Get credit for your contribution - - - All contributions are recognized both on our website and in the guidebook's citation. - " - url: https://github.com/pyOpenSci/python-package-guide/pulls - btn_label: See what we're working on now - btn_class: btn--success -community-created: - - image_path: images/landing-pages/people-working-together.png - image_size: 500px - alt: "A group of people working on a tetris like set of building blocks, trying to stack the blocks together. " - title: "Community-created Python Packaging Guide" - url: https://www.pyopensci.org/python-package-guide/ - btn_label: Explore out our Python packaging guide. - btn_class: btn--primary - excerpt: " - Our packaging guide is a living document that will help you navigate the Python packaging ecosystem and learn about modern Python packaging best practices. - - * Created & curated by the community - - * Reviewed by beginner to expert level Pythonistas - - * Accurate, modern & beginner-friendly - - - All contributions are recognized both on our website and in the guidebook's citation. - " -toc: false ---- - -{% include feature_row id="intro" type="center" %} - -
-
- -{% include feature_row id="community-created" type="right" %} - -
-### pyOpenSci Python packaging guidebook sections - -
-{% for atutorial in site.packaging %} - {% include tutorial-grid.html %} -{% endfor %} -
- -
-
- -
-
- -{% include div_purple_bottom.html %} - -
-
- -## Beginner friendly Python packaging tutorials - -Beginner-friendly Python packaging tutorials that will take you through -the full process of creating a Python package, following modern best -practices. - -
-{% for atutorial in site.tutorials %} - {% include tutorial-grid.html %} -{% endfor %} -
- -
- -
-
- -{% include div_purple_top.html %} - -
-
-{% include feature_row id="packaging-easier" type="left" %} - -
-
- - -{% include div_purple_bottom.html %} diff --git a/_pages/partners.md b/_pages/partners.md deleted file mode 100644 index 6b91ac3d..00000000 --- a/_pages/partners.md +++ /dev/null @@ -1,183 +0,0 @@ ---- -layout: splash -permalink: /partners.html -title: "pyOpenSci Community Partners: affiliated package review" -classes: flowing -header: - overlay_image: images/headers/pyos-peer-review-header.png -intro: - - excerpt: "More here on community partners." -scientists: - - image_path: images/landing-pages/peer-review-people.png - alt: "A pencil sketch of a round table with people sitting around it from different backgrounds working on laptops and also writing together." - excerpt: "Our catalog of vetted open source tools makes it easier for scientists to find the trusted tools that they need to develop their open science workflows." - title: "Help scientists find the Python software tools that they need" - url: https://www.pyopensci.org/python-packages.html - btn_label: Explore our accepted Python packages - btn_class: btn--primary -mission: - - excerpt: "Domain-specific communities can partner with PyOpenSci to leverage both our peer review process and development of Python packaging packaging guidelines." -packaging-easier: - - image_path: images/people-building-blocks.jpg - alt: "A group of people working on a tetris like set of building blocks trying to stack them all together. " - title: "Let's make packaging easier for scientists, together" - excerpt: "Given the numerous diverse packaging options, many communities are crafting their own guidelines and review processes. Partnering with pyOpenSci enables your community to: - - * tap into and contribute to an established community-owned set of packaging guidelines. - - * mitigate the redundancy of multiple communities creating their own processes and standards. - - - Aligning packaging criteria supports increased consistency in packaging approaches across the Python ecosystem. This consistency will lower the barrier of entry for new potential contributors." - url: https://www.pyopensci.org/python-package-guide - btn_label: View our Python packaging guide - btn_class: btn--primary - -community: - - image_path: images/landing-pages/women-collaborating.png - alt: "A black and white pencil sketch of a group of 5 women sitting and standing around a laptop working together." - excerpt: "pyOpenSci is driven by a **vibrant community** of technical packaging experts, open science enthusiasts, and Pythonistas. When you partner with pyOpenSci, you: - - * Become part of a community rich in expertise, ready to assist you with your packaging challenges. - - * As a package grows, we help your community maintainers decide what to do if they need to step down from maintenance roles, be it finding a new maintainer or gracefully archiving the package for safekeeping." - title: "Harness the power of community" - url: https://www.pyopensci.org/our-community/index.html - btn_label: Explore our community contributors - btn_class: btn--primary -partnerships: - - image_path: /images/peer-review/peer-review-partners-process.png - alt: "diagram with a set of 3 boxes with arrows pointing to the right. The first box says submit your package with a code icon above it. The second says Review with pyOpensci Standards below. Blow the second box is a dark purple box that says Your community standards in it. And an arrow pointing to that box that says customized review. The third box says Accepted pyos + community affiliated." - excerpt: " -pyOpenSci adds an [extra layer of community-specific review](https://www.pyopensci.org/software-peer-review/partners/scientific-communities.html) to our established open peer review process. This allows domain-specific scientific Python communities to vet affiliated tools through our robust peer review process. Communities then don't have to develop and maintain their own review processes and software guidelines." - title: "Community Partnerships Streamline Peer Review & Packaging Standards" - url: https://www.pyopensci.org/software-peer-review/partners/scientific-communities.html - btn_label: Learn more about how community partnerships work. - btn_class: btn--primary -partnership_benefits: - - image_path: /images/peer-review/pyos-partnerships-peer-review.png - alt: "Diagram with a light purple background. On the left there is the pyOpenSci purple flower and it says accepted with a check mark above. There are two arrows leading to boxes on the right. The top box says JOSS published with a check next to it and the JOSS logo. The box below says Community Affiliated with a check. THe boxes are numbered 1,2,3. " - excerpt: " -Through a single review process, community maintainers: - -* Have their package accepted into the pyOpenSci ecosystem - -* Can be [published in JOSS (if in scope)](https://www.pyopensci.org/software-peer-review/partners/joss.html#) - -* Can become an affiliated package with your community, following your community guidelines. - -* Are supported in maintaining their tools, with input from our diverse, knowledgeable community, including active members from across the Python, Conda, PyPA and broader Python packaging ecosystem." - title: "Benefits of partnering with us" -toc: false ---- - -{% include feature_row id="mission" type="center" %} - -
-
- -{% include feature_row id="partnerships" type="right" %} -{% include feature_row id="partnership_benefits" type="right" %} - -
-
- -{% include div_purple_bottom.html %} - -
-
- -## Partnering with pyOpenSci increases the visibility of your communities' tools - -We will: - -- Promote your community and its packages on our website. -- Post about your affiliated packages on our social media channels. -- List your package on our [packages page](https://www.pyopensci.org/python-packages.html). -- Provide a feed allowing you to cross-list affiliated packages on your website. -- Keep in touch with maintainers to ensure packages are actively maintained. -
-
- -{% include div_purple_top.html %} - -
-
-{% include feature_row id="scientists" type="left" %} -
-
- - -{% include div_purple_bottom.html %} - -
-
- - - -## Leverage our peer review process - -Your science domain-specific community can take advantage of our existing, developed and well-documented peer review process which includes: - -* **A growing team of editors from a diverse set of scientific and technical backgrounds.** We ask partner communities to have at least 2 members of their community on our editorial team. We want our partner community to make the final decision on whether a package should receive the label of being affiliated. Typically an editor will run 2-4 reviews a year. [Learn more about the editor role.](https://www.pyopensci.org/software-peer-review/how-to/editors-guide.html) - -* **Community reviewers whose expertise spans numerous scientific domains.** We ask that partner communities help us find reviews in their specific domain areas as packages come in for review. [Learn more about the requirements for reviewers.](https://www.pyopensci.org/software-peer-review/how-to/reviewer-guide.html) - -* **Community driven packaging resources and guidelines:** Our packaging guidelines drive the peer review process. Instead of communities creating their own individual guidelines, pyOpenSci partners collaborate with us to expand packaging resources." - -[Learn more about our partnerships](https://www.pyopensci.org/software-peer-review/partners/scientific-communities.html){: .btn .btn--primary } - - -
-
- -{% include div_purple_top.html %} - -
-
- -{% include feature_row id="packaging-easier" type="left" %} - -
-
- -{% include div_purple_bottom.html %} - -
-
-{% include feature_row id="community" type="right" %} -
-
- -
-
- -## Raising the bar for scientists developing Python software - -More packages going through our review process means -we're all working together to raise the bar in terms of -quality, documentation and usability of scientific software. - -By reviewing packages from the entire ecosystem, pyOpenSci can identify redundancy in package functionality and areas where maintainers could benefit from consolidation efforts. - -
-
- -
-
-## Why do so many domain specific Python communities exist? - -Within the Python ecosystem, various science-domain-specific communities are creating peer review processes to evaluate community-affiliated tools. - -- A response to the need to vet and maintain a list of high-quality tools that their communities embrace -- A need to track software maintenance to ensure ecosystem tools in their vetted lists are maintained -- The need for packaging guidance given a complex packaging ecosystem filled with numerous tools and options - -However, creating a review process for software requires significant effort. -pyOpenSci was created to target these exact issues in support of open science. - -[Astropy](https://www.astropy.org/) is most mature of the domain-specific communities that we work with, having built its peer review process in 2013. -{: .notice } - -
-
diff --git a/_pages/python-packages.md b/_pages/python-packages.md deleted file mode 100644 index c7ce89b1..00000000 --- a/_pages/python-packages.md +++ /dev/null @@ -1,74 +0,0 @@ ---- -layout: splash -permalink: /python-packages.html -title: "pyOpenSci Open Peer Review & Accepted Packages" -classes: -header: - overlay_image: images/headers/pyopensci-software.png -intro: - - excerpt: "The Python packages below have - been accepted into the pyOpenSci ecosystem through our [open software peer review process](/about-peer-review/)." -toc: false -classes: wide -redirect_from: - - /python-packages/ ---- - -{% include feature_row id="intro" type="center" %} - -{% assign packages_sorted = site.data.packages | sort_natural: 'date_accepted' | reverse %} -{% assign total_packages = packages_sorted | size %} - -## Explore our accepted open source Python packages - -To date, {{ total_packages }} packages have been accepted by pyOpenSci through our [open peer review process](https://www.pyopensci.org/about-peer-review/index.html). You can [check out the packages that are currently under review in our GitHub submission repository.](https://github.com/pyOpenSci/software-submission/issues) - -[Click here to learn more about the process for submitting a package to pyOpenSci.](https://www.pyopensci.org/software-peer-review/how-to/author-guide.html){: .btn .btn--success } - -### Community Partners - -To view packages affiliated with our partner communities that are a part of ourpeer review partnership program, click on a community link below. [Learn more about our community partnerships here.](/partners.html) - - - -### Accepted pyOpenSci packages - -

- -
- - - - - - - - -
- - -
-
-{% for apackage in packages_sorted %} - {% if apackage.active == true %} - {% include package-grid.html isotope=true %} - {% endif %} -{% endfor %} -
- -#### Archived Packages - -Archived packages are packages that have successfully completed [pyOpenSci's software peer review process](https://www.pyopensci.org/about-peer-review/index.html) but are no longer maintained. [Check out our software maintenance policy for how we determine when a package becomes archived.](https://www.pyopensci.org/software-peer-review/our-process/policies.html#package-maintenance-and-maintainer-responsiveness) - - -
-
-{% for apackage in packages_sorted %} - {% if apackage.active == false %} - {% include package-grid.html isotope=true %} - {% endif %} -{% endfor %} -
- -
-
diff --git a/_pages/resources.md b/_pages/resources.md deleted file mode 100644 index 353cb9ee..00000000 --- a/_pages/resources.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -layout: single -title: "pyOpenSci Resources" -author_profile: false -published: true -site-map: true -permalink: /resources.html ---- - -We are currently working on all of our organization-wide documentation and -governance. The guidebook links below are being updated and we welcome feedback! - -* [**pyOpenSci Github Organization**](https://github.com/pyOpenSci): Contains our [software-review](https://github.com/pyOpenSci/software-review) repo that hosts the peer-review process. Also contains our guidebook, cookiecutter, and handbook repos (see below). - -* [**pyOpenSci Peer Review**](https://www.pyopensci.org/software-peer-review/): Contains information for package authors and reviewers. - -* [**pyOpenSci's Handbook**](https://www.pyopensci.org/handbook/): Where we discuss the direction and scope of pyOpenSci. We're a community organization, so please feel free to create an [issue](https://github.com/pyOpenSci/handbook/issues) if you have any questions or concerns! diff --git a/_pages/tutorials.md b/_pages/tutorials.md deleted file mode 100644 index 990e160e..00000000 --- a/_pages/tutorials.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -title: Scientific Python tutorials -layout: collection -permalink: /tutorials/ -collection: portfolio -entries_layout: grid -classes: wide ---- diff --git a/_pages/volunteer.md b/_pages/volunteer.md deleted file mode 100644 index cd876516..00000000 --- a/_pages/volunteer.md +++ /dev/null @@ -1,133 +0,0 @@ ---- -layout: splash -classes: flowing -title: "Get involved with pyOpenSci" -author_profile: false -published: true -site-map: true -permalink: /volunteer.html -header: - overlay_image: images/headers/pyopensci-learn-header.png - overlay_filter: 0.3 -volunteer-mission: - - excerpt: "pyOpenSci is a volunteer community that broadens participation in scientific open source. We make finding, sharing and contributing to reusable code easier for everyone, everywhere." -build-skills: - - title: "pyOpenSci volunteers build skills and community" - excerpt: "When you volunteer with pyOpenSci, you’re both giving back and developing professional skills. As a volunteer you will: - -- **Learn new skills:** you don’t have to be a Python expert to get involved with pyOpenSci. We can help you level up your packaging game and learn how to constructively review both code and copy through contributions to our online learning resources. - -- **Get recognized** on the [pyOpenSci website](https://www.pyopensci.org/our-community/index.html) and in our [GitHub repositories](https://github.com/pyOpenSci): your contribution matters, and we want to ensure your work is recognized and celebrated in a public forum. If you serve as an editor you can also connect with pyOpenSci professionally as a volunteer for our organization on LinkedIn" - image_path: /images/people-building-blocks.jpg -diverse-backgrounds: - - title: "pyOpenSci volunteers come from diverse backgrounds" - excerpt: "Our volunteers come from a diverse array of backgrounds, including industry, academia, agencies, national labs, and more. pyOpenSci volunteers are primarily engaged in both the peer review process and developing resources to support the scientific Python community. Volunteers help improve the quality, maintainability and usability of the software that scientists need for open science. They also support maintainers in developing scientific Python software." - image_path: /images/people/pyopensci-sprint-pycon-2023.png -help-us: - - image_path: - title: "Lend a Hand on GitHub" - alt: - excerpt: "Got some time to help? Check out our GitHub Project Board for a list of current issues that we could use help with. Any issue that is tagged `help-wanted` in our repos is also fair game for anyone to tackle! We add anyone who contributes to pyOpenSci to our [community page](/our-community/). " - url: https://github.com/orgs/pyOpenSci/projects/3/views/1 - btn_label: "> Check out our GitHub Help Wanted Board" - btn_class: btn--inverse - - image_path: - title: "Sign up to review a Python package" - alt: - excerpt: "We are always looking for new reviewers from a broad range of scientific domains. Some reviewers have extensive packaging expertise and others have domain expertise or focus on package usability. If you are new to reviewing we are happy to support you through our peer review mentorship program. [Learn more about the reviewer role](https://www.pyopensci.org/software-peer-review/how-to/reviewer-guide.html) and sign up using the link below." - url: https://forms.gle/GHfxvmS47nQFDcBM6 - btn_label: "> Sign up now." - btn_class: btn--inverse - - image_path: - title: "Get involved as software peer review Editor" - alt: - excerpt: "We also often recruit new editors to support our peer review process. " - url: https://www.pyopensci.org/software-peer-review/how-to/editors-guide.html - btn_label: "> Click here to learn more about the editor role." - btn_class: btn--inverse ---- - -{% include feature_row id="volunteer-mission" type="center" %} - - -
-
- -## Volunteer opportunities with pyOpenSci - -There are many ways to get involved with pyOpenSci! We’re always looking for folks to: - -{% include feature_row_pyos id="help-us" %} - - -
-
- -{% include div_purple_bottom.html %} - - -
-
- -## Share your expertise and experience with the broader community through blogging - -And last but not least, we’d also love for you to be a guest blogger on the [pyOpenSci blog](https://www.pyopensci.org/blog/index.html)! If you’d like to write about a pyOpenSci package, your experiences with pyOpenSci, or how you’re using free and open Python tools in your scientific endeavors, we’d love to hear from you! Email our Community team at [media@pyopensci.org](mailto:media@pyopensci.org) for more information. - - - -
-
- - -
-
- -{% include feature_row id="diverse-backgrounds" type="left" %} - -
-
- -{% include div_purple_bottom.html %} - - -
-
- -{% include feature_row id="build-skills" type="right" %} - -
-
- - -{% include div_purple_top.html %} - -
-
- -## Meet the most recent PyOpenSci community contributors - - -{% assign new_ppl = site.data.contributors | reverse %} - -{% assign total_people = new_ppl | size %} - -pyOpenSci has a diverse and vibrant community of pythonistas! To date, -**{{ total_people }}** wonderful people have contributed to pyOpenSci. - -
-{% for aperson in new_ppl limit:3 %} - {% include people-grid.html %} -{% endfor %} -
- -
-
- -View All Contributors - - -
-
- - -{% include connect-with-pyos.html %} diff --git a/_pages/ways-to-give.md b/_pages/ways-to-give.md deleted file mode 100644 index 57268192..00000000 --- a/_pages/ways-to-give.md +++ /dev/null @@ -1,152 +0,0 @@ ---- -layout: splash -classes: flowing -title: "Invest in the Future of Scientific Open Source" -author_profile: false -published: true -site-map: true -permalink: /ways-to-give.html -header: - overlay_color: "#33205c" - overlay_filter: 0.3 ---- - -
- -By supporting pyOpenSci, you are directly investing in the sustainability of the -open-source Python ecosystem that modern businesses, universities, and -researchers rely on. A healthy, diverse open-source community is critical to -ensuring the long-term health and accessibility of the fundamental tools that -enable productivity and innovation at scale. - -Whether you are a company looking to align your brand with industry leaders, or -an individual funder passionate about open science, we offer clear pathways for -you to support our mission. - -
- - - A group of contributors sitting at a table at a pyOpenSci sprint working on their laptops and talking to each other. - -
- -## For companies: corporate sponsorship - -By sponsoring pyOpenSci, your company directly expands high-impact, expert-led -training programs that build a stronger pipeline of diverse talent in the -scientific open-source space. Your support enables us to deliver rigorously -reviewed educational resources, ultimately fostering a more resilient and -informed ecosystem that scales scientific discovery. - -### Benefits of contributing - -
    -
  • - - Long-term impact. - Open source development and the critical infrastructure it supports are - rapidly evolving due to the impacts of generative AI. pyOpenSci is uniquely - positioned to address these challenges, and our sponsors will be at the - forefront of this evolution, directly supporting a mission that shapes the - future of open source tools. -
  • -
  • - - Brand awareness. - Your brand will benefit from multi-channel promotion integrated directly into - our evolving content strategy, which spans our primary website, - organizational and personal social platforms, dedicated mailing lists, and - an upcoming asynchronous course platform tailored for universities. -
  • -
  • - - Target audience. - Expose your brand to practitioners who don't just use tools—they have high - visibility to those who develop them, teach them, and shape how research - communities adopt new workflows. -
  • -
- -Sponsorship funds directly support our mission to make scientific Python more -accessible. From special programs to scholarships, your sustained support -ensures our scientific infrastructure remains a crucial, valuable resource. - -### Example sponsorship tiers - -Tiers vary by organization; the table below illustrates how support can align -with different goals. Contact us to design a package that fits your team. - -| Tier | Example focus | Example visibility & benefits | -| --- | --- | --- | -| **Ecosystem catalyst** | Companies ready to lead in supporting open source research software | Premier placement across channels; tailored activations such as mentored sprints and dedicated office hours; strategic collaboration with pyOpenSci leadership | -| **Momentum driver** | Teams growing their scientific Python footprint | Sustained visibility on the website and in mailings; alignment with webinars and community programs; connection to our cross-disciplinary network | -| **Foundation builder** | Organizations beginning to invest in open science education | Recognition alongside training and course materials; support for scholarships and learner access; pathway to deeper partnership | - -Inquire to learn more - -
- -{% comment %} -
-
- -### A learner perspective - -
-
- Leah Wasser, Executive Director and Founder of pyOpenSci, smiling in a professional headshot outdoors with trees and water in the background. -
-{% include pyos-blockquote.html quote="Absolutely honored to have had the opportunity to contribute to the amazing pyOpenSci open-source project at my first-ever #PyConUS24 back in May! 🙌 As a total newbie, I was blown away by how warm and welcoming the pyOpenSci team was—they made diving into my first open-source contribution so much less intimidating (even if it was just documentation)! 😅 It's been an incredible experience to see firsthand how collaboration can drive innovation in the Python community, and I'm excited to keep learning and contributing." author="Brianne Wilhelmi" event="Software Engineer" class="highlight purple wide highlight-quote--split" %} -
-
-
-{% endcomment %} - -
-
- -## For funders & community: direct donations - -The scientific research community is experiencing unparalleled challenges. From -shifting federal and grant funding to the rise of generative AI, the need to -support science and the tools that drive innovation at scale has never been -greater. - -Every donation—whether a one-time gift or a recurring contribution—directly -empowers the individuals and structures propelling open science forward. - -### What your investment enables - -
    -
  • - - Sustaining core operations. - Your funding directly supports our infrastructure, community support, and - the leadership required to run our programs. -
  • -
  • - - Rewarding volunteer contributions. - We provide volunteer stipends that keep our vital open-source software peer - review running. -
  • -
  • - - Developing trusted training materials. - We build rigorously reviewed tutorials and training materials that support - better software development. -
  • -
  • - - Expanding access. - Your donations fund scholarships to our asynchronous courses, expanding - access to world-class education while sustaining pyOpenSci's mission. -
  • -
- -Donate today - -
-
diff --git a/_posts/2019-10-25-welcome-to-pyopensci.md b/_posts/2019-10-25-welcome-to-pyopensci.md deleted file mode 100644 index 044066cd..00000000 --- a/_posts/2019-10-25-welcome-to-pyopensci.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -layout: single -title: "Welcome to the pyOpenSci Blog!" -blog_topic: updates -excerpt: "pyOpenSci is a community organization devoted to the development of robust open source Python software to support science." -author: "Leah Wasser" -permalink: blog/welcome-to-pyopensci/ -header: - overlay_color: "#33205c" -categories: - - updates - - blog-post ---- - -## Welcome To PyOpenSci! - -This is just a short post to let everyone know that we are starting a community blog post. Here we will write about many things including: - -1. Packages that have gone through the review process. -2. Challenges that PyOpenSci is confronting in the open source Python community -3. Activities and events going on - -and more. - -Stay tuned for more in the upcoming months! diff --git a/_posts/2019-10-26-pyos-min-mistakes-theme.md b/_posts/2019-10-26-pyos-min-mistakes-theme.md deleted file mode 100644 index 0b074d91..00000000 --- a/_posts/2019-10-26-pyos-min-mistakes-theme.md +++ /dev/null @@ -1,460 +0,0 @@ ---- -layout: single -title: "pyOS Website Theme Docs" -excerpt: "pyOpenSci minimal mistakes theme documentation." -author: "pyopensci" -permalink: docs/pyos-website/ -header: - overlay_color: "#33205c" -last_modified: 2025-03-24 -toc: true -categories: - - docs - - blog-post -classes: wide ---- - - -## Text alignment - -Align text blocks with the following classes. - -Left aligned text `.text-left` -{: .text-left} - -```markdown -Left aligned text -{: .text-left} -``` - ---- - -Center aligned text. `.text-center` -{: .text-center} - -```markdown -Center aligned text. -{: .text-center} -``` - ---- - -Right aligned text. `.text-right` -{: .text-right} - -```markdown -Right aligned text. -{: .text-right} -``` - ---- - -**Justified text.** Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque vel eleifend odio, eu elementum purus. In hac habitasse platea dictumst. Fusce sed sapien eleifend, sollicitudin neque non, faucibus est. Proin tempus nisi eu arcu facilisis, eget venenatis eros consequat. -{: .text-justify} - -```markdown -**Justified text.** Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque vel eleifend odio, eu elementum purus. In hac habitasse platea dictumst. Fusce sed sapien eleifend, sollicitudin neque non, faucibus est. Proin tempus nisi eu arcu facilisis, eget venenatis eros consequat. -{: .text-justify} -``` - ---- - -## Figures and figure alignment - -Position images with the following classes. - -### A regular figure with no alignment specified - -
- - - A timeline showing the growth of pyOpenSci from 2019 to 2024. -
Growth of pyOpenSci from 2019 to 2024. Growth of pyOpenSci from 2019 to 2024. Growth of pyOpenSci from 2019 to 2024. Growth of pyOpenSci from 2019 to 2024.
-
-
- -The code for the figure above is below: - -```html -
- - - A timeline showing the growth of pyOpenSci from 2019 to 2024. -
Growth of pyOpenSci from 2019 to 2024
-
-
-``` - -### Figure -- align center - -
- - - A timeline showing the growth of pyOpenSci from 2019 to 2024. -
Growth of pyOpenSci from 2019 to 2024. Growth of pyOpenSci from 2019 to 2024. Growth of pyOpenSci from 2019 to 2024. Growth of pyOpenSci from 2019 to 2024.
-
-
- -The image above is **centered** using `align-center`. - -```html -
- - - A timeline showing the growth of pyOpenSci from 2019 to 2024. -
Growth of pyOpenSci from 2019 to 2024
-
-
-``` - -### Figure -- align left - -
- - - A timeline showing the growth of pyOpenSci from 2019 to 2024. -
Growth of pyOpenSci from 2019 to 2024 Growth of pyOpenSci from 2019 to 2024 Growth of pyOpenSci from 2019 to 2024 Growth of pyOpenSci from 2019 to 2024
-
-
- - -The rest of this paragraph is filler for the sake of seeing the text wrap around the 150×150 image, which is **left aligned**. There should be plenty of room above, below, and to the right of the image. Just look at him there --- Hey guy! Way to rock that left side. I don't care what the right aligned image says, you look great. Don't let anyone else tell you differently. - -```html -
- - - A timeline showing the growth of pyOpenSci from 2019 to 2024. -
Growth of pyOpenSci from 2019 to 2024
-
-
-``` - -You can also adjust the width inline: - -```html -
- - - A timeline showing the growth of pyOpenSci from 2019 to 2024. -
Growth of pyOpenSci from 2019 to 2024
-
-
-``` - -### Figure -- align right - -Below is a right aligned image - -
- - - A timeline showing the growth of pyOpenSci from 2019 to 2024. -
Growth of pyOpenSci from 2019 to 2024 Growth of pyOpenSci from 2019 to 2024 Growth of pyOpenSci from 2019 to 2024 Growth of pyOpenSci from 2019 to 2024
-
-
- -And now we're going to shift things to the **right align**. Again, there should be plenty of room above, below, and to the left of the image. Just look at him there --- Hey guy! Way to rock that right side. I don't care what the left aligned image says, you look great. Don't let anyone else tell you differently. - -The html looks like this: - - -```html -
- - - A timeline showing the growth of pyOpenSci from 2019 to 2024. -
Growth of pyOpenSci from 2019 to 2024 Growth of pyOpenSci from 2019 to 2024 Growth of pyOpenSci from 2019 to 2024 Growth of pyOpenSci from 2019 to 2024
-
-
-``` - -### Figure -- full extending outside of the current area - -The image below should extend outside of the parent container on right. - - -
- - - A timeline showing the growth of pyOpenSci from 2019 to 2024. -
Growth of pyOpenSci from 2019 to 2024 Growth of pyOpenSci from 2019 to 2024 Growth of pyOpenSci from 2019 to 2024 Growth of pyOpenSci from 2019 to 2024
-
-
- - -```html -
- - - A timeline showing the growth of pyOpenSci from 2019 to 2024. -
Growth of pyOpenSci from 2019 to 2024 Growth of pyOpenSci from 2019 to 2024 Growth of pyOpenSci from 2019 to 2024 Growth of pyOpenSci from 2019 to 2024
-
-
-``` - -## Blockquote styles - -### Regular blockquote - -```markdown -> This entire effort underscores the power of community when guided in -> the right direction, showcasing how collective effort can drive -> meaningful progress. -{: .highlight-quote } -``` - -> This entire effort underscores the power of community when guided in -> the right direction, showcasing how collective effort can drive -> meaningful progress. - - -### Fancy blockquote - -```markdown -> This entire effort underscores the power of community when guided in -> the right direction, showcasing how collective effort can drive -> meaningful progress. -{: .highlight-quote } -``` - -> This entire effort underscores the power of community when guided in -> the right direction, showcasing how collective effort can drive -> meaningful progress. -{: .highlight-quote } - - -### Fancy blockquote magenta & purple variants - -```markdown -> This entire effort underscores the power of community when guided in -> the right direction, showcasing how collective effort can drive -> meaningful progress. -{: .highlight-quote .magenta } -``` - -> This entire effort underscores the power of community when guided in -> the right direction, showcasing how collective effort can drive -> meaningful progress. -{: .highlight-quote .magenta } - - -```markdown -> This entire effort underscores the power of community when guided in -> the right direction, showcasing how collective effort can drive -> meaningful progress. -{: .highlight-quote .purple } -``` - -> This entire effort underscores the power of community when guided in -> the right direction, showcasing how collective effort can drive -> meaningful progress. -{: .highlight-quote .purple } - - -### Quotes using include files - -You can also create blockquotes using include files. -Below is a green and magenta version of the same quote - -````html -{% raw %} -{% include pyos-blockquote.html quote="[*I want to... *] Streamline the development of good quality, socially responsible, and easily shareable software." author="Anonymous" event="AGU 2019 Townhall" class="highlight magenta" %} -{% endraw %} -```` - -{% include pyos-blockquote.html quote="[*I want to... *] Streamline the development of good quality, socially responsible, and easily shareable software." author="Anonymous" event="AGU 2019 Townhall" class="highlight magenta" %} - -```markdown -{% raw %} -{% include pyos-blockquote.html quote="[*I want to... *] Streamline the development of good quality, socially responsible, and easily shareable software." author="Anonymous" event="AGU 2019 Townhall" class="highlight purple" %} -{% endraw %} -``` - -{% include pyos-blockquote.html quote="[*I want to... *] Streamline the development of good quality, socially responsible, and easily shareable software." author="Anonymous" event="AGU 2019 Townhall" class="highlight purple" %} - -The default color for the quotes is the pyos teal green. - -``` -{% raw %} -{% include pyos-blockquote.html quote="[*I want to... *] Streamline the development of good quality, socially responsible, and easily shareable software." author="Anonymous" event="AGU 2019 Townhall" class="highlight" %} -{% endraw %} -``` - -{% include pyos-blockquote.html quote="[*I want to... *] Streamline the development of good quality, socially responsible, and easily shareable software." author="Anonymous" event="AGU 2019 Townhall" class="highlight" %} - -### Quotes in notice blocks - -``` -
-{% raw %} -{% include pyos-blockquote.html quote="[*I want to... *] Streamline the development of good quality, socially responsible, and easily shareable software." author="Anonymous" event="AGU 2019 Townhall" class="highlight" %} -{% endraw %} -
-``` - -
-{% include pyos-blockquote.html quote="[*I want to... *] Streamline the development of good quality, socially responsible, and easily shareable software." author="Anonymous" event="AGU 2019 Townhall" class="highlight" %} - -
- -## Buttons - -Make any link standout more when applying the `.btn .btn--primary` classes. - -```html -Link Text -``` - -| Button Type | Example | Class | Kramdown | -| ------ | ------- | ----- | ------- | -| Default | [Text](#link){: .btn} | `.btn` | `[Text](#link){: .btn}` | -| Primary | [Text](#link){: .btn .btn--primary} | `.btn .btn--primary` | `[Text](#link){: .btn .btn--primary}` | -| Success | [Text](#link){: .btn .btn--success} | `.btn .btn--success` | `[Text](#link){: .btn .btn--success}` | -| Warning | [Text](#link){: .btn .btn--warning} | `.btn .btn--warning` | `[Text](#link){: .btn .btn--warning}` | -| Danger | [Text](#link){: .btn .btn--danger} | `.btn .btn--danger` | `[Text](#link){: .btn .btn--danger}` | -| Info | [Text](#link){: .btn .btn--info} | `.btn .btn--info` | `[Text](#link){: .btn .btn--info}` | -| Inverse | [Text](#link){: .btn .btn--inverse} | `.btn .btn--inverse` | `[Text](#link){: .btn .btn--inverse}` | -| Light Outline | [Text](#link){: .btn .btn--light-outline} | `.btn .btn--light-outline` | `[Text](#link){: .btn .btn--light-outline}` | - -| Button Size | Example | Class | Kramdown | -| ----------- | ------- | ----- | -------- | -| X-Large | [X-Large Button](#){: .btn .btn--primary .btn--x-large} | `.btn .btn--primary .btn--x-large` | `[Text](#link){: .btn .btn--primary .btn--x-large}` | -| Large | [Large Button](#){: .btn .btn--primary .btn--large} | `.btn .btn--primary .btn--large` | `[Text](#link){: .btn .btn--primary .btn--large}` | -| Default | [Default Button](#){: .btn .btn--primary} | `.btn .btn--primary` | `[Text](#link){: .btn .btn--primary }` | -| Small | [Small Button](#){: .btn .btn--primary .btn--small} | `.btn .btn--primary .btn--small` | `[Text](#link){: .btn .btn--primary .btn--small}` | - -## Call out boxes (notices) - -You can create a basic notice or call out box for a pargraph by adding `{: .notice}` to the line immediately below the paragraph. - - -```markdown -**Technical and social skills go hand in hand.** Open source communities are most productive when contributors and maintainers recognize this balance between the technical and social skills associated with contributing. -{: .notice} -``` -Which looks like this rendered: - -**Technical and social skills go hand in hand.** Open source communities are most productive when contributors and maintainers recognize this balance between the technical and social skills associated with contributing. -{: .notice} - -There are multiple classes that you can use to modify the background color of the call out block. - -| Notice Type | Class | -| ----------- | ----- | -| Default | `.notice` | -| Primary | `.notice--primary` | -| Info | `.notice--info` | -| Warning | `.notice--warning` | -| Success | `.notice--success` | -| Danger | `.notice--danger` | -| Measure | `.notice--measure` | - -### Readable width (`.notice--measure`) - -Use **`.notice--measure`** together with **`.notice`** (and optionally a color class such as `.notice--info`) on pages whose front matter includes **`classes: flowing`**. It caps the notice to a readable line length and centers it in the column—useful on full-bleed hubs such as [Learn](/learn.html), where a plain `.notice` would otherwise stretch edge to edge. - -```markdown -Short callout text matches the hero column width. -{: .notice .notice--measure } -``` - -Color variant example: - -```markdown -**Heads up:** details for people skimming the page. -{: .notice .notice--info .notice--measure } -``` - -The measure rules are defined under `.flowing` in the site CSS, so they do not apply on a normal single-column post unless you add `flowing` to that page’s `classes` list. - -**Watch out!** This paragraph of text has been emphasized with the `{: .notice--primary}` class. -{: .notice--primary} - -**Watch out!** This paragraph of text has been emphasized with the `{: .notice--info}` class. -{: .notice--info} - -**Watch out!** This paragraph of text has been emphasized with the `{: .notice--warning}` class. -{: .notice--warning} - -**Watch out!** This paragraph of text has been emphasized with the `{: .notice--success}` class. -{: .notice--success} - -**Watch out!** This paragraph of text has been emphasized with the `{: .notice--danger}` class. -{: .notice--danger} - -
-You can also add the `.notice` class to a `
` element. Notice below that `markdown="1"` allows you to use markdwon within the html div. - -```html -
-You can also add the .notice class to a
element. - -* Bullet point 1 -* Bullet point 2 -
-``` - -
- -To add a heading element to a notice block but ignore in the TOC, use `no_toc` as a class like this: - -```html -
-

Notice Headline:

- here is some html text. -
-``` - -Which will render like this - -
-

Notice Headline that won't appear in toc

- {{ notice-text | markdownify }}. - You can add more text here too -
- - -
-#### Notice Headline -{: .no_toc } - -Here is some html text. - -Here is a list -* item 1 -* item 2 -* item 3 - -Testing a div with a h4 header - -````html -
-#### Notice Headline -{: .no_toc } - -This is how you generate a div using markdown - -
-```` -
- - - -Using the Kramdown Markdown renderer with Jekyll allows you to add [block](http://kramdown.gettalong.org/quickref.html#block-attributes) and [inline attributes](http://kramdown.gettalong.org/quickref.html#inline-attributes). This is nice if you want to add custom styling to text and image, and still write in Markdown. - -**Jekyll 3:** Kramdown is the default for `jekyll new` sites and those hosted on GitHub Pages. Not using Kramdown? That's OK. The following classes are still available when used with standard HTML. -{: .notice--warning} - - -## YouTube - -You can use embeds to add a youtube video to a page. You can copy the id from the video url and add it to the include line below. The jekyll will do the rest for you! - -``` -{% raw %} -{% include video id="n9IZGT4DxDs" provider="youtube" %} -{% endraw %} -``` - -{% include video id="n9IZGT4DxDs" provider="youtube" %} diff --git a/_posts/2019-11-18-pandera-dataframe-validation.md b/_posts/2019-11-18-pandera-dataframe-validation.md deleted file mode 100644 index 77633851..00000000 --- a/_posts/2019-11-18-pandera-dataframe-validation.md +++ /dev/null @@ -1,263 +0,0 @@ ---- -layout: single -title: "Pandera: A Statistical Data Validation Toolkit for Pandas" -blog_topic: software -excerpt: "Pandera is a a flexible and expressive toolkit for performing statistical validation checks on pandas data structures that was recently accepted into the pyOpenSci ecosystem. Learn more about Pandera." -author: "Niels Bantilan" -permalink: /blog/pandera-python-pandas-dataframe-validation.html -header: - overlay_image: images/blog/headers/pandas.png - caption: "Photo credit: [**Ann Batdorf, Smithsonian's National Zoo**](https://www.flickr.com/photos/nationalzoo/5371290900/in/photostream/)" -categories: - - blog-post - - pandas - - data-validation - - dataframes - - highlight -comments: true ---- - -Modern data engineering and analysis workflows will often involve using data -manipulation libraries, which, in the Python universe, would be tools like -[pandas](https://pandas.pydata.org/). One problem you may have encountered with -this powerful data manipulation tool is that the dataframe can be an opaque -object that's hard to reason about in terms of its contents, data types, and -other properties. - -One tool that may help you with this problem is -[pandera](https://pandera.readthedocs.io/en/latest/index.html), which was -accepted by pyOpenSci as part of its ecosystem of packages on September 2019. -Pandera provides a flexible and expressive data validation toolkit that helps -users make statistical assertions about pandas data structures. - -## A Statistical Data Validation Toolkit for Pandas - -Image showing pandera package logo. - -To illustrate `pandera`'s capabilities let's use a small toy example. Suppose -you're analyzing data for some insights in the context of a mission-critical -project, where it’s vital to ensure the quality of the datasets that you're -looking at. - -Each row in the dataset is uniquely identified by a `person_id`, and each -column describes that person's `height_in_cm`s and `age_category`. - - -```python -import pandas as pd - -dataset = pd.DataFrame( - data={ - "height_in_cm": [150, 145, 122, 176, 137, 151], - "age_category": ["20-30", "10-20", "10-20", "20-30", "10-20", "20-30"], - }, - index=pd.Series([100, 101, 102, 103, 104, 105], name="person_id"), -) -print(dataset) -``` - -``` - height_in_cm age_category -person_id -100 150 20-30 -101 145 10-20 -102 122 10-20 -103 176 20-30 -104 137 10-20 -105 151 20-30 -``` - -You want to ensure that some columns have the correct data type, or that the -dataset fulfills certain statistical properties. Pandera allows you to validate -a DataFrame to ensure that these conditions are met. It allows you to spend -less time worrying about the correctness of a DataFrame's data so you can make -the right assumptions in analyzing it. - - -### Column Presence and Type Checking - -The most basic type of schema is one that simply checks that specific columns -exist with specific datatypes. - -```python -import pandera as pa - -schema = pa.DataFrameSchema( - columns={ - "height_in_cm": pa.Column(pa.Int), - "age_category": pa.Column(pa.String), - }, - index=pa.Index(pa.Int, name="person_id"), -) - -schema(dataset) -``` - -The `schema` object is callable, so you can validate the dataset by passing -it in as an argument to the `schema` call. If the dataframe passes schema -validation, `schema` simply returns the dataframe. - -If not, it'll provide useful error messages: - - -```python -invalid_dataframe = pd.DataFrame({ - "weight_in_kg": [44, 31, 55, 61, 55, 62], - "age_category": ["20-30", "10-20", "10-20", "20-30", "10-20", "20-30"], -}) - -schema(invalid_dataframe) -``` - -``` -SchemaError: column 'height_in_cm' not in dataframe - weight_in_kg age_category -0 44 20-30 -1 31 10-20 -2 55 10-20 -3 61 20-30 -4 55 10-20 -``` - -### Basic Statistical Checks - -If you want to make stricter assertions about the empirical properties of the -dataset, we can supply the `checks` keyword argument to the -[`Column`](https://pandera.readthedocs.io/en/latest/dataframe_schemas.html#column-validation) -and [`Index`](https://pandera.readthedocs.io/en/latest/dataframe_schemas.html#index-validation) -constructors with a [`Check`](https://pandera.readthedocs.io/en/latest/checks.html#) -or list of `Check`s. - -```python -schema = pa.DataFrameSchema( - columns={ - "height_in_cm": pa.Column( - pa.Int, - # height in centimeters should be between 100 and 300 - checks=pa.Check(lambda s: (100 < s) & (s < 300)), - ), - "age_category": pa.Column( - pa.String, - # check allowable age categories - checks=pa.Check(lambda s: s.isin(["10-20", "20-30"])) - ), - }, - index=pa.Index( - pa.Int, - name="person_id", - checks=[ - # id is a positive integer - pa.Check(lambda s: s > 0), - - # id is unique - pa.Check(lambda s: s.duplicated().sum() == 0), - ] - ), -) - -schema(dataset) -``` - -A `Check` object specifies the exact implementation of how to validate a -column or index. The first positional argument in its constructor is a callable -with the signature: - -``` -Callable[ pd.Series, Union[ bool, pd.Series[bool] ] ] -``` - -Notice that the only constraint to the callable is that takes a `Series` as -input and returns a boolean or a boolean Series. By design, checks have access -to the entire pandas `Series` API to make assertions about the properties of a -particular column or index. - -### Indexed Error Messages - -In cases where the `Check` returns a boolean `Series`, violations of the -schema are reported by the index location of failure cases. - -```python -invalid_data = pd.DataFrame( - data={ - "height_in_cm": [91, 105, 87, 87], - "age_category": ["10-20", "10-20", "10-20", "10-20"] - }, - index=pd.Series([200, 201, 202, 203], name="person_id") -) - -schema(invalid_data) -``` - -``` -pandera.errors.SchemaError: failed element-wise validator 0: - -failure cases: - person_id count -failure_case -87 [202, 203] 2 -91 [200] 1 -``` - -The error is reported as a stringified dataframe where the `failure_case` index -enumerates instances of `height_in_cm` values that failed data validation, the -`person_id` column is the index location of the failure case, and `count` -column displays the number of instances of a particular failure case. - - -### Statistical Hypothesis Tests - -What if we wanted to test the hypothesis that older people tend to be taller? -We can achieve this with the [`Hypothesis`](https://pandera.readthedocs.io/en/latest/hypothesis.html) -check: - -```python -schema = pa.DataFrameSchema( - columns={ - "height_in_cm": pa.Column( - # perform a one-sided two-sample t-test of - # the distribution of heights by age category, - # with an alpha value of 5% - checks=pa.Hypothesis.two_sample_ttest( - groupby="age_category", - sample1="20-30", - relationship="greater_than", - sample2="10-20", - alpha=0.05, - equal_var=True, - ) - ), - "age_category": pa.Column( - pa.String, - checks=pa.Check(lambda s: s.isin(["10-20", "20-30"])), - ) - } -) - -schema(dataset) -``` - -## Validate your Pandas Dataframes Today! - -Whether you use this tool in Jupyter notebooks, one-off scripts, ETL -pipeline code, or unit tests, `pandera` enables you to make pandas code more -readable and robust by enforcing the deterministic and statistical properties -of pandas data structures at runtime. - -Hopefully this post has given you a flavor of what `pandera` can do. It -offers a few more features that you may find useful: - -- [Series schema validation](https://pandera.readthedocs.io/en/latest/series_schemas.html) -- [Coercing column data types](https://pandera.readthedocs.io/en/latest/dataframe_schemas.html#coercing-types-on-columns) -- [Multi-index validation](https://pandera.readthedocs.io/en/latest/dataframe_schemas.html#multiindex-validation) -- [Vectorized vs. element-wise checks](https://pandera.readthedocs.io/en/latest/checks.html#vectorized-vs-element-wise-checks) -- [Wide checks](https://pandera.readthedocs.io/en/latest/checks.html#wide-checks) -- [Groupby Column Checks](https://pandera.readthedocs.io/en/latest/checks.html#column-check-groups) -- [Check input/output decorators](https://pandera.readthedocs.io/en/latest/decorators.html) - -## What's Next? - -I'm actively developing this project and have some exciting features coming -up soon, such as [built-in checks](https://github.com/pandera-dev/pandera/issues/74), [first-class Dask support](https://docs.dask.org/en/latest/dataframe.html), -and [yaml schema specification](https://github.com/pandera-dev/pandera/issues/91). If you'd like to contribute to this -project, you're welcome to head on over to the [github repo](https://github.com/pandera-dev/pandera)! diff --git a/_posts/2019-12-03-agu-2019-pyopensci-events.md b/_posts/2019-12-03-agu-2019-pyopensci-events.md deleted file mode 100644 index 817ddf8a..00000000 --- a/_posts/2019-12-03-agu-2019-pyopensci-events.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -layout: single -title: "PyOpenSci at AGU 2019: Join Us" -blog_topic: community -excerpt: "Several pyOpenSci and Open Source Software events will occur at AGU 2019. Come learn more about open source Python tools for science." -author: "Leah Wasser" -permalink: /blog/pyopensci-at-agu-2019-python.html -header: - overlay_color: "#33205c" -categories: - - updates - - blog-post ---- - - -Hey all! I'm a part of the pyOpenSci Leadership team and Director of the Earth Analytics Education Initiative at Earth Lab, University of -Colorado, Boulder. There are several open source software events that you may -want to consider if you plan to be at AGU 2019 this year in San Francisco. -Please join us to learn more about pyOpenSci and to help shape what pyOpenSci -becomes. - -All are welcome!! - -## pyOpenSci Ignite Talk: Tuesday Dec 10, 2019 8am in Moscone West - 2010, L2 - -I will giving an invited ignite talk: pyOpenSci Promoting Open Source Python Software To Support Open Reproducible Science. This talk is a part of a larger open source software initiative organized by pyOpenSci collaborators -Leonardo Uieda and Lindsey Heagy. - - -## pyOpenSci Town Hall: Wed Dec 11, 2019 12:30-1:30pm in Moscone West - 2002 - -Please join us for a town hall dedicated to pyOpenSci: Data FAIR: pyOpenSci: Building a Community Around Open Source Python Software for Science. -We expect a strong presence from the rOpenSci community at this event as well! -pyOpenSci is being modeled after rOpenSci, but focused on the Python -programming language! - -
-### AGU Event Resources - Sign Up To Be A Reviewer & Townhall Presentation - -Here are a few resources shared at the town hall: -1. The pyopensci ignite talk from the open source ignite session - -### Signing Off For Now -I look forward to meeting you at AGU 2019!! Connect with my on twitter if you have questions at AGU! @leahawasser. I will be tweeting during the event! - -~leah diff --git a/_posts/2020-04-29-movingpandas-movement-data-analysis.md b/_posts/2020-04-29-movingpandas-movement-data-analysis.md deleted file mode 100644 index 1726bea2..00000000 --- a/_posts/2020-04-29-movingpandas-movement-data-analysis.md +++ /dev/null @@ -1,106 +0,0 @@ ---- -layout: single -title: "MovingPandas: Data Structures and Methods for Movement Data Analysis" -blog_topic: software -excerpt: "MovingPandas is an easy to use toolkit for exploring movement data that has recently passed the PyOpenSci review." -author: "Anita Graser" -permalink: /blog/movingpandas-movement-data-analysis.html -header: - overlay_image: images/blog/headers/pandas.png - caption: "Photo credit: [**Ann Batdorf, Smithsonian's National Zoo**](https://www.flickr.com/photos/nationalzoo/5371290900/in/photostream/)" -categories: - - blog-post - - pandas - - spatial -comments: true ---- - -Movement data is everywhere: from tracking apps that record human or vehicle movement to ecologists monitoring wildlife behavior. Movement data analysis is challenging since movement data combines time series and spatial data analyses questions. Existing spatial analysis libraries, such as GeoPandas, are great at handling spatial data but they don't support moving objects. - -MovingPandas aims to fill the gap of missing tools for exploring movement data. It provides data structures and methods for dealing with data of moving objects. MovingPandas has been accepted by pyOpenSci as part of its ecosystem in March 2020. - -## A Toolkit for Movement Data Analysis - -The MovingPandas repository contains multiple tutorials in the form of Jupyter notebooks that illustrate diverse analysis capabilities using different datasets, including: tracking data of ships, migration of birds, and tracks from a horse's GPS collar. - -MovingPandas Trajectory objects are created from GeoPandas GeoDataFrames. A minimal example would be: - -```python -import pandas as pd -import geopandas as gpd -import movingpandas as mpd -from shapely.geometry import Point -from datetime import datetime -from pyproj import CRS - -df = pd.DataFrame([ - {'geometry':Point(0,0), 't':datetime(2018,1,1,12,0,0)}, - {'geometry':Point(6,0), 't':datetime(2018,1,1,12,6,0)}, - {'geometry':Point(6,6), 't':datetime(2018,1,1,12,10,0)}, - {'geometry':Point(9,9), 't':datetime(2018,1,1,12,15,0)} -]).set_index('t') -gdf = gpd.GeoDataFrame(df, crs=CRS(31256)) -traj = mpd.Trajectory(gdf, 1) -``` - -MovingPandas provides static plots using Matplotlib and interactive plots using hvplot: - -```python -traj.plot() -``` - -![The trajectory is plotted as a blue line on a white background with latitude and longitude values labeled on the axes.]({{ site.url }}/images/movingpandas/mp_fig1.png) - -Matplotlib and hvplot parameters are passed along to the underlying libraries to enable extensive customization of plots: - -```python -traj.hvplot(geo=True, tiles='OSM', line_width=5, frame_width=300, frame_height=300) -``` - -![The trajectory is plotted as a wide blue line on an OpenStreetMap background with latitude and longitude values labeled on the axes.]({{ site.url }}/images/movingpandas/mp_fig2.png) - -### Exploring Movement Characteristics - -MovingPandas makes it straightforward to compute movement characteristics, such as trajectory length and duration, as well as movement speed and direction. - -For example, we can explore the daily travelled distance as recorded by a GPS tracker: - -```python -df = read_file('tracker.gpkg') -df = df.set_index('t') -tc = mpd.TrajectoryCollection(df, 'CollarID') - -daily = tc.split_by_date(mode='day') -daily_lengths = [traj.get_length() for traj in daily.trajectories] -daily_t = [traj.get_start_time() for traj in daily.trajectories] -daily_lengths = pd.DataFrame(daily_lengths, index=daily_t, columns=['length']) -daily_lengths.hvplot(title='Daily trajectory length') -``` - -![The evolution of the length of the daily trajectories is plotted over the whole obseration period.]({{ site.url }}/images/movingpandas/mp_fig3.png) - -In this case, the movement data, which comes from a GPS collar of a horse, reveals that the animal tends to travel farther during summer days than during shorter winter days. - -Other functions deal with trajectory generalization, splitting trajectories into subtrajectories, clipping trajectories to an area of interest, and extracting trajectory start and end times and locations. - -### Standing on the Shoulders of Giants - -By leveraging existing functionality within the Python data analysis ecosystem, such as, for example: -time series handling by Pandas, -spatial data analysis by GeoPandas, and -interactive plotting by HoloViews, -MovingPandas can focus on its core functionality dealing with challenges that are specific to movement data. - -For example, the close integration with HoloViews makes it possible to create -[interactive dashboards](https://holoviews.org/user_guide/Dashboards.html) to explore the effect of different trajectory generalization methods: - -![The animated map and speed histogram show that speeds increase when the generalization tolerance value is increased.]({{ site.url }}/images/movingpandas/mp_fig4.gif) - -## Test Your Movement Data Anylsis Skills Today! - -You can [try MovingPandas in a MyBinder notebook - no installation required](https://mybinder.org/v2/gh/movingpandas/movingpandas-examples/main?filepath=1-tutorials/1-getting-started.ipynb) - -## What's next? - -MovingPandas is under active development and there are some exciting features coming up. -If you’d like to contribute to this project, you’re welcome to head on over to the [Github repo](https://github.com/anitagraser/movingpandas)! diff --git a/_posts/2022-09-10-executive-director-pyopensci-leah-wasser-updates.md b/_posts/2022-09-10-executive-director-pyopensci-leah-wasser-updates.md deleted file mode 100644 index 57888b93..00000000 --- a/_posts/2022-09-10-executive-director-pyopensci-leah-wasser-updates.md +++ /dev/null @@ -1,127 +0,0 @@ ---- -layout: single -title: "Big Changes for pyOpenSci: an update from the new Executive Director, Leah Wasser" -blog_topic: updates -excerpt: "pyOpenSci has recently transitioned to a fiscal sponsor and has a new executive director - Leah Wasser. Learn more about our goals over the next few years and how to get involved with the community." -author: "Leah Wasser" -permalink: /blog/new-executive-director-leah-wasser.html -header: - overlay_color: "#33205c" -categories: - - updates - - blog-post - - highlight -redirect_from: - - /pyopensci-updates/executive-director-pyopensci-leah-wasser-updates/ -comments: true ---- - - -## pyOpenSci is now an independent community organization - -I am thrilled to announce that pyOpenSci is now a fiscally sponsored project of -[Community Initiatives](https://communityin.org/our-projects/support-a-project/)! This means that pyOpenSci now serves the community of -scientific Python users and open source software maintainers. pyOpenSci is not -affiliated with any specific organization. I (Leah) will now be devoting ALL of my -(work :grin: ) time to the pyOpenSci project full time as the official Executive Director. - -## pyOpenSci’s current funding and structure - -I am grateful for two years of funding from the Sloan Foundation to -support this effort. Once I acquire additional funding, I -will be able to hire more staff to grow the organization. I am working with a -board of two right now: Tracy Teal (chair) and [Karen Cranston](https://karencranston.ca/). They both bring -tremendous experience building and supporting The Carpentries, among other -efforts; Tracy and Karen will guide me in propelling pyOpenSci forward. I also am -working with two incredible editors that have been crucial in supporting peer -review: [David Nicholson](https://nicholdav.info/) and [Ivan Ogasawara](https://xmnlab.github.io/). -In the upcoming months we will recruit more editors to support our open source software review process. -Finally, I am also continuing to coordinate with and learn from the -well-established and successful [rOpenSci](https://www.ropensci.org). -rOpenSci has a strong precedent for open, community-driven peer review. Our -community has a lot to learn from rOpenSci. I am grateful for their -continued support. - -## pyOpenSci is not just peer review of open source software -Peer review is critical to the pyOpenSci mission. But I have pyOpenSci bigger -and broader aspirations for pyOpenSci, too. There are several organizational -goals supported by the Sloan Foundation grant that we will be working on over -the next few years: -* Create and grow a diverse and supportive community around a dynamic and -open peer review process and Python open science skills. Diversity is important and we plan to bake diversity focused programs into all of pyOpenSci activities from the beginning. -* Streamline and improve the quality of smaller Python Scientific Open Source software tools that support scientific workflows: think api wrappers, spatial data tools and domain specific tools rather than Numpy and Pandas. - * We do this through peer review which not only assess code quality, testing infrastructure and CI setup. Our peer review also evaluates tool usability, documentation clarity and ensures alignment with community accepted standards for Python packaging. - * One way our peer review process fosters usability of the tools is by ensuring documentation includes vignettes that showcase tool functionality and provide easy quickstarts for users. - * Once tools are accepted into our ecosystem, we will track tool maintenance and find new maintainers, or support sunsetting as it makes sense to ensure users aren't left hanging. - * Through peer review we will also address redundancy in tool functionality across the scientific Python ecosystem. -* Create a model to support peer review within domain specific subcommunities. We will begin our work here with the Pangeo community. Here, we will support a peer review process with community-specific standards applied on top of ours to ensure tool consistency. -* Provide training and mentorship in skills needed to contribute to open source. These skills are not just for open source. They are also fundamental to good open science practices and will support career trajectories in open science too. -* Grow support for maintainers. A critical component of this effort will be a communication campaign around the importance of open source to science. - -### We will also continue to curate our partnership with the Journal of Open Source Software (JOSS). -Package maintainers can still submit a package to us if they wish to publish with -[the Journal of Open Source Software (JOSS)](https://joss.theoj.org/). Our partnership supports a "fast track" review in JOSS if its functionality -fits their scope and requirements. Once a package is accepted by pyOpenSci, if -the package fits into the JOSS scope our review will be accepted by JOSS. A package -author then only needs to write a paper for JOSS, and they will be "dual" -published with us and JOSS. This, friends, is a win-win! - -## Who does pyOpenSci serve? -pyOpenSci supports software tools for open science and domain specific -scientific projects. These tools and packages can act as glue that holds a -research community together, but they haven't always received as much support -as the foundational frameworks that are widely used (e.g. Pandas, numpy, scipy). -We also support scientists using these tools and will empower folks like you -with the skills needed to both contribute to Open Source and improve your open -science workflows! In the long term these skills will be invaluable in your -data intensive career trajectory! - -## pyOpenSci is about collaboration - not reinventing the wheel -Collaborations with existing efforts are critical. I welcome any feedback from -you regarding groups that we should be engaging with! I will also need help -finding reviewers and editors, mentors to support training efforts and voices to -spread the word about the project. - -## And… what's the timeline? -Unless I figure out how to create numerous robot clones of myself, please know -that all of the above won’t happen immediately. I am currently the sole -employee of the organization and this is my second week. But never fear - we -will accomplish a lot in the next two years and look forward to and will greatly -value support from some of you along the way! - -## Stay in touch! -Once I have a road map for activities developed, I will communicate this here on our -[website](https://www.pyopensci.org/). - -## What can you do now? -There are a few ways to get involved with us now. - -1. [You can sign up to be a reviewer by filling out our reviewer form](https://forms.gle/B6zAukLCvJot5nws6). If you do this, we will reach out to you if a package is submitted that suits your background and interests! - * We will also be growing our editorial board in the upcoming months. Once you have reviewed for us you are eligible to be considered as an editor. We will be posting more about this process in the upcoming months. -1. Our open peer review process is running! If you have a package that you are working on, [you can submit it to our peer review process](https://github.com/pyOpenSci/software-review/issues). - * You can learn more about our review process here in our [community guide (which will be updated in the upcoming months so that it is more clear)](https://www.pyopensci.org/software-peer-review/about/package-scope.html). - * If you have a package that you wish to submit but need some guidance, you can also submit a presubmission inquiry or a request for help here. -2. You can [follow us on twitter](https://www.twitter.com/pyopensci) to keep tabs on our progress. - -If you want to stay in touch with me, feel free to follow me on -([twitter: @leahawasser](https://www.twitter.com/leahawasser)). Or connect with -me on [GitHub](https://www.github.com/lwasser). - -## pyOpenSci was built with support from numerous community members -I would be remiss if I didn't take a moment to thank the huge suite of community -members who participated in early meetings and discussions surrounding pyOpenSci. -Please note that this list is not comprehensive and likely misses folks who -attended discussions held at the SciPy Conference a few years prior to my involvement and those -whose names I am missing on meeting notes. Feel free to reach out and ask for -corrections as needed! Max Joseph, Jenny Palomino, Kylen Solvik, Chris Holdgraf, -Paige Bailey, Luiz Irber, -Arfon Smith, Leonardo Uieda, Filipe Fernandes, Carson Farmer, Neil Chue Hong, -Joe Hamman, Ryan Abernathy, Lindsey Heagy, Mike Trizna, Noam Ross, Karthik Ram, -Martin Skarzynski, Levi Wolf, Daniel Chen, Ivan Ogsawara, David Nicholson, Tania Allard, -Kirstie Whitaker, Sander van Rijn, Philip Meier, Moritz Lurig ... and so many -more! - -I am so excited to be leading this effort and look forward to keeping in touch -with everyone and supporting the community as pyOpenSci grows. - -More to come... soon! diff --git a/_posts/2022-09-27-call-for-python-software-review-editors.md b/_posts/2022-09-27-call-for-python-software-review-editors.md deleted file mode 100644 index 1b5cc886..00000000 --- a/_posts/2022-09-27-call-for-python-software-review-editors.md +++ /dev/null @@ -1,93 +0,0 @@ ---- -layout: single -title: "pyOpenSci Editorial Process - New Editor in Chief and a Call For Editors" -blog_topic: software -excerpt: "pyOpenSci is developing its open peer review process for Python scientific software. Learn about our structure and if you are interested, apply to be on our editorial board." -author: "Leah Wasser" -permalink: /blog/python-software-review-process-editor-in-chief.html -header: - overlay_color: "#33205c" -categories: - - updates - - blog-post - - highlight - - peer-review -comments: true ---- - -## An update on pyOpenSci's review process - -Hey there! I just wanted to update everyone about where pyOpenSci is with -its peer review process. We are currently hard at work updating [our peer review guides](https://www.pyopensci.org/software-peer-review/) to -streamline the peer review process. For the next 2 months (October & November 2022) -we will prioritize setting up an editorial board to support new reviews as they -come in. - -With that below you will find a few updates. - -## A Warm welcome to our first Editor in Chief, David Nicholson! - -[David Nicholson](https://github.com/NickleDave), who has been working with pyOpenSci for the past few years as -an editor will take on the Editor in Chief role! [You can learn more about that -role here](https://www.pyopensci.org/software-peer-review/how-to/editor-in-chief-guide.html) in our peer review guide. This position will rotate once we have our editorial board setup. - -As Editor in Chief, David will: - -* Be the first person to reach out to anyone submitting a new review or a pre-submission. -* Perform initial software checks to ensure your package is ready for -* Facilitate kicking off the peer review process by finding an editor for your package - -David has a background in behavioral / neuro / cognitive science and is a machine learning guru. -Of course, he's a Pythonista through and through. We are lucky to have David as a -part of our pyOpenSci team. - -*Stay tuned for a blog post from David introducing himself.* - -## We are creating an editorial board! - -To support our open peer review, we want to bring on a few people (like yourself?!) -to serve as guest editors / editors. Here's how this works: - -If you are new to our organization, we start you as a guest editor. What this -means is that you will review one package and we will support you in that -process to ensure you learn the ropes. Then if you are happy and work well -with our editorial team we will bring you on officially as an editor. - -Editors at pyOpenSci make a minimum of a 1-year commitment with a possibility of -longer given mutual consent. You will be responsible for handling 3-4 package -reviews during that year. We may also bring on guest editors in other -instances, e.g., because of their familiarity with a specific domain. - -You can read more about the [roles and responsibilities of an editor here](https://www.pyopensci.org/software-peer-review/how-to/editors-guide.html). - -We currently have a package in the queue that is spatial / remote sensing related -that we'd love to have an editor onboard to review. But we are in need of editors -from other domain backgrounds too. -{: .notice } - -### What background do I need to be an editor for pyOpenSci? - -We welcome applications from potential editors with significant experience in -one or more of the following areas: -* open source software, -* open science -* software engineering -* peer-review - -Members of the pyOpenSci editorial team have diverse backgrounds. We welcome -editors from academia, government, and industry. We especially welcome -applications from prospective editors who will contribute to the diversity -(ethnic, gender, disciplinary, and geographical) of our board. We also value -having a range of junior and senior editors. - - -### How do I apply? - -To sign up to be considered as an editor for pyOpenSci, please fill out the -form below: - -[Click here to apply to be an editor ](https://forms.gle/YH4kTeDFoYjDtefh7){: .btn .btn--info .btn--large} - -## Questions? -If you have any questions, please reach out to me at leah at pyopensci.org or on -twitter at [@LeahAWasser](https://twitter.com/LeahAWasser). diff --git a/_posts/2022-10-24-why-should-python-open-source-matter-science.md b/_posts/2022-10-24-why-should-python-open-source-matter-science.md deleted file mode 100644 index 3575ee3f..00000000 --- a/_posts/2022-10-24-why-should-python-open-source-matter-science.md +++ /dev/null @@ -1,264 +0,0 @@ ---- -layout: single -title: "Why should Python open source package health matter to scientists? (and to you!)" -blog_topic: software -excerpt: "Free and open source software tools are the foundation for thousands if not millions of scientific workflows. Yet, it is rare that users fully understand it's importance in moving science forward. Here, I discuss the value of free and open source software for science; why you as a scientist should care; and what pyOpenSci is doing to try to support Python scientific tools for science." -author: "Leah Wasser" -permalink: /blog/why-python-open-source-software-matters-for-scientists.html -header: - overlay_color: "#33205c" -categories: - - blog-post - - highlight - - python-packaging - - peer-review -toc: false -comments: true -last_modified: 2024-08-29 ---- - -
-## Part 1/3: Blog series on package health - -This blog post is part 1 of a 3 part series on open source package health. -The series was inspired by a conversation -held on twitter. This blog post is not a comprehensive perspective on what pyOpenSci -plans to track as an organization. Rather it's a summary of thoughts collect during -the conversation on twitter that we can use to inform our final metrics. -* [In blog 2/3 I will discuss -why free and open source package metrics matter and which categories of metrics we at pyOpenSci are thinking about tracking.](/blog/why-python-package-health-metrics-matter.html) -* [In blog 3/3, I'll recap a conversation held on -twitter around package health metrics](/blog/what-makes-open-source-python-package-healthy.html). - -
- -## Why Python open source package health should matter to you as a scientist - -If you are a scientist, the health of a scientific python package may not be something that -you care about. What might seem more important is doing your science, and processing -your data to get to that coveted scientific inquiry and exploration stage. - -In actuality, package health is incredibly important to science, especially open, -data-intensive science. It should be important to you too! - -Why? Let me provide a few reasons below: - -#### 1. Free and open source tools (FOSS) make your code simpler. - -Free and open source tools provide commonly needed functionality wrapped up in simple tested functions and objects that you don't need to recreate yourself. - -If you are creating open workflows to process your data, you are likely using free-to-download (and use) tools - software. These tools make it easier for you to access, open, process and visualize your data. These free and open tools allow -allow you to write less (complicated) code to process your data. Code that -someone else (a package maintainer) maintains (often in their spare time but we'll -get to that in another blog post!). - -#### 2. Open source software provides centralized maintenance of commonly used workflows - -Imagine 1,000 scientists accessing climate data. They all need to download the -data and plot it. However they made download different types of climate data, -different models, different variables. The base code that all 1,000 scientists -write to download and open the data is the same. Similarly the base code for -plotting is also the same. - -Isn't it better that one person writes great code and -updates it as things like the download API change? Or they update plotting -functionality? - -* This central maintenance avoids you and many other people needing to write code that -does the same thing. It makes it easier for you to process your climate data and get -to the science. Your code is simpler. - -* This centralization of tools that perform tasks that many -people need to do avoids the problem of 1,000 people trying to create the same workflow and creating different and potentially problematic code. - -* This avoids everyone reinventing the scientific wheel. - -#### 3. Free and open source software reduces the barrier of needing a paid license to build upon your work. - -Free and open source software removes the barrier a paid license to run your code. This makes your work more accessible. - -There is a lot more to say about the value of open source here but i'll leave -that to another blog post. - -
- - Image with a blue computer keyboard background with the text: why scientists should care about open source software on top. - -
If you are a scientist using open science approaches, then your workflow likely depends upon open source tools. These tools are critical to our work and yet are often not supported or consider - as integral components of open science. -
-
- -## Creating and maintaining open source software is hard work - -Creating these open source tools to work with data is not a trivial task. Often -the people (who may be developers or scientists) who develop the tools: - -* Aren't paid to do the work -* Burn out from all of the effort associated with supporting the tools -* Aren't acknowledged for their effort -* Have to deal with users who are frustrated by bugs, but aren't able to communicate that frustration kindly or effectively to help the maintainer get it fixed while also acknowledging their effort (which again is often volunteer) - -Maintainers also get new jobs, and need to step away from maintaining that tool. - -All of the above causes a challenge where once-maintained tools are now left -unmaintained and vulnerable to new bugs as other packages that tool depends on -are updated, or as the Python language itself is updated. - -### Package usability is also important but not always considered by maintainers - -Not all developers focus on usability when designing a new tool. Some do. -Maintainers often start, -as expected, with trying to get a job done. Documenting a package well enough -so that a beginner can get started with using it, is not always an immediate goal. - -Yet usability is critical to developing a user base. To making a tool accessible -to more people which could in turn help grow community around it that -supports that tool. - -This can be frustrating for scientists who are trying to find the right tool -to use to support their analysis. And as such another area that we definitely -want to consider when building pyOpenSci. - - -## Maintainers do a lot of hard work and rarely get credit for it - -Before I say anything else here: - -> Please - cite software in your work if you use it! And also if you need to report a bug, please do so in a kind and thoughtful way! - -Maintainers work hard on their packages. One package may be the foundation for -data processing and analysis across hundreds to thousands (or more) scientific -papers. But unlike scientific papers, work on the package continues long after a -paper is published. A package is a living thing that needs continual work and love. - -So, what happens to that package that you are using in your workflow, -when the developer gets a new job or finds they no longer have the time to -maintain it? What happens when you need to update your workflow to support a paper review OR when you want to -build upon it for another analysis if that tool no longer -is maintained? - -
- - Image showing girl crying with text orphan python packages are breaking my open workflows. - -
Your workflow likely depends upon tools that are being developed - by volunteers. Supporting these tools is critical to supporting open science. And - open science is critical to accelerating scientific discovery Source: Meme created by yours truly. :)
-
- -This, my friend, is why you should care about, and support, open source software! - -## pyOpenSci is designed to create diverse community for and to support the open source Python tools that you as a scientist are using in your workflows. - -The issues discussed above around maintenance, usability and quality of software, -are the types of issues that pyOpenSci will address. - -
- - Image showing xkcd comic with a robotic type of image representing a scientific workflow and pointing to the small open source package maintained by one person that is a major dependency of the workflow. - -
Critical scientific workflows and projects often have dependencies - that are maintained by volunteers. Source: XKCD
-
- -But we (pyOpenSci) need to track package use, and maintenance, collect data and -quantify outcomes to determine if we are making the impact that we want to. To -determine if we are truly helping you, as a scientist in selecting the tool that -will help you in your workflow and also be maintained over time and documented -enough that you can get started using it quickly. - -We also want to ensure that we are supporting maintainers as well. To help them -with the hard job of showing up each day to maintain a package that maybe hundreds -to thousands of scientists are using. - -### pyOpenSci needs to collect data around metrics to track all of these issues - -pyOpenSci needs to do more than just open peer review of scientific Python -packages. We need to collect data to better understand the issues and how we -impact those issues over time. - -A few package-related goals of pyOpenSci include: - -* Ensure that package quality is better after the review than before -* Inspire maintainers to develop more robust package infrastructure including testing -* Improve the usability of packages through documentation and vignettes (short tutorials showing users how to get started with the package) -* Ensure that packages are maintained over time; If they aren't maintained, ensure that they are archived or sunsetted in a way that users know they are no longer maintained. (no more dark orphan repositories!) - - -To make sure we reach our goals, we have to collect metrics on packages -submitted to our open peer review process to track quality and health over time. -And hopefully, through our review process and support of -maintainers, we will help to improve the overall quality of packages being created -to support scientific workflows. - -> We want to help the community. - - -## How pyOpenSci hopes to improve the usability and quality of smaller open source software packages that support science - -These, my friend are lofty goals. But our mission is to help -scientists build better software. And to ensure that the community understands the -maintenance level of that software before they adopt using it. - -We also want scientists to understand how hard maintainers work to create the -tools that they use. And to cite that work if they use the tools in the same -way they might cite a peer reviewed article. But that is another blog to be written. - -So how do we track open source tool health (for science)? - -### Peer review is actually the second step in our process. - -We won't begin to review a package [without bare minimum checks](https://www.pyopensci.org/software-peer-review/how-to/author-guide.html#does-your-package-meet-packaging-requirements). -We hope that these bare minimal checks help maintainers as they try to decide -what is good enough infrastructure for their package. - -We hope that these checks will also help new maintainers that are creating -new packages even if they never submit their package to us for peer review. - -## Goals for package metrics - -These metrics will help us quantify several of our goals: - -We hope that: - -* Peer review improves Python package structure and usability. -* Peer review in some way supports maintenance and/or responsible archiving when a package comes to life-end. -* Over time, the package is improved and maintained with possible contributions for those other than the maintainer. - -We need metrics to understand things like - -* Community adoption of the package (are scientists using it?) -* Maintenance level of the package (are maintainers still working on it and fixing bugs?) -* Infrastructure (are tests setup to help identify if contributions break things? ) -* Usability (is the package documented in a way that helps users quickly get started) - -### A discussion about package health on twitter - -A few weeks ago, I posted on twitter to see what the community -thought about "*what constitutes package health*". - - - - -The twitter convo made me realize that there are -many different perspectives that we can consider when addressing this question. - -More specifically, pyOpenSci is interested in the health of packages that -support science. So we may need to build upon already existing -efforts that determine metrics and customize them to our needs. - -## Continue reading the blog series on package health - -This blog post is part 1 of a 3-part series. - -[ Click here to proceed to the part 2/3, where I discuss why free and open source package metrics matter.](/blog/what-makes-open-source-python-package-healthy.html){: .btn .btn--info .btn--large} - -In the [final post of this three part blog series](/blog/what-makes-open-source-python-package-healthy.html), I'll recap a conversation held on -twitter around package health metrics. This conversation will be used to define the -types of metrics that we will collect as an organization. - -## Feedback? Leave it below - -If you have any thoughts on pyOpenSci metrics and goals or questions, please -leave them in the comments below! diff --git a/_posts/2022-11-07-why-python-package-health-metrics-matter.md b/_posts/2022-11-07-why-python-package-health-metrics-matter.md deleted file mode 100644 index eb954912..00000000 --- a/_posts/2022-11-07-why-python-package-health-metrics-matter.md +++ /dev/null @@ -1,127 +0,0 @@ ---- -layout: single -title: "Why Python package health metrics are a priority for pyOpenSci and other open source communities" -blog_topic: software -excerpt: "Collecting baseline data that aligns with the goals and outcomes of your project, program or organization is critical to do at the beginning. Here I briefly explain why Python package health metrics are so important to the long-term success of pyOpenSci." -author: "Leah Wasser" -permalink: /blog/why-python-package-health-metrics-matter.html -header: - overlay_color: "#33205c" -categories: - - blog-post - - highlight - - python-packaging - - peer-review -toc: false -comments: true -last_modified: 2024-08-29 ---- - -
-## Part 2/3: Blog series on package health -This blog is the second in a 3 part series. [In the previous blog post,](/blog/why-python-open-source-software-matters-for-scientists.html) -I discussed why the health of (Python) open source -packages should matter to you as a scientist (and as a person who values and -uses free and open source tools in your workflow). In this post, I'll talk -more about why collecting metrics are critical to both program development success -and to the success of open source tools. I'll wrap up this series -[with a discussion on what types of package metrics pyOpenSci should be collecting -around the free and open source Python packages that you use.](/blog/what-makes-open-source-python-package-healthy.html). - -NOTE: all of this is in the context of a conversation on Twitter. It is not a -comprehensive perspective on the final metrics that pyOpenSci plans to collect. -
- -## Metrics are critical to the development of any program - -I've created a few open science focused programs now from the ground up. One at -NEON and another at CU Boulder. When building a new program, one of the first -things that I do (after defining the mission and goals) is to define the metrics -that constitute success. - -These metrics are critical to define early because: - -* They drive everything that you do. -* And often they take time to develop -* It's critical to have solid baseline data. This baseline data needs to be collected from the start of your program as often, it can't be collected later, retrospectively. - -If you have evaluation or education in your professional background like I do, -you may even -[create a logic model](https://thecompassforsbc.org/how-to-guide/how-develop-logic-model-0) to map activities to outcomes and goals. This logic -model helps you define how -to collect the data that you need to track outcome success. - -## Baseline data are critical to collect at the start of building a program or organization - -As I am building the pyOpenSci program, I find myself thinking about what metrics -around Python scientific open source software we want to track to better understand: - -1. The outcomes of our activities -2. The overall health of packages in our growing pyOpenSci ecosystem (specific to our organization) -3. How/if we contributed to improving the health of packages in our ecosystem -4. How we are impacting the broader scientific python, open source community - -### Baseline metrics collected from the start will help your future self when running or working in an organization - -As mentioned above, collecting metrics from the start -of your efforts allows you to get off the ground running with data that you can use to compare to future data. Thus while it may not be the work that you want to do, it will -help your future self. - -For pyOpenSci, collecting metrics allows us in the future -to evaluate our programs and adaptively change things to -make sure we are getting the outcomes that we want. - -Outcomes such as -* Scientists being better able to find packages that are maintained to support their workflows -* Improved documentation in packages as a result of our reviews - -### But what metrics should we collect about scientific Python packages? - -[In a previous post,](/blog/why-python-open-source-software-matters-for-scientists.html) I spoke -generally about why open source should matter to you as a scientist and as a -developer or package maintainer. - -To better understand what data we should be collecting to track our packages' -health over time, I went to Twitter to see what my colleagues around the world -had to say. That conversation resulted in some really interesting insights. - -[In my next blog post, I will summarize the discussion that happened on twitter.](/blog/what-makes-open-source-python-package-healthy.html) -{: .notice } - -Most importantly, it allowed me to begin to break down and group metrics in -terms of pyOpenSci goals. - -### Goals for package metrics - -We hope that: - -* Peer review improves Python package structure and usability. -* Peer review in some way supports maintenance and/or responsible archiving when a package comes to life-end. -* Over time, the package is improved and maintained. -* Over time we hope to facilitate outside contributions to these packages. - -We need metrics to understand things like - -* Community adoption of the package (are scientists using it?) -* Maintenance level of the package (are maintainers still working on it and fixing bugs?) -* Infrastructure (are tests set up to help identify if contributions break things? ) -* Usability (is the package documented in a way that helps users quickly get started) - -## Four metric categories that pyOpenSci cares about - -Based on all of the feedback on twitter, which is summarized in the next post, and what I *think* might be a start at what pyOpenSci -needs to consider, I organized the conversation into four broad categories: - -1. Infrastructure -2. Maintenance -3. Community adoption (and usability??) -4. Diversity, Equity, Inclusion, and Accessibility (DEIA) - discussed less on twitter but integral to pyOpenSci goals. - -These four categories, at not by any means mutually exclusive. They are merely a way -to begin to organize an engaging and diverse conversation. All of the categories are priorities of pyOpenSci. - -[ Click here to read more about how these metric categories evolved within the conversation on Twitter!](/blog/what-makes-open-source-python-package-healthy.html){: .btn .btn--info .btn--large} - -## Thoughts? - -Leave any feedback that you have in the comments section below. diff --git a/_posts/2022-11-21-what-makes-a-python-package-healthy.md b/_posts/2022-11-21-what-makes-a-python-package-healthy.md deleted file mode 100644 index 6698c1f2..00000000 --- a/_posts/2022-11-21-what-makes-a-python-package-healthy.md +++ /dev/null @@ -1,525 +0,0 @@ ---- -layout: single -title: "What Makes a Python Open Source Package Healthy? A Conversation on Twitter." -blog_topic: software -excerpt: "How should pyOpenSci measure Python open source package health and level of maintenance and usability? Here I summarize a conversation held on twitter around this very topic. Feedback is welcome!" -author: "Leah Wasser" -permalink: /blog/what-makes-open-source-python-package-healthy.html -header: - overlay_color: "#33205c" -categories: - - blog-post - - highlight - - python-packaging - - peer-review -toc: false -comments: true -last_modified: 2024-08-29 ---- - -
-## Part 3/3: Blog series on package health - -This blog post is part 3 of a 3 part series on open source package health. -The series was inspired by a conversation -held on twitter. This blog post is not a comprehensive perspective on what pyOpenSci -plans to track as an organization. Rather it's a summary of thoughts collect during -the conversation on twitter that we can use to inform our final metrics. - -- [In blog 1/3 I discussed why Python open source software matters to scientists (and package maintainers too)](/blog/why-python-open-source-software-matters-for-scientists.html) -- [In blog 2/3 I discussed - why free and open source package metrics matter and which categories of metrics we at pyOpenSci are thinking about tracking.](/blog/why-python-package-health-metrics-matter.html) - -In this post, I'll summarize a conversation that was held on twitter that gaged -what the community thought about metrics to track the health of scientific -Python open source packages. - -
- -## Packages and open source software, a few terms to clarify - -- When I say Python package or Python open source software, I'm referring to a - tool that anyone can install to use in their Python environment. -- When I say open source or free and open source software I'm referring to Python tools that are free to download and and their code is openly available for anyone to see (open source). - -## What is package health anyway? - -There are many different ways to think about and evaluate open source -Python package health. - -Below is what I posted on Twitter to spur a conversation about what makes a -package healthy. And more specifically what metrics should we (pyOpenSci) collect -to evaluate health. - -**My goal:** to see what the community thought about "_what constitutes package health_". - - - - -The twitter convo made me realize that there are -many different perspectives that we can consider when addressing this question. - -More specifically, pyOpenSci is interested in the health of packages that -support science. So we may need to build upon existing -efforts that have determined what metrics to use to quantify package health and customize them to our needs. - -
-### A note about our pyOpenSci packages - -pyOpenSci does not focus on foundational scientific Python packages like Xarray -Dask, or Pandas. Those packages are stable and already have a large user base -and maintenance team. Rather we focus on packages that are higher up in the -ecosystem. These packages tend to have smaller user bases, and smaller maintainer -teams (or often are maintained by one volunteer person). - -
- Image used by Jake Vanderploss in the 2017 pycon conference that shows
-    the ecosystem of scientific python packages starting with foundational packages
-    and moving out to the wealth of smaller, domain specific packages. -
Image used by Jake Vanderploss in the 2017 pycon conference that shows - the ecosystem of scientific python packages starting with foundational packages - and moving out to the wealth of smaller, domain specific packages. pyOpenSci - focuses on the packages that are higher up in the ecosystem that often could - benefit from more support. -
-
- -Our package maintainers: - -- Often don't have the resources to build community -- Often are keen to build their user base and to contribute -to the broader scientific python ecosystem. -
- -## Existing efforts on health metrics: Chaoss project and the Software Sustainability Institute (neil) - -I'd be remiss if I didn't mention that there are several projects out there that -are deeply evaluating open source package health metrics. - -Several people including: Nic Weber, Karthik Ram and Matthew Turk mentioned the -value and thought put into the [Chaoss project](https://chaoss.community/). - - - - - - - -The Software Sustainability Institute lead by Neil P Chue Hong has also -thought about package health extensively and pulled together some data accordingly. Neil was also -a critical guiding member of the earlier pyOpenSci community meetings that were -held in 2018. - - - -### Snyk and security (which aren't discussed in this post) - -One topic that I am not delving into in this post is security issues. Snyk -is definitely a leader in this space and was mentioned at least once in -the conversation. - -### Some existing metric examples - -Below are some of the metrics that you can easily access via Snyk's website. - - - -#### [Pandera python package metrics on Snyk](https://snyk.io/advisor/python/pandera) - -
- Image showing the metrics from the Snyk website for the `pandera` package in our ecosystem. -
Here you can see what a Snyk report looks like for pandera, a package accepted into our ecosystem a few years ago. Pandera gets a very healthy report because it's heavily used among other metrics. -
-
- -#### Now let's look at [pyGMT package statistics on Snyk](https://snyk.io/advisor/python/pygmt) - -
- Image showing the metrics from the snyk website for the pyGMT package in our ecosystem. -
In comparison to pandera, pyGMT gets a lower, but still good, health score. I suspect this is due to lower community adoption and use. pyGMT is a much newer package. We'd argue however that pyGMT has a very healthy level of maintenance and even healthier package structure. -
-
- -And of course the [scientific Python project has also been tracking the larger packages](https://devstats.scientific-python.org/): - -## What metrics should pyOpenSci track for their Python scientific open source packages? - -So back to the question at hand, what should pyOpenSci be tracking for [packages -in our ecosystem](/python-packages/)? -Hao Ye (and a few others) nailed it - health metrics are multi-dimensional. - - - -> I may be a bit biased here considering I have a degree in ecology BUT... I definitely support the ecological perspective always and forever :) - -As Justin Kiggins from Napari and CZI points out, metrics are also perspective based. -We need to think carefully about the -organization's goals and what we need to measure as a marker of success and as a -flag of potential issues. - -See insightful thoughts below: - - - - - - - - - -Alas it is true that metrics designed for reporting that a funder requires -for a grant may differ metrics designed for internal evaluation that -informs program development. pyOpenSci has a lot to unpack there over the -upcoming months! - -### Three open source software healthy metric "buckets" - -Based on all of the Twitter feedback (below), and what I _think_ might be a -start at what pyOpenSci needs, I organized the Twitter conversation into three -buckets: - -1. Infrastructure -2. Maintenance -3. Community adoption (and usability??) - -These three buckets are all priorities of pyOpenSci. - - DEIA is another critical concern for pyOpenSci but I won't discuss that in this blog post. -{: .notice } - -## Infrastructure in a Python open source GitHub repository as a measure of package health - -So here I start with Python package infrastructure found in a GitHub repository -as a preliminary measure of package health. -When think of infrastructure I think about the files and "things" available in a -repository that support its use. I know that no bucket is perfectly isolated -from the others but i'm taking a stab at this here. - - The code for many open source software packages can be found on [GitHub](https://www.github.com). GitHub is a free-to-use website that runs `git` which is a version control system. Version control allows developers to track historical changes to code and files. As a platform built on top of git, GitHub allows developers to communicate openly, review new code changes and update content in a structured way. -{: .notice } - -### What does GitHub (and Ivan) think about health checks for Python open source software? - -Ivan Ogasawara is a long time advisor, editor and member of the pyOpenSci community. He's also a generally a great human being who is growing open science efforts such as -[Open Science Labs](https://opensciencelabs.org/); which is a global community devoted to education efforts and tools that support open science. - -Ivan was quick to point out some basic metrics offered by GitHub which follow their -[community standards online guidebook here](https://opensource.guide/). - - - -Actually it's totally related, Ivan! Let's have a look look at the [pyOpenSci contributing-guide GitHub repository](https://www.github.com/pyopensci/software-peer-review/community) to see how we are -doing as an organization. - -Note that we are missing some important components: - -- A code of conduct -- A contributing file that helps people understand how to contribute -- Issue template for people opening issues -- Pull request templates to guide people through opening pull requests -- Repository admins accepting content reporting - -{% include figure image_path="/images/python-software-health/healthy-software-repo-file-checks-github.png" alt="Image showing the community standards page in GitHub. You can see in the image we are missing several critical files including a code of conduct file, a contributing file that helps people understand how to contribute to the guide and issue and pull request templates." caption="Here you can see the community page in GitHub for our contributing-guide repository. Note that we are missing several important items in the repo including a code of conduct file, a contributing file that helps people understand how to contribute to the guide and issue and pull request templates. HELP!" %} - -> Um.... we've got some real work to do, y'all on our guides and repos. We need to set a better example here don't we? We welcome help welcome if you are reading this and wanna contribute. Just sayin... - -### GitHub bare minimum requirements are a great start! - -The GitHub minimum requirements for what a software repository should contain -are a great start towards assessing package health. In fact I've created a `TODO` -to add this url of checks to our pre-submission and submission templates as -these are things we want to see too; and also to update our repos accordingly. - -> Health check #1: are all GitHub community checks green? - -Looking at these checks more closely you can begin to think about different categories -of checks that broadly look at package usability (readme, description), community engagement (code of conduct, -templates), etc. - -
-The GitHub list includes: - -- Description -- README.md file ((but what's in that)) -- Code of conduct (but what's in that file?!) -- License (OSI approved) -- Issue templates (great for community building) -- Pull request templates - -
- -#### These checks are great but don't look at content and quality - -But these checks don't look at what's in that README, or how the issue templates -are designed to invite contributions that are useful to the maintainers (and that -guide new potential contributors). - -In short, GitHub checks are excellent but mostly exterior infrastructure focused. -They don't check content of those files and items. - -#### So what do content checks look like? - -As Chris mentions below, things like having a clearly stated goal and intention, -likely articulated in the **README** file is a sign of a healthy project. This goal was -ideally developed prior to development beginning. Further, if well-written, -it helps keep the scope of the project management. - - - -### Test suites and Python versions - -Another topic that came up in the discussion was testing and test suites. Evan, -who has been helping me improve our website navigation suggested looking at test suites -and what version of Python those suites are testing against. - - - -Test suites are critical not only to ensure the package functionality works as expected -(if tests are designed well). They also make it easier for contributors to check -if changes they made to the code -in a GitHub pull request don't break things unexpectedly. - -Tests can also be created -in a Continuous Integration (CI) workflow to ensure code syntax is consistent -(e.g. linting tools such as `Black`) and to test documentation builds for broken -links and other potential errors. - -> How should pyOpenSci handle Python versions supported in our review process? - -In fact the website that you are on [RIGHT NOW has a set of checks that run to test links](https://github.com/pyOpenSci/pyopensci.github.io/actions) throughout the site and to check for alt tags in support of accessibility (Alt tags support people using screen readers to navigate a website). - -
- Image showing the output of htmlproofer for a website. You can see that it tells you when and where there are broken links or missing alt tags. -
Notice in this output from htmlproofer on GitHub actions (continuous integration) that every page with a broken link or image with a missing alt tag will be flagged. Any flags will result in a broken build on GitHub - the dreaded red x. -
-
- -### Infrastructure: Is it easily installable? - -How the package is installed is another critical factor to consider. While -these days most packages do seem to be uploaded to PyPI, some still aren't. And -there are other package managers to consider too such as `Conda`. - - - -## Maintenance activity as a metric of health - -The second topic that came up frequently on Twitter was the issue of maintenance. - -Jed Brown had some nice overarching insight here for things they look at that are -indicators of both maintenance and bus factor (risk factor, mentioned below as a measure -of how many people / institutions support maintenance). More people and more institutions -equals lower risk, fewer people and fewer institutions supporting the package equates -to a higher maintenance risk (or risk of the package becoming a sad orphan with no -family to take care of it. - - - -How many times have you tried to figure out what Python package you should use -to process or download data, and you found 4 different packages on PyPI all in -varying states of maintenance? - -I've certainty been there. So has RenéKat it seems: - - - -It's true. For a scientist (or anyone) it's a waste of time to install something -that won't be fixed as bugs arise. It's also not a good use of their time to have to dig into a package repository to see if it's being maintained or not. - -pyOpenSci does hope to help with this issue through a curated catalog of tools -which will be developed over time. - -### But what constitutes maintenance? - -How do we measure degree of maintenance? Number of issues being addressed and closed? Average commits each month, quarter or year? - -This could be a relative metrics too. Some package maintainers -may spend lots of time on issues or have too many to handle quickly as Melissa -points out replying to a comment about evaluating maintenance by looking -at issues being closed: - - - -But alas I think there are ways around that. We can look at commits, pull requests and such -just to see if there's any activity happening in the repository. Or if it's gone dark -(dark referring to no long being maintained, answer to issues, fixing bugs, etc). - - - -Greg, interestingly suggested one might be able to model expected future lifetime of a package -based upon current (and past?) GitHub activity. - - - -Uh oh! But are commits enough, Kurt asks? Is there such a thing as a perfect project? - - - -Koen had a more broadly profound thought that would be ideal to consider when -creating a new package; especially a small package that supports specific -scientific workflows. - -> Does it do one thing, well? Really well? - -Yes, please. - - - -While this might be challenging to enforce in peer review, it is a -compelling suggestion. - -### How do developers evaluate package maintenance? - -There is a developer perspective to consider here too. Yuvi Panda pointed out a few -items that they look for: - -1. Frequency of merged commits -2. [Bus factor](https://www.michaelbromley.co.uk/blog/but-what-about-the-bus-factor/) -3. Release cadence (a topic brought up a few times throughout the discussion) - -{% include figure image_path="/images/python-software-health/bus-factor-python-package-health.png" alt="Funny meme image showing a bus being wrecked by a train with the text - bus factor isn't about buses." caption="Bus factor refers to the degree of risk associated with a package based on the number of maintainers and organizations supporting it." %} - -Remember, _bus factor_ has nothing to do with buses, but there is some truth to the -analogy of what happens when the wheels fall off. - - - -One thought I had here was to look at commits from the maintainer relative to -total commits to get a sense of community contribution (if any). - - - -> The CHAOSS project has an entire working group devoted to risk. - -Or perhaps pyOpenSci asks their maintainers what their perceived risk is? -IE: how long do you think the package might remain maintained. They will obviously -know better than anyone what their funding environment and support it like. - -Erik suggested that metrics can be dangerous and somewhat subjective at times. -Akin to the whole - maps can lie; data can lie too . Ok it's our interpretation that is -the risk or lie not the data but ... you follow me, yea? - - - -Some including Pierre brought up the idea of consistent releases. Not necessarily -frequency but just some consistency to demonstrate that the package was being -updated. - - - -Other discussions evolved around semantic versioning and release -roadmaps. - -## Community adoption of an open source Python tool - -Community adoption of an scientific Python package was another broad category seen over and over throughout the Twitter conversation. - -- How many users are using the tool? -- How many stars does the package have? -- How often is is the package cited? - -### Is the package cited? - -While we'd love to quantify citations, the reality of this is that most people -don't cite software. But some do, and we hope you are one of them! - - - -### What about stars (and commits) as a metrics of adoption (and maintenance)? - -The tweeter below looks at stars and commit date as signs of community -adoption and maintenance. - - - -As Chris Holdgraf mentions below, a package can reach a point where the same -type of activity can have varying impacts of -perceived level of maintenance. Many users opening issues, -can represent community interest and perhaps even community adoption. And massive -volumes of unaddressed -issues _might_ represent unresponsive maintainers. - -> Or perhaps the maintainers are just overwhelmed by _catastrophic success_. - - - -Yup - - - -> But I need at least 5 (thousand) croissants, now. ANDDDD so does my friend. - -Juan agrees that a steady stream of issues suggests adoption. Especially since -opening issues on GitHub suggests that the users have some technical literacy. - - - -## Metrics quantifying community around tools - -I'd be remiss if i didn't at least mention that some of the discussion steered -towards community around tools. For instance, Evan brought up community governance -being a priority. - - - -But the reality of our users was summarized well here by Tania. Most scientists -developing tools are trying to simplify workflows with repeated code. Workflows -that others may be trying to develop to do the same thing. They aren't necessarily -focused on community, at least not yet. - - - -Further, capturing metrics around community is hard as Melissa points out. Most -of the above resources don't capture these types of items. And also, how would one -capture the work on a community manager quantitatively? - - - -## Are some things missing here? Yes, of course. - -But it's a great start! - -- Some other items that didn't come up in the conversation included downloads. -- Packages found as dependencies or in environments on GitHub - -Joel rightfully noted that my original tweet seemed less concerned with package -quality and more concerned with community and use. I think they are right. -We are hopeful that peer review metrics and recommended guidelines for packaging -will get at package quality. - - - -## Summarizing it all will be a WIP (Work In Progress) - -There is a lot of work to do in this area. And a lot of work that has already -been done to learn from. It's clear to me that we should start by looking at -what's been done and what people are already collecting in this area. And then -customize to our needs. - -A few items that stand out to me that we could begin collecting now surrounding -package maintenance and community adoption are below. -This list will grow but it's just a start. - -### Package Maintenance and Community Adoption - -- Date of last commit -- Date of last release -- Annual frequency of releases -- Number of open issues / quarter -- Issues opened by maintainers vs non maintainers -- Number of commits made by non maintainers / year - -### Package quality & infrastructure - -- GitHub core checks for README, Contributing guide, etc -- Documentation & associated documentation quality (vignettes and quick start) -- Defined scope and intent of package maintenance -- Testing and CI setup - -I will share a more comprehensive list once we pull that together as an organization -in another blog post. Stay tuned for more! - -## Thoughts? - -If you have any additional thoughts on this topic or if I missed important -parts of the conversation please share in the comment section below. diff --git a/_posts/2023-03-22-demystifying-python-packaging.md b/_posts/2023-03-22-demystifying-python-packaging.md deleted file mode 100644 index 85f88285..00000000 --- a/_posts/2023-03-22-demystifying-python-packaging.md +++ /dev/null @@ -1,189 +0,0 @@ ---- -layout: single -title: "Demystifying the Python packaging ecosystem " -blog_topic: education -excerpt: "pyOpenSci has published the packaging structure and tool section of it's guide. This chapter has been reviewed by dozens of core community members to ensure accuracy and is community-driven. " -author: "Leah Wasser" -permalink: /blog/demystifying-python-packaging.html -header: - overlay_color: "#33205c" -categories: - - blog-post - - highlight - - python-packaging -toc: false -comments: true -last_modified: 2024-08-29 ---- - -## A guide to make Python packaging easier for scientists - -I've spent the last few months working on creating a Python packaging guide. This guide seeks to help those creating new scientific Python packages select a packaging tool and workflow. This guide also supports the [pyOpenSci peer review process](https://www.pyopensci.org/about-peer-review/). - -Below, I provide a brief overview of our content development process given the [packaging tool guide chapter has been published](https://www.pyopensci.org/python-package-guide/package-structure-code/intro.html)! Yay! - -# TL;DR - -There are a few key takeaways from this post: - -1. Many learn how to create a Python package by copying the structure of other packages built by maintainers they respect. While this is an OK approach, it doesn't help people better understand the ecosystem. And in some cases it can cause more confusion given the many different options and approaches. -2. Much of the existing Python packaging blog post and guidebook content online is dated. The community needs dependable, maintained resources that they can rely on! -3. Much of the existing packaging resources assumes some base technical knowledge about packaging making it difficult for beginners to work through and understand the content. -4. pyOpenSci is [creating a Python packaging guidebook](https://www.pyopensci.org/python-package-guide/) driven by significant community input from packaging maintainers, scientists and others in the community. The guide's goal is to help people understand best practices for packaging that follow current standards and use current tools. -5. We will also be creating some tutorials in the near future. - -The [packaging chapter of our guide](https://www.pyopensci.org/python-package-guide/package-structure-code/intro.html) is online now! Stay tuned for more content on environments, CI and testing! - -
- - Diagram showing a decision tree with the tools flit, hatch, pdm and poetry highlighted. The diagram is called Pick a Packaging Tool. - -
An diagram from our packaging guide that shows the core packaging tools that scientists may need to decide between. For a pure python package you can use any tool that you wish based on the features that you want in a tool. However, if you have a more complex build you may want to use PDM or the PyPA's build tool with a powerful back-end like meson-python. -
-
- -## My jump into packaging - -In the Fall of 2022, in support of my [new role as Executive Director of pyOpenSci](/blog/new-executive-director-leah-wasser.html), I began to explore Python packaging tools in an effort to update our guidebook in support of our [package peer review program](https://www.pyopensci.org/software-peer-review/about/our-goals.html). - -I saw significant community confusion around how to create a Python package. But, in my mind, it wouldn't be that big of a challenge to create a guidebook. - -- There are a bunch of tools out there. -- There are a bunch of Python standards that people should follow - -I just needed to find the combination of tools and standards that we could recommend to people in an attempt to demystify the packaging ecosystem. - -No problem, right? - -At the same time I noticed that many did not want to talk about -Python packaging. And I wondered, why? - -
- - A meme showing a mountain lion with it's little paw up like an italian mobster. the meme text says - so you want to talk about python packaging.. - -
People didn't want to talk about Python packaging. It was such a painful, sore topic. -
-
- -### My approach to learning how to create a Python package - -I've worked on the development of 3 other Python packages. Each time my approach to create a package was asking the question: - -- What would [GeoPandas](https://geopandas.org/en/stable/) do? - -GeoPandas is a spatial library that supports working with vector data (think points, lines and polygons). I decided to follow their structure, because I respected the Geopandas maintainers greatly, and I had [contributed to the package.](https://github.com/geopandas/geopandas/pull/1128) - -My approach to packaging was: "monkey see, monkey do". I was the monkey. - -I also munched on some bananas. It worked out alright. - -## The monkey approach to packaging is not ideal - -Copying a package's structure is like copying code from stack overflow and pasting it into your workflow in hopes it runs. If it doesn't run, you don't know enough to fix it! Frustration sets in. - -However, at least in Stack Overflow you can see when the post was published and know that it might be dated. I found it hard to find updated information on Python packaging tools. I found this particularly challenging considering I found so many tool options. And each tool had a level of documentation that assumed some depth of knowledge around Python packaging. - -Where does the authoritative and complete guide to packaging live and who maintains it? Further, is it helpful enough for a begineer to dig into and get started quickly? - -## In education, early wins are key - -I've taught data intensive science for almost 20 years. If there is one thing I know about teaching those new to technical areas, it is that early wins are critical. Whether the win is creating a simple data plot within the first 20 minutes of a workshop, or using an `init` method in PDM to create a package structure, early wins can motivate a beginner mind as: - -1. It builds confidence that they can do it -2. That confidence allows a learner to continue even when it gets more complicated - -I struggled to find any resources that provided users of Python packaging tools with early wins. Rather, I found that I needed to increase my technical knowledge of packaging to even understand many of the resources out there. - -### How could pyOpenSci help? - -To support pyOpenSci's goals of making packaging easier for scientists while also improving package quality I knew we needed to create a guide that would help others navigate the packaging ecosystem. At a minimum, helping users understand the tool landscape and how to pick a tool was a good start. - -### Python packaging is not bad. It's just not well documented - -From all of the above I came to a conclusion that Python packaging is not bad. It's just not well documented. If people understood what all of the tools did and how to pick one, it might be akin to shopping for a car\*. - -_\*But without the annoying sales person who might assume you know nothing about cars if you are a women..._ - -## A Python packaging guide for scientists - -In creating this guide, I talked with scientific Python tool maintainers, folks from [PYPA](https://www.pypa.io/en/latest/), [scientific python](https://scientific-python.org/) and maintainers of core packages (such as Flit, Hatch, Poetry and PDM) to get insights into common workflows, common challenges and tools that folks are using. This guide has been a true example of community-driven content. [If you are curious, you can see the contributor list here.](https://github.com/pyOpenSci/python-package-guide#contributors-) - -The packaging chapter alone had over [200 comments to address in round 1](https://github.com/pyOpenSci/python-package-guide/pull/55) of reviews. And another [200+ in round 2 of review](https://github.com/pyOpenSci/python-package-guide/pull/55). All of the chapters in our guide go through community review however this particular chapter elicited a LOT of strong response regarding which tools do what, and how they should be described. - -Sometimes, the discussions got tense. People have strong opinions about packaging approaches. Also, not everyone agrees on the best technical approaches. But even more interesting is that many involved knew something about some of the tools but often that was based on word of mouth or a quick glance at documentation. (this is largely because tools are evolving quickly). The people that knew the most were also the most technical, and often involved in the actual development of the tools. - -## An assessment of python packaging tools - -My take away from all of this: - -After hundreds of comments and conversations; - -After testing each one of the tools in our guide with a start to end workflow; - -My takeaway is that Python doesn't have a packaging problem (if you are a user creating a pure Python package). - -Python has a much more human problem where approaches to packaging are simply unclear, not well documented and often debated - heavily. - -Further, the standards created for Python packaging while important, live on a website that is not intended for the broader public to use. - -Sure, there are many tricky parts to packaging. And understanding the standards can be even trickier. This is certainly not a perfect system. - -However, we **can** create packages using the given existing tools -- now! I promise, this is true. - -It's just (extremely) hard to figure out: - -1. how do you create a package, -2. which tool should we use, and -3. why use that tool over the others? - -The Python ecosystem is evolving rapidly. Approaches evolve and it's hard to know which approaches are the most current. Those who deeply understand the packaging challenges represent a small subset of the community and also are technically proficient. - -In general, users want to use the simplest approach to publish their packages online. - -Remember - early wins go a long way. - -
- - A meme from the movie flight flub showing brad pitt with his arms crossed in a black tshirt. The text says - the first rul of peps is do not talk about peps. - -
At one point, I was trying to link to Python standards - known as PEPS (Python Enhancement Protocols) given everyone was sharing the various PEPs that tools follow. I was then told that we should avoid linking to these pages as they weren't intended for public consumption. If that is the case then how does the public get current information about standards? While I understood the goal, My mind was a bit blown. This was one of many points of confusion that I had to sort through. :) -
-
- -## Assessment of packaging tools functionality - -At the same time there is no good assessment that i've seen of the tools -that do exist to help users in the ecosystem. I had questions about: - -- what each tool does -- what each tool could do better. -- And how I should pick a tool for my packaging needs. - -It was clear that people want that guidance. - -## A few spoilers regarding what is to come - -With this all said, i'll now set the stage for what's to come from -pyOpenSci in the upcoming months. And what i've learned so far. - -- There are a few great packaging tools that support comprehensive build workflows. -- Many of the packing tools out there only have one core maintainer (a low bus factor). What would happen if a few of them just teamed up and worked together (and with the community) to move forward? Or could we somehow change that to add stability to the ecosystem? -- We need better documentation with clear beginning to end quick-start tutorials that help new users get started. If the tools were better documented more people would use them. - -Right now, Poetry is the most common (modern) packaging tool being used. Have a look at its documentation and you'll see why! PDM, however, has numerous features that are be ideal for the scientific ecosystem's needs. - -Specifically it allows you to use [different build back ends](https://www.pyopensci.org/python-package-guide/package-structure-code/python-package-build-tools.html#build-front-end-vs-build-back-end-tools), which is good news if you are creating either a pure python package OR a package with some C/C++ extensions. - -_Poetry can't (yet) be a single solution to packaging because right now it's support of non pure python builds is not documented (and might not ever be). But it could be a great solution for those creating pure Python package._ - -## What's next? - -In the next few blog posts i'm going to present each Python build workflow tool including: - -- Flit -- PDM -- Hatch and -- Poetry. - -I'll break down the pros and cons of using each tool. I will also provide examples of what using the tool looks like. In the meantime, [check out our shiny new packaging chapter here to see the overview of packaging tools and approaches for scientists creating pure Python packages](https://www.pyopensci.org/python-package-guide/package-structure-code/intro.html). diff --git a/_posts/2023-06-13-pycon2023-peer-review-david-nicholson-crowsetta.md b/_posts/2023-06-13-pycon2023-peer-review-david-nicholson-crowsetta.md deleted file mode 100644 index 2928760e..00000000 --- a/_posts/2023-06-13-pycon2023-peer-review-david-nicholson-crowsetta.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -layout: single -title: "Sorry This Talk Doesn't Have any ChatGPT in It: pyOpenSci peer review of Crowsetta " -blog_topic: software -excerpt: "David Nicholson gave a lightning talk about his experience going through the pyOpenSci peer review process with his Python package called Crowsetta. Learn more about the people involved in peer review and watch the 5 minute video here." -author: Leah Wasser -permalink: /blog/pycon2023-crowsetta-peer-review.html -header: - overlay_color: "#33205c" - overlay_image: /images/blog/headers/david-nicholson-2023-header.png -categories: - - blog-post - - highlight - - peer-review -toc: false -classes: wide -comments: true -last_modified: 2024-08-29 ---- - -# pyCon 2023 US -David Nicholson Lightning Talk on pyOpenSci Peer Review - -David Nicholson, our pyOpenSci Editor in Chief, gave a fantastic lightning talk this year at [pyCon US 2023](https://us.pycon.org/2023/). This year's pyCon was held in Salt Lake City, Utah in April. David braved the expansive keynote room stage - talking to a gigantic room full of Pythonistas. He spoke about his experience going through our scientific Python software peer review process. - - - -## Submitting Crowsetta to pyOpenSci for peer review - -Just a few months prior, David had submitted a package he's been developing called Crowsetta, that helps scientists work with annotations for animal vocalization and bioacoustics data. Given he is the Editor in Chief of our peer review process, we had wonderful volunteers from our editorial team step in to run the review to ensure it wasn't in any way biased. - -
- - Image showing the title slide of David's talk. At the top is says - Sorry this talk doesn't have any ChatGPT. It then says my experience submitting to pyOpenSci. At the bottom you can see david's website (nicholdav.info), github NickleDave, Twitter nicholdav and mastodon account nicholdav@fosstodon.org - -
Intro slide for David's Lightning talk at pyCon 2023 US. -
-
- -[crowsetta](https://github.com/vocalpy/crowsetta) - -In his talk (which was NOT about ChatGPT in case you were wondering :) ), David talked about who was involved and what the process and his experience was like. Check out the video below to learn more! - -The talk itself is about 5 minutes long but you can always keep it running to see the other lightning talks posted by the pyCon organizers. All of the pyCon 2023 US talks are online if you want to check out some of the others! [You can also check out our -pyOpenSci presentation about Python Packaging and experiences with the sprints here if you'd like.](/blog/pycon-2023-packaging-presentation-sprints-leah-wasser.html) - -## About the pyOpenSci peer review process - -Crowsetta went through our [pyOpenSci peer review process](https://www.pyopensci.org/about-peer-review/) in the Spring 2023. [If you want to check out the review for Crowsetta, click here.](https://github.com/pyOpenSci/software-submission/issues/68). David also took advantage of our [partnership with the Journal of Open Source Software (JOSS)](https://www.pyopensci.org/software-peer-review/partners/joss.html) which allowed the Crowsetta to become both a vetted pyOpenSci tool and also to get a cross-ref enabled citation from JOSS. Through this partnership JOSS accepts our review as theirs and only reviews the submitted paper. - -## Learn More - -David's talk can be found in our [pyOpenSci Zenodo community](https://zenodo.org/communities/pyopensci/?page=1&size=20), [here](https://zenodo.org/record/8033167). - -[![zenodo doi citation for David's lighting talk](https://zenodo.org/badge/DOI/10.5281/zenodo.8033167.svg)](https://doi.org/10.5281/zenodo.8033167) diff --git a/_posts/2023-06-14-pycon-maintainers-summit-python-packaging-talk-leah-wasser.md b/_posts/2023-06-14-pycon-maintainers-summit-python-packaging-talk-leah-wasser.md deleted file mode 100644 index bc107155..00000000 --- a/_posts/2023-06-14-pycon-maintainers-summit-python-packaging-talk-leah-wasser.md +++ /dev/null @@ -1,235 +0,0 @@ ---- -layout: single -title: "My First Time Attending PyCon - A Tale of Sprints and Python Packaging" -blog_topic: education -excerpt: "This year I attended by first PyCon US meeting representing pyOpenSci. In this blog I talk about my experience, highlighting the sprints we lead and talks on packaging." -author: "Leah Wasser" -permalink: /blog/pycon-2023-packaging-presentation-sprints-leah-wasser.html -header: - overlay_color: "#33205c" -categories: - - blog-post - - highlight - - python-packaging - - talks -classes: wide -toc: false -comments: true -last_modified: 2024-08-29 ---- - -## How it all went down - PyCON US 2023, Salt Lake City, Utah - -This year was my first time attending pyCon US! I was intimidated to attend such -a big Python meeting. For years i've attended science meetings such as -AGU (American Geophysical Union), ESRI (GIS) users conferences and ESA (Ecological -Society of America). I've been to and lead data science hackathons and been to -the annual SciPy meeting. But i've never been to a pure tech conference. - -Even after teaching data science using R and Python for the past 10 years I -_STILL_ feel like an imposter sometimes. - -_What's up with that?_ - -But I went and had a fantastic time. Getting to talk to people all day, every -day about all things Python felt like how I might imagine a trip to Disneyworld -feels for an 8 year-old... (minus the cotton candy, costumes and the upside down -rides). - -I felt energized, excited. I learned so much and met SO MANY incredible people. - -A few highlights of the meeting are below. - -## Python packaging, packaging, packaging - -Did I mention packaging? No? - -Ok well I spent a lot of the meeting talking to people about Python packaging. - -I even got to present in the maintainers summit (see the video below) on... -guess what? - -PYTHON PACKAGING! - -It was my first time recording myself talking formally about packaging and using -OBS studio. And I have to say I have some sort of shifty eye syndrome going on. -I think I still have a bit to learn from those YouTubers on video creation! - - - -This presentation echoed the sentiment that I shared in this [blog post about -Python packaging.](/blog/demystifying-python-packaging.html) - -### TLDR breakdown - -If you're short on time, the take-aways of the talk were: - -- Our [pyOpenSci packaging guide](https://www.pyopensci.org/python-package-guide/) is community-driven. It is created via a heavily-moderated review process that consisted of several rounds of reviews. -- Guidebook reviews were heavily moderated. This ensured that we formed consensus on packaging recommendations. -- Packaging might seem "messy" but really it's just a matter of knowing what tools to use and how to use them. While this is not a simple task. We believe that these community-driven resources will help the scientific Python community by demystifying the packaging ecosystem! - -### Creating our packaging guide - -I spent a bit of time in that video talking about how we create our guides. - -- Every part of our guides are reviewed by lots of people. -- Reviews for just a few pages of the guide could have 200 or more comments. -- Our process is normally several steps including an initial writing of content that is reviewed by experts. Followed by 2 rounds of open reviews of the content. The image below describes that process. - -
- - Graphic that has a large purple thick arrow. The title says Python Packaging Guide - Our Process. The sections in the arrow include Talk with core experts (write a section of the guide), semi-closed review (core experts review), Open Review 1 (ping tool developers and maintainers and welcome broad community feedback) and Open Review 2 (welcome broad community feedback). - -
Image from my talk that shows our packaging guide review process. Our packaging guide is community-driven. This means that every page of the guide has been reviewed by dozens of people with expertise in the packaging space. While this meant that it is taking extra time to create the guide, it also means the community supports it. We value community knowledge and input at pyOpenSci. -
-
- -Another part of our packaging guide review process is getting input from packaging -experts in the community. These experts come from the core python community, -the packaging community, the scientific community and even maintainers of core -packaging tools. - -_Leave no stone unturned_ (my motto when doing most things). - -
- - .sdf - -
Image from my talk that shows our packaging guide review process. Our packaging guide is community-driven. This means that every page of the guide has been reviewed by dozens of people with expertise in the packaging space. While this meant that it is taking extra time to create the guide, it also means the community supports it. We value community knowledge and input at pyOpenSci. -
-
- -### Why I LOVE peer review - -If you haven't already guessed, I have a deep seeded love for peer review. In my -mind, anything that is produced that requires a lot of technical knowledge will -only be improved when vetted by lots of people. - -Sure, it takes a lot of extra work to produce a guide that way. And it slows down -the process. But, the end product will be worth it. - -As such we not only review Python packages at pyOpenSci. We also make sure that -all of our content is heavily reviewed too. - -## The only (woman) in the room - -So I have to say this. While pyCon was the most wonderful experience, I did have -a few awkward moments. For one, I was often one of -the few female identifying people in the room. This was particularly true are the packaging summit where I gave a small presentation related to PyPA and packaging tools! - -I did feel welcomed and included. BUT, I can say that it is interesting to walk -into a room and _know_ that you are different. I noted the -things that made me feel super welcome and comfortable. - - - -## pyCon was more about (freakin amazing) people than code - -I can't even begin to highlight ALL OF THE AMAZING PEOPLE I met at pyCon. In some -cases I met folks who I had been interacting with online Such as C.A.M. from the -Python core team who I "met" on the Python Discourse. Or [Pradyun](https://github.com/pradyunsg) another Python core dev who has been -involved with pyOpenSci providing guidance on the packaging space for months. Pradyun is also a part of our advisory council. His expertise is really invaluable to our organization. - -I got to meet [Erik](https://github.com/eriknw) who's Python package - [python-graphblas is going through our -peer review process right now.](https://github.com/pyOpenSci/software-submission/issues/81) - -I met Chase, CEO of a really cool company called [Million Concepts](https://millionconcepts.com/about.html) and some of the folks from the [Python Heliophysics community](https://heliopython.org/). - -Finally, I got to meet and hang out with the amazing [Inessa Pawson](https://github.com/InessaPawson). If you haven't heard of -Inessa's work it's extraordinary. Inessa has been working as a contributor lead -for the Numpy project and is also a project manager for Open Teams. - -There are so many other people who I got to know and build working relationships -at this meeting. - -## pyOpenSci Sprints at PyCON - -pyOpenSci also lead 2 sprints at pyCon! On Sunday we lead a [mentored sprint](https://www.mentored-sprints.dev/). If you haven't heard of mentored sprints, they are an -amazing format that allows those who are newer to contributing to open source -to get support in the contribution process. - -At the mentored sprints I was grateful to have [Luiz Irber](https://github.com/luizirber) and [David Nicholson](https://github.com/NickleDave) supporting -the (larger-than-expected) group! Luiz served as editor for pyOpenSci years ago -during one of our very first reviews. And David is now our editor in chief of -pyOpenSci. - -We had a full table plus an overflow table of -people who wanted to contribute! And each of them was able to contribute (many -for their very first time!!). It was awesome. - -
- - Image showing people at laptops sitting around a round table wearing masks during our PyCon 2023 mentored sprint. - -
Contributors working together in small groups during our mentored sprints at PyCon 2023. People worked together on issues in pairs and had help from dedicated mentors. Most of the contributors had Python experience but also most had never contributed to open source before. -
-
- - -The people at the sprint were not the people who I expected. -Many of them had significant technical skills and backgrounds. But many of them -also had never committed to an open source project. Perhaps they had used subversion -but not git / GitHub. Perhaps they knew python well but didn't know where to start -in terms of contributing. - -In total we ended up with 8 pull requests submitted during the sprints and 2 -others submitted after. Every pull request was from a new contributor to -pyOpenSci. And also they were mostly made by those new to contributing in general. - -If you want to check any of them out - please click on any of the links below! - -
- -## Contributions from the pyOpenSci mentored and regular sprints at PyCon US 2023 - -Contributions to open source tools and communities can come in all shapes and -sizes. Note that some of the items below are small fixes (which are a huge help). -And others are a bit more involved. - -### More technical pr's - -- [This pr was a bit more involved and included some changes to how we handle tokens in our metrics package.](https://github.com/pyOpenSci/update-web-metadata/pull/24) -- [This Python code update fixed the format that we were using to manage authentication tokens](https://github.com/pyOpenSci/update-web-metadata/pull/23) -- [This is a more technical PR by Austin that updated some of our typing and cleaned up some of our API request calls](https://github.com/pyOpenSci/update-web-metadata/pull/19) -- [This pr by Tiffany updated parsing our review issues. It ensured that we grabbed all maintainers from a review issue so we can list them in our package list once the package is accepted.](https://github.com/pyOpenSci/update-web-metadata/pull/14) -- [This pr was another technical one submitted by Austin to fix how we were authenticating with the GitHub API](https://github.com/pyOpenSci/update-web-metadata/pull/11) -- [Barnabas submitted this PR. It fixes a glitch in the website to ensure all maintainers are listed there (building on top of tiffany's PR above that fixed the code that actually collected maintainer names). ](https://github.com/pyOpenSci/pyopensci.github.io/pull/172) - -### Less Technical PR's - -These pr's are highlighted here because it's important to know that not all contributions -need to be highlight technical. They can be text fixes that are equally important to projects. - -- Not all pr's need to be huge and technical. This one was actually done using only the GitHub interface! [This is a much less technical pr that updated the repos that we were using to grab contributors from. We wanted to grab all contributors so we can recognize them on our website.](https://github.com/pyOpenSci/update-web-metadata/pull/13) -- [This was a less technical but equally important issue opened that suggested we add some more information to our guidebook. Not all contributions need to be code!](https://github.com/pyOpenSci/pyopensci.github.io/issues/168) -- [This is another example of a pr that has no code involved! It was an update to our guidebook simplifying text that was redundant! Submitted by Mahe](https://github.com/pyOpenSci/software-peer-review/pull/210) - -### Packaging Guide pull requests - -In the spirit of understanding that not all contributions need to be code! The pr's -below, submitted by Jeremy, identified lots of typos in our packaging guide! This -was SO SO helpful to us!! - -- [Packaging guide typo fixes.](https://github.com/pyOpenSci/python-package-guide/pull/82) -- [Second pr fixing typos.](https://github.com/pyOpenSci/python-package-guide/pull/81) - -
- -All of the contributors are now listed on our website. And we are grateful for -each and every one of them! - -## Would I attend pycon again? - -I'm sure I don't have to tell you the answer to that question. - -heck-yea I would! - -The people I met at that meeting even sitting in the lunch room were incredible. -And many of them have become colleagues that I am still in touch with and who -are now involved with pyOpenSci. - -## A final highlight of the meeting - David Nicholson's Lightnight talk - -One of the other highlights of the meeting that I can't forget to mention is -David getting up on the ginormous speaker stage in the main ballroom and -giving a lightning talk about his experience submitting Crowsetta, a Python package, to pyOpenSci. [Check out a blog on his talk and watch the 5 minute presentation here.](/blog/pycon2023-crowsetta-peer-review.html) It was awesome! diff --git a/_posts/2023-07-18-pyopensci-scipy-2023.md b/_posts/2023-07-18-pyopensci-scipy-2023.md deleted file mode 100644 index ca08a26c..00000000 --- a/_posts/2023-07-18-pyopensci-scipy-2023.md +++ /dev/null @@ -1,225 +0,0 @@ ---- -layout: single -title: "Shark Attacks and Open Science - pyOpenSci @ SciPy 2023 Meeting" -blog_topic: community -excerpt: "The SciPy 2023 meeting did not disappoint this year. We have a whole new group of contributors helping to build this vibrant and diverse Python open science community. Learn about my experience there." -author: "Leah Wasser" -permalink: /blog/scipy-2023-community.html -header: - overlay_color: "#33205c" -categories: - - blog-post - - highlight - - community - - talks -classes: wide -toc: false -comments: true -last_modified: 2024-08-29 ---- - -## Shark attack - pyOpenSci at SciPy - -I was so excited for SciPy this year. - -I wanted to spread the word about pyOpenSci's core mission - supporting -the scientific open source Python community. I wanted to get more people -involved. - -pyOpenSci represents everything that matters most to me: - -- 🌱 Community & People -- 🎓 Education -- 🔓 Open Science and Open Source -- 🌈 Diversity, Equity and Inclusion - -## Unplanned is often best - -I am not used to going into a meeting with no specific plans and obligations. -While pyOpenSci didn't get a talk or a community session / BoF this year, we -did get a lightning talk! It was a randomized selection, and I threw my name -into the bucket (literally) with fingers crossed that i'd get a lightning talk. - -And on the final day of the meeting, I was selected to present! - - - -## The shark attack - my lightning talk about pyOpenSci - -Let me give you the backstory on lightning talks at SciPy. It's known that -moderators will often "play" with those presenting. - -Puns are always pervasive and community embraced! - -This year there was a "sea" theme featuring sharks and crab claws. 😂 Watch -below as the session is started with a crab claw pun by Paul followed up with -a shark attack on yours truly from [Madicken](https://github.com/munkm/). You will also learn about the -pyOpenSci mission and vision. - - - -## Sprints - my new favorite part of every meeting - -A sprint, in the tech world, is a short time period where people on a team work -together to complete something on a technical project. At conferences, there are -often open sprints. The idea here is that people, often some of whom are new -to a project, get together in person and work on things that the project needs. - -### Mentored sprints make open source more inclusive - -In our open source world we also have [mentored sprints](https://www.mentored-sprints.dev/). -The term mentored sprints was coined by an [amazing team of people](https://www.mentored-sprints.dev/team/) including [Tania Allard (who's passion for open source and open data resonates with my own)](https://www.trallard.dev/). -It focuses on supporting those who are new to sprinting and using platforms such as -GitHub in making their first contribution to open source. - -Given pyOpenSci's core values around diversity equity and inclusion, every -sprint we hold is a mentored sprint as far as i'm concerned! - -This was the second sprint that i've lead with the [first being at pyCon US 2023](https://www.pyopensci.org/blog/pycon-2023-packaging-presentation-sprints-leah-wasser.html). - -### An organized list of tasks is key for any sprint - -My friend, colleague and [esteemed pyOpenSci advisory council member,](https://www.pyopensci.org/our-community/index.html#pyopensci-advisory-council) [Inessa Pawson](https://github.com/InessaPawson) taught me -that: - -1. It's best to go into a sprint with an organized set of help-wanted issues. -1. Identifying issues that could be completed in a few hours to a day is ideal. -1. And tagging issues as beginner friendly helps those who are newer to sprints - -
- - An image with a black background showing all of the issues that we could use help with at pyOpenSci. The caption for this image contains a link to the board which will allow you to hear and read through each issue listed on the board. - -
Our pyOpenSci help-wanted issue board has a list of things that we could use help with. Issues that could be done during a sprint are tagged "sprintable". Issues that are ideal for beginners are also tagged - beginner friendly. View the board here. -
-
- -I went into our SciPy 2023 sprint with a [more organized pyOpenSci help-wanted board.](https://github.com/orgs/pyOpenSci/projects/3) -This board has been a great way to keep track of things -that we need help with. - -** GitHub PROTIP:** I struggled at PyCon with assigning people who didn't belong -to a repository or our organization to specific issues. Now, I know that -if someone comments on an issue first, I can then assign it to them -(many thanks to [Thomas Fan](https://github.com/thomasjpfan) for the tip!!). -{: .notice .info} - -## So many helpful contributions to pyOpenSci! - -I am absolutely blown away by and profoundly grateful for the -support that pyOpenSci received at this year's SciPy sprints! - -We had over 20 pull requests emerge from this sprint - WOW! Two sprinters also -submitted their first ever contributions!! - -** Info:** a pull request, known as a "pr", represents a set of suggested changes to a set of code or text. In the GitHub.com interface you can view the suggested changes and comment on them - in the same way that you might comment on suggested changes in a Google doc. -{: .notice } - -Some of the contributions included: - -- Updating our website workflow to allow for site preview on every pr. This means that no one needs to setup a ruby environment locally in order to view website changes. And the less ruby environments contributors need to deal with, the better as far as I am concerned :) !! -- Updating and enhancing our contributor package metadata workflow to be more efficient and effective -- _A first contribution ever!!_ Grace helped us by fixing typos in our throughout our peer review guide! She called these fixes trivial but there is NO SUCH THING as a trivial pull request. We need fresh sets of eyes on all of our guides and appreciate any and all fixes that pr's bring big or small! - -In case you are curious, most of the pull requests submitted during the sprint this year are listed below: - -
-## Pull requests submitted to pyOpenSci the 2023 SciPy sprints -- **Thomas:** Thomas submitted a [set of pr's that allow us to preview our website after every pull request](https://github.com/pyOpenSci/pyopensci.github.io/pulls/thomasjpfan) is submitted. -- **Mike:** Mike tackled our automated workflow that tracks contributors across our GitHub repositories and also that tracks packages, reviewers and editors in our review process. -- **ruoxi** Ruoxi submitted an issue surrounding a rendering issue with our packaging guide in the Safari browser. And also a [pull request updating text around our partnership](https://github.com/pyOpenSci/software-peer-review/pull/232/files) with the Journal of Open Source Software (JOSS) where they accept our review as theirs and only review your paper. -- **Grace:** [Grace made her first, second, third ...seventh!! pull requests ever fixing typos in our peer review guide](https://github.com/pyOpenSci/software-peer-review/pulls/g-patlewicz) -- **Ricky:** [Url redirect fixes on website](https://github.com/pyOpenSci/pyopensci.github.io/pull/235) -- **Kerry:** [Fixed the packages order so that newest was at the top of the page not the bottom](https://github.com/pyOpenSci/pyopensci.github.io/pull/234) - -
- -### People kept sprinting without me! - -I left before day two of the sprints. However, that did not stop -the community from continuing to sprint and contribute to -pyOpenSci! People continued to work additional -website fixes that were still open our project board. - -### Lessons learned from SciPy 2023 - -I learned a lot this year from SciPy. - -Sometimes the best moments are the unexpected ones. I had the -chance to connect with amazing individuals and share pyOpenSci's -impactful mission that I care about so deeply. - -And the best part? Our pyOpenSci community continues to grow, attracting more -wonderful Pythonistas who share our vision. Together, I'm confident that we -will make a positive impact on scientific open source Python community. - -That's what truly matters. - -And I gave out a lot of pyOpenSci stickers too! - -
- - Two pyOpenSci sticker designs. the one is a light purple flower with white stamen and the letters pr and O as the center of the sticker. The S is a snake. The econd sticker is a darker and lighter purpler that says pyOPenSci and has the top half of a flower above the O in open. - -
Fresh off the press - pyOpenSci stickers! -
-
- -## For all of you introverts - a few tips that helped me this year - -My approach to participating in SciPy was so much better than that at pyCon. - -I learned some valuable lessons about taking care of -both my work and my mental well-being. As an introvert in a busy meeting filled -with awesome colleagues, it's easy to get burnt out. - -Here's what I did to make sure I left the meeting feeling refreshed and energized: - -- 🌟 I prioritized mental health: It's all about balance. I put as much effort into taking care of myself as I did into my work during the meeting. -- 🌟 Embraced breaks: During the meeting, I consciously took short breaks to unwind. Whether it was chilling in my hotel room or going for a stroll outside, giving my brain a breather made a world of difference. And guess what? I slept better at night too! - -* 🌟 Me time matters: While I didn't participate in every social activity, I didn't feel like I was missing out. Instead, I used that time to recharge solo and get some extra sleep. And let me tell you, it worked wonders! -* 🌟 Balanced work and recovery: To avoid getting run down, I allowed myself to miss the second day of the sprints. This allowed me to travel home on Sunday and recover in the afternoon with my furry friend, Juno. - -In the end, I may have missed a bit of the action, but the payoff was totally worth it. I left the meeting feeling way better than I did after PyCon. - -So, fellow introverts, remember this little secret weapon called -"recovery time" at your next big event! It's a game-changer! - -## A personal note - flying solo in the open source world is never truly solo - -Back in March 2023, I made a bold decision to leave a toxic academic -environment and fully dedicate myself to building and growing pyOpenSci—an -amazing, community-focused organization. - -Let me tell you, taking that leap of faith was pretty intimidating. The academic -setting had taken a toll on me, shattering my confidence and even affecting my -health. But I knew in my heart that I wanted to channel all my energy into -community work, collaborating with people who respected and appreciated me as -much as I respected them. - -And guess what? This journey has been beyond my wildest dreams! Not only has -the pyOpenSci community thrived and made a remarkable impact in just its first -year, but it has also turned out to be the kind of inclusive, supportive -community I always envisioned. - -It's incredible how not only is pyOpenSci helping others, but it's also been a -source of support and healing for me. I couldn't be more grateful for this -vibrant and uplifting environment that we've created together. - -I'll keep pushing forward, knowing that this beautiful journey is just the -beginning. - -Thank you, SciPy for supporting me and reinforcing the fact that I made the right decision! And i'd be remiss if I didn't also thank the pyOpenSci community -that is truly bring pyOpenSci's vision to life. - -## Wrapping up - -And that is all I have to say about SciPy 2023! It was an incredible experience. -If you are reading this and we connected at SciPy this year or if you contributed -to pyOpensci this year, I just want to say thank you. - -From the bottom of my heart. I see change coming in the upcoming years. -pyOpenSci wants to be a part of and to drive that change!! - -We can't achieve that without your help! diff --git a/_posts/2023-09-11-community-manager-job-available.md b/_posts/2023-09-11-community-manager-job-available.md deleted file mode 100644 index eab69280..00000000 --- a/_posts/2023-09-11-community-manager-job-available.md +++ /dev/null @@ -1,175 +0,0 @@ ---- -layout: single -title: "CLOSED: pyOpenSci is Hiring a Community Manager" -blog_topic: updates -excerpt: "This position has been filled. pyOpenSci is hiring a community manager. This position will oversee numerous aspects of pyOpenSci's community building efforts including managing social media, our slack organization and writing blog posts. It will also be heavily involved in our DEIA related activities." -author: "Leah Wasser" -permalink: /blog/pyOpenSci-hiring-community-manager-fall-2023.html -header: - overlay_color: "#33205c" -categories: - - blog-post - - jobs -toc: true -comments: true -redirect_from: - - /blog/pyOpenSci-hiring-community-manager-spring-2023.html -last_modified: 2024-08-29 ---- - -
-**IMPORTANT:** we are no longer accepting applications for this position. -Thank you for your interest. -
- -## Job Posting: pyOpenSci Community Manager - -pyOpenSci is accepting applications for a Community Manager. The Community -Manager supports growth and development of an inclusive pyOpenSci community. Our vibrant community is -dedicated to supporting high quality Python open source software that drives open science. - -While our organization is global we can only accept applications from candidates who are eligible for employment in the United States. - -## About pyOpenSci - -pyOpenSci is a diverse scientific open source / open science community that: - -1. Develops processes, guidelines, and training around the creation and maintenance of scientific Python packages. -2. Leads a community-driven peer review process of Python scientific software - -pyOpenSci’s core program is open peer review of scientific Python software. -Through peer-review we enforce community-defined packaging standards while -improving usability, documentation and package quality. Further, we are creating a catalog of vetted scientific [Python tools](https://www.pyopensci.org/python-packages.html) that have ongoing -maintenance for scientists to use. - -Core to our mission is increasing participation -of groups that have historically underrepresented / excluded from the open -source and open science community. We do this through mentorship, training and -strategic partnerships with existing organizations -such as [MetaDocencia](https://www.metadocencia.org/), [Open Science Labs](https://opensciencelabs.org/) and other -organizations. - -pyOpenSci is a community-owned organization, fiscally sponsored by Community -Initiatives. We are grateful to be funded by the [Sloan foundation](https://sloan.org/). - -## About the role / what we are looking for - -We are looking for someone with experience managing, engaging with and inspiring large, -diverse, online communities. This person will promote pyOpenSci's mission to the broader scientific and tech communities. They will also help develop educational content around Python packaging and open science workflows. - -We are looking for someone that is excited about: - -1. facilitating outreach activities -2. developing community partnerships -3. promoting our organization at online and in-person events -4. engaging with our vibrant community to encourage participation -5. developing and teaching open source and open science content -6. writing blog posts and social media content to promote the organization -7. developing educational content around open science and Python packaging -8. getting the word out about pyOpenSci! - -We are also looking for someone that is both committed to DEIA work and also has experience working on programs that target increasing diversity in the scientific open source software community. Ideally, we'd like someone who is familiar with both the Python programming -language and the goals of open science. - -This position provides the opportunity for someone to drive the pyOpenSci -mission of building a diverse and supportive community forward. - -### Hiring overview - -This is a full-time position with benefits with an ideal start date around November 2023 but no later than January 1, 2024. This position will report -to the pyOpenSci Executive Director. Applicants must be eligible for employment in the United States. - -Review of applications will begin on September 1, 2023 and will be ongoing until the -position is filled. All positions in our organization are -grant funded. We currently have funding for this position through at least June 2025. Assuming continued project -success, we will continue to seek additional funding to extend -the work beyond June 2025. - -## Work environment & hiring requirements - -This position is fully remote. We also prefer that you live in the United States to simplify asynchronous remote work. -**IMPORTANT:** While our reach is global, we can only hire someone who is either a resident / citizen in the United states or a resident with a valid work permit. - -All work that you do should exemplify our community [Code of Conduct](https://www.pyopensci.org/handbook/CODE_OF_CONDUCT.html) - -## What Your Key Responsibilities Will Be: - -### Community Management - -- Build and manage an active, diverse, and inclusive online pyOpenSci community by engaging with the community, staying informed of the needs and motivations of current and prospective members and responding with programming that meets these needs -- Manage community conflict and [enforce our Code of Conduct](https://www.pyopensci.org/handbook/CODE_OF_CONDUCT.html) as a driving member of the Code of Conduct team. -- Facilitate regular communication with pyOpenSci community members. -- Organize, plan, and drive participation at community building events, both virtual and in-person, that provide positive and valuable interactions for members, consistent with pyOpenSci’s mission -- Lead diversity, equity, and inclusion (DEIA) efforts to ensure that pyOpenSci members from all backgrounds are supported and empowered to participate. This will include the development of a mentorship program for underrepresented groups in open source -- Solicit new packages for review as well as identify and facilitate the resolution of potential hold ups in the review process. -- Help maintain databases of community members, maintainers and packages for pyOpenSci - -### External Communications - -- Cultivate strategic relationships with new and existing partner organizations Current partners include [JOSS](https://joss.theoj.org/), - [rOpenSci](https://ropensci.org/), [Pangeo](https://pangeo.io/), [The Carpentries](https://carpentries.org/), [MetaDocencia](https://www.metadocencia.org/) and [2i2c](https://2i2c.org/), [astropy](https://www.astropy.org) -- Travel to meetings to represent pyOpenSci and build awareness of pyOpenSci's mission and programs. - -### Content Creation - -- Grow pyOpenSci’s online presence through content creation and management for multiple platforms (e.g. social media, discussion forums, Slack, blogs, and GitHub) -- Identify and fill gaps in pyOpenSci’s web resources, including our peer review and Python packaging guide -- Development of training resources around open source and open science skills - -## About You - What experience you need for this position - -- Outgoing personality that is driven by community work -- Experience working in a relevant discipline such as science communication, science, math, engineering, or computer science (or a bachelors degree in this space). We strongly prefer someone who understands the scientific community. -- Fluency speaking english -- 2+ years of experience working with, promoting and managing communities that support open source projects; alternatively demonstrated experience in the scientific data science space as an evangelist or advocate -- Experience creating strategic content for social media platforms and tools (specifically Mastodon and LinkedIn) and fostering online engagement -- Strong desire to deepen your knowledge of best practices for DEIA efforts and demonstrated experience working in the DEIA space -- Ability to work in a distributed, collaborative team environment - including managing ambiguity, concurrent projects, and work taking place across multiple time zones -- Experience using git/GitHub or willingness to learn -- Excellent time and project management skills -- Excellent oral and written communication skills. These skills will be applied to various areas including website and blog content creation, conflict resolution, technical writing, and external communication at conferences including presentations. -- Strong interpersonal skills. Ideally you have experience cultivating trust through active listening and responsive dialogue to support members in feeling welcome and supported in joining and actively participating in the community -- Creative, motivated problem-solver, with an ability to bring forth new ideas for engagement, create systems from scratch, and find new partners - -## About You - Other expertise that will be useful but not required - -- Fluency speaking Spanish -- Experience developing or contributing to open source software -- Experience or familiarity with Python programming -- Bachelors degree in STEM or communications and/or demonstrated experience working in the open science / open source space. - -## What to expect - -### Within 1 month, you will… - -- Meet and interact with the pyOpenSci community on Slack and begin to regularly communicate with members answering questions and facilitating conversations. -- Contribute to the pyOpenSci documentation for peer review and python packaging -- Have reviewed pull requests that update our python packaging guide, peer review guide and website content -- Written a blog on pyopensci.org introducing yourself -- Setup and be managing pyOpenSci social media accounts on Mastodon, LinkedIn - -### Within 3 months, you will be… - -- Updating our database of people and packages reviewed -- Growing our social media following and support community meetings and events -- Working with the community to develop new blog posts on packages in our ecosystem -- Developing and contributing to a mentorship program to support new contributors to open source -- Curating and contributing content to our online community on [social media](https://fosstodon.org/@pyOpenSci), Slack and other platforms as needed. - -### Within 6 months, you will be… - -- Planning a community unconference online event -- Be building relationships with core organizations in the open source and DEIA open science space -- Be working with the executive director to secure funding for pyOpenSci -- Be working with the executive director to develop a training program and content that teaches scientists how to create python packages and implement open science workflows. - -## Equal Employment Opportunity - -Community Initiatives is an equal opportunity employer and gives consideration -for employment to qualified applicants without regard to age, race, color, -religion, creed, sex, sexual orientation, gender identity or expression, -national origin, marital status, disability or protected veteran status, or -any other status or characteristic protected by federal, state, or local law -Posting Contact Information - -For questions about the job, please email: admin at pyopensci.org diff --git a/_posts/2023-10-18-rse-2023-pyopensci-bof.md b/_posts/2023-10-18-rse-2023-pyopensci-bof.md deleted file mode 100644 index 164b59a6..00000000 --- a/_posts/2023-10-18-rse-2023-pyopensci-bof.md +++ /dev/null @@ -1,223 +0,0 @@ ---- -layout: single -title: "pyOpenSci Python packaging discussion at the Research Software Engineering meeting 2023" -blog_topic: education -excerpt: "pyOpenSci held a community birds of a feather session at the 2023 RSE meeting in Chicago where we discussed peer review and packaging pain points. Learn about the packaging pain points that they RSE-Python community experiences and how pyOpenSci is addressing Python packaging pain points." -author: "Leah Wasser" -permalink: /blog/pyOpenSci-research-software-2023.html -header: - overlay_color: "#33205c" -categories: - - blog-post - - community -toc: true -comments: true -last_modified: 2024-08-29 ---- - -## Intro - -In October 2023, the United States Research Software Engineering (US-RSE), -funded by the Sloan foundation, held its very first ever -[meeting](https://us-rse.org/usrse23/). - -I attended this meeting and lead a community session around our peer review -process and Python packaging. Key TL&DR takeaways were: - -1. Many RSE's use Python (the show of hands in the main conference room on day 2 suggested 60-70% of them use it in their daily work) -1. Most feel like the packaging ecosystem can be confusing to navigate given there are numerous ways of creating a Python package -1. RSE's are using a suite of different packaging tools and approaches in their Python work. - -The good news take away is that the exact pain points described to me at this -meeting are the ones that pyOpenSci is taking on. We are currently working on -an end-to-end packaging tutorial that hopefully will shed light on and demystify -a complex but vibrant ecosystem of Python packaging tools and options. - -## What is a Research Software Engineer? - -So, what is an Research Software Engineer (RSE)? While the position has existed for some time, an RSE can be [loosely defined](https://us-rse.org/about/what-is-an-rse/) as someone who uses code regularly to do research. Many RSE's also work on developing -software, in Python. - -As a side note this is a position that hasn't been traditionally -supported by default academic environments even though this work is critical -to research in the open science space. The RSE position should be a clearly -defined, funded, respected and supported career path in all academic -institutions that want to embrace open science as a way of doing research. -My opinion is: academia needs to extend the definition of academic products to -including not only publications but other output that is equally valuable and -even more critically important such as software. RSE roles should be associated -with clear academic career paths. - -Why? Because software is DRIVING open science. Without open source code, you -can't make a workflow truly open and reproducible. This is why peer review is so -important to pyOpenSci. We want to support maintainers in both developing and -maintaining the critical software that is driving science. We also want them -to get credit for their work which is why we partner with the Journal of -Open Source software. - -
- - AN image that shows a house with a chimney blowing smoke out that says scientific discovery. Below the house is open source as the
-    foundation for open science. At the top of the slide is says open source == foundation - -
To be truly open, code that drives scientific inquiry needs to have an open license. Thus open science depends upon open source software. As such it makes sense that academia value open source software in the same way they value publications that are derived from using such software. -
-
- -> The RSE position should be a clearly -> defined, funded, respected and supported career path in all academic institutions -> that want to embrace open science as a way of doing research. - -Ok i'll casually step off my soap box now... back to regular scheduled -programming we go... - -## pyOpenSci as the Chicago RSE meeting - -I lead a pyOpenSci Birds of a Feather (BoF) session at the Chicago RSE meeting. -BoF sessions are informal community gatherings around a specific topic. BoF's provide a chance for the community to engage with each other, ask questions, provide input and even get involved in an effort. - -I spent most of my time in this 1.5 hour session talking about pyOpenSci and the work we are doing related to: - -1. [peer review of scientific Python software](https://www.pyopensci.org/about-peer-review/index.html) -2. the community-driven [Python packaging resources](https://www.pyopensci.org/python-package-guide/) that pyOpenSci is developing -3. [Community peer review partnerships that we are developing](https://www.pyopensci.org/partners.html) and -4. the training and mentorship program that we are building both support scientists and diversify who is participating in open source - -We had a lively discussion around packaging - more on that below. - -## pyOpenSci BoF by the numbers - -I used Mentimeter to drive an engaging and interactive session. You can check out the slides below: - -
- -Mentimeter allowed me to capture audience feedback in several forms both verbal and via phones and computer through mentimeter. - -I'll try to summarize if all for you here. - -- We had approximately 50 people attend our 1.5 hour BoF -- I spent ~1 hour after the BoF answering questions and talking to folks about all things Python open source - with a strong emphasis on packaging challenges. -- Some of that discussion bled into dinner after where I spoke with one of our community partners, Nabil from [sunpy](https://www.sunpy.org) - -In our BoF, I introduced the three core programs that pyOpenSci currently runs which are: - -1. Open peer review of scientific Python software -2. Community-driven packaging resources -3. Mentorship and training to diversify our ecosystem - -
- - A flower image with three petals - software peer review, community partnerships and packaging resources. at the center of the flower it says diverse, inclusive community - -
pyOpenSci has three core programs related to Python scientific software. We spent a lot of time talking about software peer review and - also packaging challenges in our BoF session. -
-
- -### Days since your last Python environment broke - -One of our community members, Isabel, suggested a great icebreaker question : how long has it been since you had a broken Python environment. - -It is no surprise that most pythonistas regularly deal with -environment challenges. - -So if you've been in this boat too, you are are not alone! - -
- - A mentimeter slide. at the top it says days since your last failed python environment. To the right is a picture of people from the Office - a tv show. They are holding a chalk board that says 0 days since last ruined Python environment. On the left is 6 sections with purple circles representing peoples votes for 0 days, 1-7 days, 8-14 days, 15-30 days and > 30 days. No one voted for never. But 12 voted for 1-7 days, 9 for 8-14, 8 for 15-30 and 9 for > 30 - -
Above each of the purple dots represent a vote for a specific number of days. For 12 people, it had only been 1-7 days since their environment had broken. For 9 other lucky people, it had been more than 30 days. A great followup question regarding this might be whether they used containerized environments, or pinned environments that didn't need modifications. -
-
- -Full disclosure the one person who voted for 0 days, admitted to the fact that they hadn't used Python in the past month. :) - -## RSE's experience with Python packaging - -In the BoF i asked some questions related to Python packaging so we could have a -discussion around some of the challenges. This is useful to pyOpenSci as we -developing our packaging guide and associated resources to guide the scientific -community through the process of creating a package. - -Below you can see a word cloud generated from the question - -"What Python packaging tools have you used?" - -
- - This is a word cloud with many different words representing tools that scientists use to create packages. Amongst the words the biggest - which represent tools that multiple people selected - were pip, conda, pytest, mamba, pypi, github actions, setuptools and poetry. - -
From this word cloud you can begin to see that there are many different tools in the python ecosystem that scientists use to create packages. The sheer volume of options can be great for some who know a lot about the ecosystem. However, for many others it can be overwhelming to have to chose between tools and approaches. This is a pain point that pyOpenSci is addressing. -
-
- -A few things that popped out to me included: - -- unsurprisingly **setuptools** is still heavily used in our ecosystem even with the wealth of other build tools available -- **mamba** is definitely growing traction in this space. we love mamba given how fast it is in resolving scientific environments. -- people know about **grayskull**! If you don't know about it, you should! it's a great way to create the yml file that you need to submit your package to the conda-forge channel of conda. At the Scipy 2023 packaging BoF most in the room seemed unaware of this tool. -- **mkdocs** seems to be giving sphinx a run for it's money! it sure does create beautiful docs. And quarto is also gaining traction. However with that said Sphinx is still the most popular packaging tool (at least in that room at that moment). - -### How many is too many Python packaging tools? - -The broad take away from this graphic is that there are a LOT of tools -available for scientists to use. And becoming familiar with all of these is a big ask for a scientist who just wanted their code to be reusable by others. - -We have some work to do at pyOpenSci to [demystify this ecosystem](/blog/demystifying-python-packaging.html)! - -## Challenges in the ecosystem - -The other telling question was "what are your biggest challenges in the Python packaging ecosystem? - -The responses were varied but can be grouped into several - -### Too many packaging options and no one clear best practice - -It's no surprise that there were a handful of responses related to the volume of packaging options which make it hard to figure out which -path to take when creating a package. Not only that but the options have changed over time as standards have evolved in the ecosystem - -I think the comment below summarizes this well: - -> too many options, and tutorials feel like consensus documents rather than making strong recommendations for One Best Way - -The good news is that this is exactly the pain point that pyOpenSci is working on. You can check out our [community-driven packaging guide](https://www.pyopensci.org/python-package-guide/) which presents an overview -of the ecosystem with recommendations for best practices. This guide has been reviewed by dozens of Pythonistas in our ecosystem including those who built and maintain core packaging tools. - -Currently, we are developing packaging tutorials that answer -the most fundamental question: - -> How do I create a (pure) python package? - -All of our packaging content is community driven, created using a robust -community review process for packaging experts across our ecosystem. We -feel confident that we will be able to shed some light on this complex -and evolving ecosystem. - -## Other questions about pyOpenSci - -Admittedly many of the questions that I received in this session were about packaging. The community generally frustrated sometimes turning to tools -such as ChatGPT to ask questions about packaging. - -As someone who has spent a large amount of time testing ChatGPT with packaging questions, I can tell you with certainty that it will lead you in a confusing direction as it doesn't have it's packaging facts straight. - -Proceed with caution!! - -We also got some other questions about our peer review process, how we -interact with JOSS (Journal of Open Source Software) and how our [partnership programs](/partners.html) work. - -I'll save those questions for a followup post on pyOpenSci programs. - -## pyOpenSci has work to do! - -My key takeaways from the US-RSE meeting is: - -1. It's great that support is forming for this academic critical role that is required if we truly want to embrace open science. If you want to learn more visit about the US RSE effort, visit the [US-RSE website](https://us-rse.org/) website. -2. The US RSE community is facing the same Python packaging challenges that we've seen across other communities. pyOpenSci's is committed to continuing it's work in the peer review and packaging space. Key an eye out for our tutorials in the upcoming months and check out our packaging guide in the meantime for an overview of the ecosystem. -3. If you are interested in getting involved with pyOpenSci there are a few different ways: - -- you can [review a pull request](https://github.com/pyOpenSci/python-package-guide/pulls) on our packaging guide. Or open and issue if you have a problem with the guide. -- You can [sign up to be a reviewer for pyOpenSci here](https://docs.google.com/forms/d/e/1FAIpQLSeVf-L_1-jYeO84OvEE8UemEoCmIiD5ddP_aO8S90vb7srADQ/viewform) - -As for me, I hope to attend the 2024 RSE meeting and to continue this important -conversation around Python packaging and peer review of software. diff --git a/_posts/2023-11-29-welcome-jesse-pyos-community-manager.md b/_posts/2023-11-29-welcome-jesse-pyos-community-manager.md deleted file mode 100644 index 2496a0f7..00000000 --- a/_posts/2023-11-29-welcome-jesse-pyos-community-manager.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -layout: single -title: "👋 Hello, world! Greetings from the new pyOS Community Manager" -blog_topic: community -excerpt: "Learn more about our new pyOpenSci Community Manager, Jesse Mostipak, in her first pyOpenSci blog post!" -author: "Jesse Mostipak" -permalink: /blog/pyOpenSci-new-community-manager.html -header: - overlay_color: "#33205c" -categories: - - blog-post - - community -toc: true -comments: true -last_modified: 2024-08-29 ---- - -## Chicago, cats, and community - -I’m thrilled to be joining pyOpenSci (pyOS) as the Community Manager, and bringing my experiences as a researcher, educator, and developer advocate to support the creation and maintenance of free and open Python tools for processing scientific data. I’ve spent most of my adult life building online communities, from video games to programming to data science, and it’s exciting to see so many familiar faces in my first weeks at pyOS. - -One of the things I’m most excited to bring to pyOS is the creation of engaging, approachable, and accessible technical content around some of the thornier Python issues, like [packaging](https://www.pyopensci.org/python-packages.html)! Whether it’s blog posts, social media content, or videos, I want to ensure that our educational materials are available to everyone, regardless of geography. - -Another area that I’ll be particularly focused on is the [pyOS Peer Review program](https://www.pyopensci.org/about-peer-review/index.html). The Peer Review program with pyOS supports scientists in getting credit for the efforts they’ve invested in open source Python tools, while also supporting the standardization of packaging and improved package visibility. And as the pyOS Community Manager, I want to help celebrate all of the hard work that authors, maintainers, and editors put into package development by sharing behind-the-scenes stories, package announcements, and the contributions of every individual with the broader community. - -When I’m not helping build equitable, diverse, and accessible technical communities, I’m chilling with my two cats, Jinx and Luna, or riding my bike on the incredible gravel trails in the Chicago area. - -
- - A close-up photo of two cats sitting side-by-side on a blanket. Jinx, an orange and white tabby with a medium-length coat sits on the left, gazing directly into the camera. Luna, a short-haired black cat, sits on the right, looking at something past the camera - -
World's greatest co-workers Jinx (left) and Luna (right). -
-
- -## Why pyOpenSci - -I first got started in scientific programming during my time as a graduate student in Immunology and Infectious Diseases. Learning R opened up a world of possibility for me, and I eventually used that early experience to build a career in data science. And out of all the learning materials available, I found the community to be the most critical in my success. While I’m no longer spending my days coding, I wanted to continue working with supportive, welcoming, and educational online communities that were focused on helping its members solve technical challenges. - -pyOpenSci meets all of those criteria, and the more I learned about its philosophy around community building, as well as all of the ways for people of all levels of technical knowledge to get involved, the more I knew I wanted to be a part of things. I’m still within my first month at the organization, but I can already tell that it’s a special place to be. Keep an eye out, because I’ve already started planning content and activities to help grow our community! diff --git a/_posts/2024-01-03-czi-funds-pyopensci-2024.md b/_posts/2024-01-03-czi-funds-pyopensci-2024.md deleted file mode 100644 index 39496d2f..00000000 --- a/_posts/2024-01-03-czi-funds-pyopensci-2024.md +++ /dev/null @@ -1,77 +0,0 @@ ---- -layout: single -title: "CZI Funds pyOpenSci" -blog_topic: community -excerpt: "CZI has received funding from the Chan Zuckerberg Foundation. Learn more about how this funding will support pyOpenSci over the next 2 years." -author: "Leah Wasser" -permalink: /blog/czi-funds-pyOpenSci-2024.html -header: - overlay_color: "#33205c" -categories: - - blog-post - - community -toc: true -comments: true -last_modified: 2024-08-29 ---- - -## A bright 2024 is ahead for pyOpenSci - -We are thrilled to announce that pyOpenSci has received 2 years of funding to cover core operations from the [Chan Zuckerberg Initiative (CZI)](https://chanzuckerberg.com/). These CZI funds will be used to continue critical pyOpenSci work that: - -* supports Python open source maintainers in developing the usable and maintainable scientific software the enables open science; -* helps scientists navigate a complex Python packaging ecosystem; -* diversifies the scientific Python open source community. - - -## Open science builds trust and enables more people to participate - -Open and reproducible science builds trust and accelerates research and -discovery. Open science supports scientific research that is both transparent and -reusable. Free and open source software is critical to open science as it ensures that -the analyses of research data are broadly accessible. To build truly open research workflows, scientists need to use free and open source software (FOSS). FOSS removes the barriers that licenses and other fees may create making diverse participation more accessible. - -### Broad inclusion is critical for both open source and open science - -Broad inclusion of underrepresented and historically excluded individuals is critical to pyOpenSci's mission. The open source ecosystem has been found to be [even less diverse then the broader tech community](https://www.wired.com/2017/06/diversity-open-source-even-worse-tech-overall/). To help remedy this, pyOpenSci empowers everyone to participate in both the development and use of open source software. This -empowerment enables open science to reach its full potential. - -### Open source maintainers need support -Despite the importance of open source software to fundamental -open science principles, open source maintainers do not get the support they -need. Maintainers need both institutional and community support in learning -and engaging in open source software development, maintaining tools and engaging -with the broad user base that may begin to use their tools in support of open science. - -## pyOpenSci is pushing for change - -pyOpenSci is pushing for change. We envision a world where: - -* The scientific Python open source ecosystem is diverse and inclusive; -* Individuals developing open software are supported; -* Scientists have the skills needed to both contribute to open source and also develop collaborative open science workflows; these skills have significant overlap. - -## pyOpenSci builds a supportive and inclusive world through three programs - -
- - A flower petal image with 3 flower petals and a flower center. In each petal there is text. The first says software peer review, the second community partnerships and the third packaging resources and recommendations. The center circle of the flower says diverse inclusive community. - -
pyOpenSci supports scientists developing open source software through three programs: 1) peer review of scientific software, 2) community partnerships and 3) packaging resources and recommendations. These three programs are supported by a diverse and inclusive community that cares about the open source software that is needed for open science. -
-
- -The three programs pyOpenSci runs are: - -* [Peer review of scientific Python software](https://www.pyopensci.org/about-peer-review/index.html) - The pyOpenSci software peer review process helps scientists find the vetted and trusted tools they need to build reproducible open science workflows. We also empower our community with critical open science skills that support contributing to open source software. - -* [Community partnerships with domain-specific scientific Python communities](https://www.pyopensci.org/partners.html). Domain-specific communities partner with PyOpenSci to leverage pyOpenSci's peer review process as a way to track vetted, high quality tools. Communities also support the development of Python packaging packaging guidelines in an effort to streamline the development of packaging recommendations across the scientific Python ecosystem. - -* Training and resources to help scientists develop and maintain high-quality, accessible, open source software. Our community developed the [Python packaging guide](https://www.pyopensci.org/python-package-guide/), which provides resources and tutorials that help scientists navigate a complex Python packaging ecosystem. The Python packaging guide also makes recommendations for community accepted best practices. - -## Looking forward to 2024 - -pyOpenSci is excited to grow a more inclusive and supportive scientific Python community in 2024! - -We are always looking for volunteers to support our community programs. -If you are interested in getting involved with pyOpenSci, [learn more here](/get-involved-contact.html). diff --git a/_posts/2024-03-01-pyos-newsletter-march-2024.md b/_posts/2024-03-01-pyos-newsletter-march-2024.md deleted file mode 100644 index 539f473b..00000000 --- a/_posts/2024-03-01-pyos-newsletter-march-2024.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -layout: single -title: "pyOpenSci Newsletter: March 2024" -blog_topic: updates -excerpt: "New partnerships, packages, and conference announcements!" -author: "Jesse Mostipak" -permalink: /blog/pyos-newsletter-march-2024.html -header: - overlay_color: "#33205c" -categories: - - blog-post - - community -classes: wide -toc: true -comments: true -last_modified: 2024-08-29 ---- - -## Welcome, AstroPy -pyOpenSci has officially partnered with the [AstroPy Project](https://www.astropy.org/) through our [Community Partnership program](https://www.pyopensci.org/partners.html). AstroPy is a Community Python Library for Astronomy, and the AstroPy Project is a community effort to develop a single core package for Astronomy in Python, while also fostering interoperability between Python astronomy packages. We're thrilled to be partnering with the AstroPy project, and can't wait to share more! -
- - Three sequential arrows, with the first reading 'submit your package', the second reading 'Review, pyOpenSci standards', and the third reading 'Accepted, pyOpenSci + Community Affiliated'. Below the arrows is a box beneath 'Review' that says 'Your Community Standards', with an input arrow reading 'Customized review'. - -
pyOpenSci adds an extra layer of community-specific review to our established open peer review process. This allows domain-specific scientific Python communities to vet affiliated tools through our robust peer review process. Communities then don’t have to develop and maintain their own review processes and software guidelines. -
-
- -## Hello, sunPy - -[sunPy](https://sunpy.org/), a Python package that provides fundamental tools for accessing, loading, and interacting with solar physics data in Python, has successfully gone through the pyOpenSci open peer review process, which is the first step in our budding partnership with the heliophysics community! - -You can read more about sunPy and pyOpenSci in [this lovely blog post](https://sunpy.org/posts/2024/2024-01-24-pyopensci.html) from Nabil Friej. - -## Say hi to sciform - -[Justin Gerber](https://github.com/jagerber48) has developed the [sciform package](https://sciform.readthedocs.io/en/stable/), which is used to convert Python numbers into strings according to a variety of user-selected scientific formatting options, was accepted into the pyOpenSci ecosystem! sciform, like all of the pyOpenSci packages, completed the [pyOpenSci open peer review process](https://www.pyopensci.org/about-peer-review/index.html). And the community support around getting the sciform package review-ready was amazing to see! - -Have a package you’ve developed, but aren’t sure it’s in scope for pyOpenSci? Complete a [pre-submission inquiry](https://github.com/pyOpenSci/software-submission/issues/new?assignees=&labels=presubmission&projects=&template=presubmission-inquiry.md&title=) and one of our editors will be in touch! - -## We're going to [PyCon US](https://us.pycon.org/2024/) -Will we see you there? Our Executive Director and Founder, [Leah Wasser](https://github.com/lwasser), had her talk. [Friends don't let friends package alone](https://us.pycon.org/2024/schedule/presentation/34/), all about Python packaging for scientists, accepted for this May's conference. We've seen a sneak peek of her talk, and guarantee that you won't want to miss it! Be sure to [register today](https://us.pycon.org/2024/)! - -## Congratulations, Leah! - -Leah Wasser also received a [Better Scientific Software (BSSw) fellowship](https://bssw.io/pages/bssw-fellowship-program) to create lessons around collaborative open science using GitHub. We plan to turn the lessons into online workshops. We'll be sure to let you know once they're ready for registration! - -[BSSw](https://bssw.io/) provides a central hub for the community to address pressing challenges in software productivity, quality, and sustainability. - -## New lessons are LIVE! -pyOpenSci released new and updated lessons in our [Python Packaging Guide](https://www.pyopensci.org/python-package-guide/tutorials/intro.html), designed to help scientists both make their Python code more shareable, as well as how to create a Python package. Be sure to check them out and let us know what you think! - -## Upcoming Python Events for Scientists -### PyCon US 2024 -The [PyCon US 2024 Maintainers Summit Call for Proposals](https://us.pycon.org/2024/events/maintainers-summit/) is LIVE until March 25th! Share your insights and experiences on best practices, developing sustainable projects, and nurturing thriving communities in a 10--15 minute talk. - -### PyConDE & PyData Berlin -Registration is still live for [PyConDE & PyData Berlin](https://2024.pycon.de/), so be sure to grab your tickets while they still last! - -The event is scheduled for April 22nd--24th, _**with Inessa Pawson delivering the keynote address,**_ titled, "The Art and Science of Tending Open Source Orchards". - -## Get the pyOpenSci monthly newsletter delivered directly to your inbox! -In addition to catching our newsletter on Fosstodon, LinkedIn, and the pyOpenSci website, you can also [sign up to have the newsletter delivered to your inbox every month](https://eepurl.com/iM7SOM)! diff --git a/_posts/2024-04-01-pyos-newsletter-april-2024.md b/_posts/2024-04-01-pyos-newsletter-april-2024.md deleted file mode 100644 index 207b31d1..00000000 --- a/_posts/2024-04-01-pyos-newsletter-april-2024.md +++ /dev/null @@ -1,71 +0,0 @@ ---- -layout: single -title: "pyOpenSci Newsletter: April 2024" -blog_topic: updates -excerpt: "New contributors, a new package, and a few new social campaigns, all to keep you connected with the pyOpenSci community!" -author: "Jesse Mostipak" -permalink: /blog/pyos-newsletter-april-2024.html -header: - overlay_color: "#33205c" -categories: - - blog-post - - community -classes: wide -toc: true -comments: true -last_modified: 2024-08-29 ---- - -## To 200 and beyond! - -This past month, pyOpenSci reached a major milestone: [200 contributors](https://www.pyopensci.org/our-community/index.html)! We have a diverse and vibrant community of Pythonistas, and are thrilled that 200 (and counting) wonderful people have contributed to pyOpenSci. Will you be next? [Learn more about getting involved](https://www.pyopensci.org/volunteer.html). - - -## Launched: editor spotlight - -pyOpenSci editors are volunteers who fulfill a critical role within pyOpenSci. Editors lead the open peer review process for three to four Python packages a year, and also weigh in on group editorial decisions. With everything they do for pyOpenSci, we wanted to celebrate them! In March we kicked off our pyOpenSci editor spotlight, where every Wednesday we share a short interview with one of our editors. You can catch up with previous spotlights, and stay tuned for more, on both our [Fosstodon](https://fosstodon.org/@pyOpenSci) and [LinkedIn](https://www.linkedin.com/company/pyopensci) pages. - -## PyCon US 2024 Maintainers Summit - -We’ve been talking (a lot) about the [PyCon Maintainers Summit](https://us.pycon.org/2024/events/maintainers-summit/) (which is now full, but [you can sign up for the waitlist](https://us.pycon.org/2024/events/maintainers-summit/)), happening this May in Pittsburgh. The Maintainers Summit, included in your [PyCon registration](https://us.pycon.org/2024/), is where we come together to discuss and foster best practices on how to develop sustainable projects and nurture thriving communities. And thanks to [Mariatta Wijaya](https://mariatta.ca/), we’ve got an inside look into everything you can expect in [this incredible video](https://www.youtube.com/watch?v=L-Ok_89QJOM) featuring Kara Sowles, Inessa Pawson, and our Executive Director, Leah Wasser! - - - Watch the video - - -## Welcome, EOmaps! -
- - The EOmaps logo, which spells out EOmaps in white letters on a black background. In place of the letter 'O' is a grey globe with white meridian lines, and a red arrow pointing to the globe. - -
- Welcome to pyOpenSci, EOmaps! -
-
-[EOmaps](https://eomaps.readthedocs.io/en/latest/) has joined the pyOpenSci ecosystem! Developed by [Raphael Quast](https://github.com/raphaelquast), EOmaps is built on top of matplotlib and cartopy and integrates well with the scientific python infrastructure (e.g., numpy, pandas, geopandas, xarray etc.), allowing you to visualize point-, raster- or vector-datasets provided in almost any format you can imagine, no matter if you're dealing with just a few unsorted data points or multi-dimensional stacks of global high-resolution datasets. - -Learn more about pyOpenSci’s open peer review process by reading through the [EOmaps submission](https://github.com/pyOpenSci/software-submission/issues/138) or checking out our [peer review page](https://www.pyopensci.org/about-peer-review/). - -## Launched: pyOpenSci package showcase - -pyOpenSci currently has [28 packages](https://www.pyopensci.org/python-packages.html) that have been accepted through our open peer review process, and we want you to get to know them better! This is why we’ve launched our pyOpenSci package showcase, which highlights an accepted package and its author(s) every Tuesday and Thursday on our [Fosstodon](https://fosstodon.org/@pyOpenSci) and [LinkedIn](https://www.linkedin.com/company/pyopensci) pages. - -Have a package you’ve developed, but aren’t sure it’s in scope for pyOpenSci? Complete a [pre-submission inquiry](https://github.com/pyOpenSci/software-submission/issues/new?assignees=&labels=presubmission&projects=&template=presubmission-inquiry.md&title=) and one of our editors will be in touch! - -## Upcoming Python Events for Scientists -### PyConDE & PyData Berlin -Online tickets are still available for [PyConDE & PyData Berlin](https://2024.pycon.de/), so be sure to grab them while they still last! The event is scheduled for April 22nd-24th, **with [Inessa Pawson](https://github.com/InessaPawson) delivering the keynote, titled “The Art and Science of Tending Open Source Orchards”**. You definitely don’t want to miss out! - -### PyCon US 2024 -[Registration is still open](https://us.pycon.org/2024/) for [PyCon US 2024](https://us.pycon.org/2024/), happening May 15th–23rd in Pittsburgh, PA. There’s also an online component for folks who can’t be there in person. It’s shaping up to be a stacked event, and you can view the schedule [here](https://us.pycon.org/2024/schedule/). - -### GeoPython -Registration is live for [GeoPython](https://geo-python-site.readthedocs.io/en/2024.0/), THE event for Python- and Geo-Enthusiasts. There are online and in-person options, and the event is scheduled for May 27th–29th in Basel, Switzerland. - -### SciPy–early bird ends soon! -Happening July 8th–14th in Tacoma, WA, [SciPy registration is live](https://www.scipy2024.scipy.org/), and you can grab your early bird tickets until April 30th. - -Have an upcoming scientific Python event that’s open to the public? Email us at [media@pyopensci.org](mailto:media@pyopensci.org) with the details to have your event listed. - -## Get the pyOpenSci monthly newsletter delivered directly to your inbox! -In addition to catching our newsletter on Fosstodon, LinkedIn, and the pyOpenSci website, you can also [sign up to have the newsletter delivered to your inbox every month](https://eepurl.com/iM7SOM)! diff --git a/_posts/2024-04-16-pyos-open-education-resources-announcement.md b/_posts/2024-04-16-pyos-open-education-resources-announcement.md deleted file mode 100644 index b25852ec..00000000 --- a/_posts/2024-04-16-pyos-open-education-resources-announcement.md +++ /dev/null @@ -1,105 +0,0 @@ ---- -layout: single -title: "pyOpenSci launches open, accessible, online educational trainings for scientists" -blog_topic: education -excerpt: "We're excited to share information about the pyOpenSci approach to education, open education resources, and announce an upcoming workshop focused on Python packaging" -author: "Jesse Mostipak" -permalink: /blog/pyos-education-announcement.html -header: - overlay_color: "#33205c" -categories: - - blog-post - - community - - highlight -classes: wide -toc: true -comments: true -last_modified: 2024-08-29 ---- - -## The three petals of pyOpenSci -
- - The three petals of pyOpenSci, a purple flower with a center and three petals. The center reads 'Diverse, Inclusive Community' while the petals, from left to right, read 'Software Peer Review', 'Community Partnerships', 'Community Driven Training & Open Education'. - -
- The three petals of pyOpenSci -
-
-pyOpenSci was founded with the mission to build diverse community that supports free and open Python tools for processing scientific data. We also build the technical skills needed to contribute to open source and that support open science. While a diverse, inclusive community is at our core, radiating out from it are the three petals–how we accomplish our community goals–of pyOpenSci. Those are: open peer review, community partnerships, and training and open educational resources. - -### Peer review -The pyOpenSci open peer review process facilitates scientists getting credit and recognition for the work they’ve invested in developing scientific Python tools. The peer review process also supports scientists in finding vetted and maintained software, which drives their open science workflows. - -Software peer review, similar to the review of scientific papers, is a process where scientists vet software code, documentation and infrastructure. pyOpenSci leads an [open peer review process](https://www.pyopensci.org/software-peer-review/our-process/how-review-works.html) run by a community of dedicated volunteers. Reviews are supportive and fully transparent with the shared goal of improving the quality, usability and maintainability of the software that is driving open science. - -[Learn more about the peer review timeline and roles.](https://www.pyopensci.org/software-peer-review/our-process/review-timeline.html) - -### Community partnerships -pyOpenSci adds an [extra layer of community-specific review](https://www.pyopensci.org/software-peer-review/partners/scientific-communities.html) to our established open peer review process. This allows domain-specific scientific Python communities to vet affiliated tools through our robust peer review process. Communities then don’t have to develop and maintain their own review processes and software guidelines. - -Our catalog of vetted open source tools makes it easier for scientists to find the trusted tools that they need to develop their open science workflows. - -[View our growing list of accepted scientific Python packages.](https://www.pyopensci.org/python-packages.html) - -### Education -pyOpenSci creates resources to help you navigate the Python packaging ecosystem with ease. Our materials are community-developed and go through extensive technical and pedagogical review. Keep reading to learn more about our approach to education! - -## pyOpenSci believes in open education resources (OER) -
-
- - - Multiple hands of various skin tones reaching up to a speech bubble that reads 'Community Developed'. - -
- pyOpenSci's open education resources are community-developed -
-
-When pyOpenSci publishes community-developed Python tutorials for scientists, it goes through a rigorous process of review and evaluation. ​​All of our tutorials are created through a multi-stage community review process–where tutorials are first developed by the pyOpenSci team or community members–before being reviewed by tool maintainers to ensure ideas and concepts are accurate. Tutorials then go through several rounds of community review for accuracy, usability and accessibility before being published as part of pyOpenSci’s open education resources.. - -All of our written content is available on the [Learn section of our website](https://www.pyopensci.org/learn.html), where you’ll find resources such as our [Python Packaging Tutorial](https://www.pyopensci.org/python-package-guide/tutorials/intro.html#), as well as in-depth guides on [Python packaging](https://www.pyopensci.org/python-package-guide/package-structure-code/intro.html), [documentation](https://www.pyopensci.org/python-package-guide/documentation/index.html), and [testing](https://www.pyopensci.org/python-package-guide/tests/index.html). - -### Free, online, asynchronous training - -
- - - A woman and a man sitting across from each other at a high table. They are smiling and working on their laptops. The text reads 'Essential Collaboration Skills for Scientists'. - -
- Essential Collaboration Skills for Scientists is just one of the many free, online trainings we're excited to develop and deliver! -
-
- -Our vision for our free, online trainings is for the curriculum to be added to our library of open education resources, making them free, accessible, and published for anyone to use. And we intend to experiment with delivering this curriculum in an asynchronous, cohort-based manner that allows learners from all over the world to come together for a period of time to work through the curriculum together. If you were part of one of the original cohorts of the R for Data Science Online Learning Community created by our Community Manager, [Jesse Mostipak](https://github.com/kierisi), then you know that these asynchronous trainings will be thoughtfully crafted, a fantastic learning experience, and a ton of fun! - -pyOpenSci is the recipient of the [Better Software for Science Fellowship](https://bssw.io/fellows/leah-wasser), which will help fund the creation of these open educational resources. - -These asynchronous trainings will be free, online, and center around community building and collaborative learning with larger cohort sizes. Part of the intention of these trainings is to foster a vibrant learning community where participants are empowered to learn from one another. - -### Paid, online, real-time training -pyOpenSci is also developing paid trainings that will be offered online to a smaller cohort of learners, using a customized curriculum. While these will be online, we guarantee they won’t be your typical Zoom workshop! For us, it’s all about conversation, collaborative learning, and engagement. These instructor-led workshops will have a high instructor to student ratio to ensure that all participants receive personalized feedback and guidance on the workshop material. Although these will be paid trainings, we hope to offer scholarships where feasible. - -## Enroll in pyOpenSci’s upcoming workshop: From Python Code to Module -
- - A line art robot standing in a field of purple flowers. The text reads 'From Python Code to Module, a live, online workshop with pyOpenSci, Thursday, April 25th 2024'. - -
- Tickets for our "From Python Code to Module" workshop are selling fast - get yours today! -
-
-If you’re interested in participating in our first paid, online, real-time training, sign up for our upcoming workshop: [“From Python Code to Module”](https://www.eventbrite.com/e/879586546037?aff=oddtdtcreator). This three hour course is intended for individuals who have experience writing Python code and Python functions, and will be taught by pyOpenSci’s Executive Director and founder, [Leah Wasser](https://github.com/lwasser). Leah has over 20 years of experience teaching data-intensive science with an emphasis helping scientists work through the pain points of working with different types of data, and puts an incredible amount of care and attention into ensuring each learner is successful in their educational goals. This is definitely a workshop you don’t want to miss! - -In this workshop, Leah will cover: -- How to identify and explain the use of the basic components of a Python package: (a specific directory structure, an __init__.py file, a pyroject.toml file and some code). -- Creating a basic python package that allows you to install your code into a local Python environment. -- Installing your package in editable mode into a Python environment. - -She will also briefly discuss how LLM’s can be used to support tasks such as documenting and formatting your code to improve usability and maintainability. While also considering the ethical and logistical challenges, pitfalls and concerns associated with using AI-based tools in software development. - -The course will take place on Thursday, April 25th, from 10AM–1PM Mountain time, and use the Spatial Chat platform. Tickets are $10, and can be purchased on [the workshop’s Eventbrite page](https://www.eventbrite.com/e/from-python-code-to-module-tickets-879586546037?aff=oddtdtcreator). The class size is capped at 35, and tickets are selling fast–we hope to see you there! - -## Connect with us! -You can stay up-to-date with all things pyOpenSci by following us on [LinkedIn](https://www.linkedin.com/company/pyopensci) and [Fosstodon](https://fosstodon.org/@pyOpenSci). diff --git a/_posts/2024-04-18-rdata-read-r-datasets.md b/_posts/2024-04-18-rdata-read-r-datasets.md deleted file mode 100644 index 846d2128..00000000 --- a/_posts/2024-04-18-rdata-read-r-datasets.md +++ /dev/null @@ -1,131 +0,0 @@ ---- -layout: single -title: "rdata: Read R datasets from Python" -blog_topic: software -excerpt: "rdata is a library for reading R datasets and converting them to Python objects that was recently accepted into the pyOpenSci ecosystem. Learn more about rdata." -author: "Carlos Ramos Carreño" -permalink: /blog/read-r-datasets-from-python.html -header: - overlay_image: images/blog/headers/pandas.png - caption: "Photo credit: [**Ann Batdorf, Smithsonian's National Zoo**](https://www.flickr.com/photos/nationalzoo/5371290900/in/photostream/)" -categories: - - blog-post - - R - - data-processing - - datasets - - highlight -comments: true -last_modified: 2024-08-29 ---- - -In the last years Python has solidified itself as the main language for data science and machine learning. -However, the R programming language is also a widely used language in statistics, offering thousands of specialized software packages in this field. -As it is common in the R community, many of these packages include accompanying data, often stored as R objects in the .rds and .rda formats. - -This variety of datasets is normally inaccessible directly from Python programs. -Instead, programmers typically have to open the data in R and manually convert it to another format, such as CSV, which can be understood by Python libraries. -This process requires an R interpreter, it is not fast, nor easy, and may even be impossible to do without information loss. -In particular, for nested structures and complex or custom R types there is no common file exchange format for Python and R that can be used to this purpose. - -The Python package [rdata](https://rdata.readthedocs.io) allows Python programmers to read R datasets in the .rds and .rda formats directly, facilitating the sharing of examples and results between the two languages. -It is a small, pure Python solution that does not require an R interpreter to work, nor external libraries in other languages. -This makes it suitable to be used in most contexts where Python can be found, including the web using Pyodide. -The package rdata has been accepted by pyOpenSci as part of its ecosystem in March 2024. - -# A library for reading R datasets - -To illustrate rdata capabilities, lets take the role of a Python developer implementing new statistical techniques. -In order to compare her results with prior approaches, she needs to use the same data as them. -Unfortunately for her, the people who implemented the original techniques do not have the data in a format directly readable by Python, as they were all statisticians using the R programming language. -They are eager to share their data with her, in the form of a .rda file called `dataset.rda`. - -A cursory inspection of the file in R shows the following: - -![Data: frame (10 obs. of 3 variables), Values: (freq (1200), model ("NovTech200"))]({{ site.url }}/images/rdata/dataset.png "The contents of `dataset.rda`") - -The dataset contains 3 variables, of which just one is a dataframe. -Moreover the dataframe contains a column with instances of a custom R class, and a boolean column with some NA values. -Thus, converting this data to a format such as CSV to read it in Python would be a difficult endeavor. -Luckily, this Python programmer has heard of the rdata Python package, which could be useful to solve this task. - -Our programmer quickly installs rdata with pip or conda, and executes the following command in the Python interpreter: - -```python -dataset = rdata.read_rda("dataset.rda") -``` - -This loads the file into a Python dictionary that she can now explore. -She now starts by checking the "model" and "freq" variables: - -```python ->>> dataset["model"] -array(['NovTech200'], dtype='>> dataset["freq"] -array([1200.]) -``` - -The original R vectors have been now converted to equivalent NumPy objects with their corresponding dtype. -Then, she checks the default conversion of the dataframe itself: -```python ->>> dataset["frame"] - level subject control -1 8 namespace(name=array(['Louis'], dtype=' -3 6 namespace(name=array(['Miriam'], dtype=' -10 5 namespace(name=array(['Mark'], dtype=' -``` - -## Support for custom classes - -The dataframe has been converted by default to a Pandas dataframe, and the NA have been preserved. -However, there is a problem with the subject column: as it contains instances of a custom R class, the default conversion did not know to which class it should be mapped in Python, returning a `SimpleNamespace` instead. - -Our programmer has developed a Python class, `Person`, which she plans to use in her code. -Instead of modifying the dataframe to manually convert the instances, she uses the following code to load the data applying the desired transformations: - -```python -def person_constructor(obj, attrs): - return Person(name=str(obj.name[0]), age=int(obj.age[0])) - -constructor_dict = { - **rdata.conversion.DEFAULT_CLASS_MAP, - "Person": person_constructor, -} - -dataset = rdata.read_rda( - "dataset.rda", - constructor_dict=constructor_dict, -) -``` - -This code defines a "constructor" of a Python object and assigns it to a dictionary of constructors, using "Person" as the key. -During the R to Python conversion process, whenever an object is found with a class attribute of "Person", the constructor function will be called with the partially converted object and its attributes, and the return value will be used as the converted value of that object. -The mapping `rdata.conversion.DEFAULT_CLASS_MAP` contains a default set of constructors, including the one for converting R dataframes into Pandas dataframes. -Now, showing the dataset again, we can verify that every instance of the original custom class has been replaced with its Python counterpart: - -```python ->>> dataset["frame"] - level subject control -1 8 Person(name='Louis', age=57) False -2 4 Person(name='Martin', age=22) -3 6 Person(name='Miriam', age=41) True -4 3 Person(name='Yoshi', age=29) False -5 9 Person(name='John', age=63) True -6 2 Person(name='Karl', age=34) False -7 7 Person(name='Eusebio', age=77) False -8 10 Person(name='Rosa', age=33) False -9 1 Person(name='Lilly', age=28) -10 5 Person(name='Mark', age=30) -``` - -## What's next? - -rdata is under active development, and new functionalities will be added as users need them. -Contributions are welcome in the [Github repository](https://github.com/vnmabus/rdata)! diff --git a/_posts/2024-04-29-pyos-newsletter-may-2024.md b/_posts/2024-04-29-pyos-newsletter-may-2024.md deleted file mode 100644 index 8d45757c..00000000 --- a/_posts/2024-04-29-pyos-newsletter-may-2024.md +++ /dev/null @@ -1,106 +0,0 @@ ---- -layout: single -title: "pyOpenSci Newsletter: May 2024" -blog_topic: updates -excerpt: "May has brought two new packages, the first (of many!) pyOpenSci workshops, all things pyOpenSci and PyCon, and several incredible speaking opportunities. Read on to learn more!" -author: "Jesse Mostipak" -permalink: /blog/pyos-newsletter-may-2024.html -header: - overlay_color: "#33205c" -categories: - - blog-post - - community -classes: wide -toc: true -comments: true -last_modified: 2024-08-29 ---- - -## Spring is in the Air -For those of us in the Northern Hemisphere, spring seems to have finally sprung. It’s a lovely time of year, filled with growth, opportunity, and new beginnings. And for all of us here in the pyOpenSci it’s brought new packages, new opportunities, and new projects that we can’t wait to share with you! - -## pyOpenSci’s first workshop was a success! -
- - Purple line art of a robot standing in a field of solid purple flowers. The text reads 'Build Your First Python Package! a live, online workshop with pyOpenSci. Thursday, April 25th, 2024. - -
- Thank you to everyone who made our first workshop such a fun experience! -
-
-On Thursday, April 25th, pyOpenSci held its first-ever workshop, titled: [Build Your First Python Package!](https://www.eventbrite.com/e/build-your-first-python-package-tickets-879586546037). With eight countries represented, we had 28 learners create and install their first Python package using [Hatch](https://hatch.pypa.io/latest/), all in the course of three hours. - -This workshop was the first of many online educational events that we plan on hosting, and was a great opportunity for us to put [our educational philosophy](https://www.pyopensci.org/blog/pyos-education-announcement.html) into practice. All of the pyOpenSci educational materials are community-developed and go through extensive technical and pedagogical review. While the workshop will *not* be available as a recording, the written content is available on the [Learn section of our website](https://www.pyopensci.org/learn.html), where you’ll find resources such as our [Python Packaging Tutorial](https://www.pyopensci.org/python-package-guide/tutorials/intro.html#), as well as in-depth guides on [Python packaging](https://www.pyopensci.org/python-package-guide/package-structure-code/intro.html), [documentation](https://www.pyopensci.org/python-package-guide/documentation/index.html), and [testing](https://www.pyopensci.org/python-package-guide/tests/index.html). - -## Hello, rdata! -[rdata](https://rdata.readthedocs.io/en/latest/), a Python package that offers a lightweight way to import R datasets/objects stored in the `.rda` and `.rds` formats into Python, has been accepted into [the pyOpenSci ecosystem](https://www.pyopensci.org/python-packages.html). Created by [Carlos Ramos Carreño](https://github.com/vnmabus), rdata has several key advantages: - -* rdata is a pure Python implementation, with no dependencies on the R language or related libraries. Thus, it can be used anywhere where Python is supported, including the web using [Pyodide](https://pyodide.org/en/stable/). -* rdata attempts to support all R objects that can be meaningfully translated. As opposed to other solutions, you are not limited to import dataframes or data with a particular structure. -* rdata allows users to easily customize the conversion of R classes to Python ones. Does your data use custom R classes? Worry no longer, as it is possible to define custom conversions to the Python classes of your choosing. -* rdata has a permissive license (MIT). As opposed to other packages that depend on R libraries and thus need to adhere to the GPL license, you can use rdata as a dependency on MIT, BSD or even closed source projects. - -If you’d like to learn more about rdata, we recommend [this guest blog post](https://www.pyopensci.org/blog/read-r-datasets-from-python.html) from Carlos! - -### Write a guest blog post for pyOpenSci -Interested in writing a guest blog post for pyOpenSci? Reach out to [media@pyopensci.org](mailto:media@pyopensci.org) to get started! We’re always looking for volunteers to write about all things open source and open science, whether that’s a package you’ve created or something new you’ve learned in Python, or anything in between! - -## Welcome, ZodiPy -
- - Screenshot of the ZodiPy GitHub repository, showing the ZodiPy logo and simulated data output. - -
- Welcome, ZodiPy! We're so happy you're here. -
-
- -[ZodiPy](https://github.com/Cosmoglobe/zodipy) is an open source, [Astropy affiliated](https://www.astropy.org/affiliated/), Python tool for simulating the zodiacal emission that a solar system observer is predicted to see given an interplanetary dust model. ZodiPy attempts to make zodiacal emission simulations more accessible by providing the community with a simple interface to existing models, and is also a new member of the pyOpenSci community! - -## pyOpenSci will be at PyCon 2024! -Not only will be *be* at PyCon US 2024, we've got a lot going on while we're there, and we want you to be a part of it! This year's PyCon US will be held in Pittsburgh, PA, from May 15th - May 23rd. - -### pyOpenSci at the PyCon Maintainers Summit -The [PyCon Maintainers Summit](https://us.pycon.org/2024/events/maintainers-summit/) is where the Python community comes together to discuss and foster best practices on how to develop sustainable projects and nurture thriving communities. Organized by Inessa Pawson (NumPy, Albus Code, OpenTeams), Chris Rose (PyHamcrest, GitHub), Kara Sowles (GitHub), and Leah Wasser (pyOpenSci), the Summit is a must-attend event, where you can hear talks from two pyOpenSci community members: - -* [Carol Willing](https://github.com/willingc) (pyOpenSci Advisory Council) will be giving a talk titled: Team Compass: Setting a course for a dynamic project. -* [Tracy Teal](https://github.com/tracykteal) (pyOpenSci Board Chair) will be giving a talk titled: Documentation: how you can build it so they will come - -### Leah's PyCon Packaging Talk -On Saturday, May 18th, pyOpenSci's Executive Director and Founder, Leah Wasser, will be giving a talk titled ["Friends Don't Let Friends Package Alone"](https://us.pycon.org/2024/schedule/presentation/34/). As she helps us see how packaging doesn't have to be a thorny topic, she'll also share: -* Why consensus-building and user-focused content development is critical for tackling thorny topics such as packaging. -* How to decide which packaging tools satisfy your packaging needs. -* Where to find more information and support around packaging. - -### pyOpenSci-led sprints at PyCon -[PyCon US Development Sprints](https://us.pycon.org/2024/events/dev-sprints/) are four days of intensive learning and development on an open source project of your choice, in a team environment. It's a time to come together with colleagues, old and new, to share what you've learned and apply it to an open source project. Leah will be running a pyOpenSci sprint on Monday, May 20th, and would love to see you there! pyOpenSci is committed to diversity and inclusion, and Pythonistas of all backgrounds and skill levels are invited to participate. - -### Get your pyOpenSci swag at PyCon -We saved the best for last, because we know everyone loves swag! We'll have a fresh batch of stickers and postcards that we want to put in your hands, so be sure to find Leah to say hello and learn more about all things pyOpenSci. Not sure where to find Leah? If you can't make it to her talk, sprints, or the Maintainers Summit, [follow her on Fosstodon](https://fosstodon.org/@leahawasser), and keep an eye on [the pyOpenSci Fosstodon account](https://fosstodon.org/@pyOpenSci) as well! - -## Listen to Leah at SciPy -We’ve received the exciting news that our Executive Director and Founder, [Leah Wasser](https://github.com/lwasser), will be speaking as well as running a Python packaging workshop at [SciPy](https://www.scipy2024.scipy.org/) this year! We’ll be sharing more about Leah’s work as the conference gets closer, but consider this your reminder to grab your tickets! - -## Congratulations on an amazing keynote, Inessa! - - -If you weren’t able to attend PyConDE & PyData Berlin 2024 to hear [Inessa Pawson’s](https://github.com/InessaPawson) keynote, [Tending Open Source Orchards](https://2024.pycon.de/program/7TEYDQ/), never fear! You can [watch her phenomenal talk,](https://www.youtube.com/live/5Bjo6pWj-88?feature=shared&t=711) where she dives into the resilient contributor communities behind every large open source project, and shares her insights on the art and science of fostering resilient open source communities. - -## Upcoming Python events for scientists -### PyCon US -[Registration is still open](https://us.pycon.org/2024/) for [PyCon US 2024](https://us.pycon.org/2024/), happening May 15th–23rd in Pittsburgh, PA. There’s also an online component for folks who can’t be there in person. It’s shaping up to be a stacked event, and you can [view the schedule here](https://us.pycon.org/2024/schedule/). - -### PyCon Italia -Fancy a trip to Florence? Then grab your tickets for PyCon Italia! The conference runs Wednesday, May 22nd through Saturday, May 25th, and has a stacked list of [keynote speakers](https://2024.pycon.it/en/schedule/2024-05-22?view=grid). - -### PyData London -Happening June 14–16, [PyData London](https://pydata.org/london2024/) tickets are available now! With tutorials on Friday, June 14th, and talks Saturday and Sunday, this is shaping up to be a phenomenal event for the international community of data scientists, data engineers, and developers of data analysis tools to share ideas and learn from each other. - -### SciPy -Happening July 8th–14th in Tacoma, WA, [SciPy](https://www.scipy2024.scipy.org/) registration is live! Early bird pricing has ended, but it’s not too late to grab a standard ticket. - -### Tell us about your event! -Have an upcoming scientific Python event that’s open to the public? Email us at [media@pyopensci.org](mailto:media@pyopensci.org) with the details to have your event listed. - -## Connect with us -Stay up-to-date with all things pyOpenSci by following us on [LinkedIn](https://www.linkedin.com/company/pyopensci) and [Fosstodon](https://fosstodon.org/@pyOpenSci). And if you’re interested in our weekly newsletter where we share news, blog posts, and monthly updates, [subscribe on LinkedIn](https://www.linkedin.com/build-relation/newsletter-follow?entityUrn=7179551305344933888). We also send out a monthly recap newsletter to [our mailing list](https://eepurl.com/iM7SOM)! diff --git a/_posts/2024-05-14-pyos-guide-to-pyconus-2024.md b/_posts/2024-05-14-pyos-guide-to-pyconus-2024.md deleted file mode 100644 index 8de200d5..00000000 --- a/_posts/2024-05-14-pyos-guide-to-pyconus-2024.md +++ /dev/null @@ -1,105 +0,0 @@ ---- -layout: single -title: "The pyOpenSci Guide to PyCon 2024" -blog_topic: education -excerpt: "PyCon US 2024 is finally here, and we can't wait to connect with you! This post has all of the talks, panels, summits, and keynotes where pyOpenSci community members (and friends!) will be speaking." -author: "Jesse Mostipak" -permalink: /blog/pyos-guide-to-pyconus-2024.html -header: - overlay_image: images/blog/2024/may/pycon-us-2024.png -categories: - - blog-post - - community -classes: wide -toc: true -comments: true -last_modified: 2024-08-29 ---- -## Connect with the pyOpenSci community at PyCon US 2024! -We know there are a million things to do and see while at [PyCon US 2024](https://us.pycon.org/2024/) in beautiful Pittsburgh this weekend, so we wanted to compile a list of every talk, keynote, summit, and panel where you can expect to meet some of the fantastic pyOpenSci community members as well as hear from some friends of pyOpenSci! - -## Friday, May 17th -### 11:00--4:30 PM: Maintainers Summit -*Room 402, David L. Lawrence Convention Center*\ -The [Maintainers Summit](https://us.pycon.org/2024/events/maintainers-summit/), organized by [Inessa Pawson](https://github.com/InessaPawson), [Chris Rose](https://github.com/offbyone), [Kara Sowles](https://github.com/karasowles), and [Leah Wasser](https://github.com/lwasser), is where the Python community comes together to discuss and foster best practices on how to develop sustainable projects and nurture thriving communities. Check out the video below about what to expect at this year's Maintainers Summit produced by PyCon US 2024 Conference Chair [Mariatta Wijaya](https://github.com/readme/stories/mariatta-wijaya): - - - Watch the video - - -Be sure you're at the Maintainers Summit not only for the opening remarks at 11:00 AM, but also so that you don't miss [Carol Willing's](https://github.com/willingc) 11:15 AM talk: Team Compass: Setting a course for a dynamic project! - -### 11:45--12:15 PM: Making Beautiful, Publication Quality Tables in Python is Possible in 2024 -*Ballroom BC*\ -**[Rich Iannone](https://github.com/rich-iannone) & [Michael Chow](https://github.com/machow)**\ -We couldn't agree more with Rich and Michael when they say that "The display of tables can be beautiful. Tables can convey information effectively, just as plots do and, sometimes, it’s the better way to present data. Truly, the time has come to bridge the divide between raw DataFrame output and wondrously-structured tables suitable for publication." In this talk they'll go over which table components make for effective displays of information, and which combinations of Python packages that fit together to make this important task possible. - -### 1:45--2:30 PM: Modern binary build systems -*Ballroom A*\ -**[Henry Fredrick Schreiner III](https://github.com/henryiii)**\ -Henry will be guiding attendees through how easy it is now to set up a binary extension using CMake, Meson, or Maturin (Rust only). It can be done with only three files each containing only a handful of lines of code. Unlike the previous solutions, this covers cross-compilation,multithreaded builds, modern C++ standards, and other features that would each require custom code in a classic setup.py. Combined with cibuildwheel for building wheels and good support from modern binding tools like pybind11 and nanobind, the barrier for entry to reliable compiled extensions has dropped dramatically. - -### 2:45--3:15 PM: Hatch: The only tool you need -*Ballroom A*\ -**[Ofek Lev](https://github.com/ofek)**\ -It's no secret that we're big fans of Hatch---we even [taught a Python Packaging Workshop](https://www.pyopensci.org/blog/pyos-education-announcement.html) using [Hatch](https://hatch.pypa.io/latest/)! With an ever-growing set of tools used in Python packaging, Ofek will be presenting on how Hatch can be used as a unifying tool for Python packaging. - -## Saturday, May 18th -### 9:00--9:25 AM: Diversity & Inclusion Panel -*Expo Hall B*\ -Diversity and inclusion is core to the pyOpenSci mission, and we can't wait for this panel. The line-up is incredible, with [Débora Azevedo](https://github.com/deboraazevedo), [Dima Dinama](https://github.com/dmaharika), [Jessica Greene](https://github.com/sleepypioneer), Jules, [Mason Egger](https://github.com/MasonEgger), and [Abigail Dogbe](https://github.com/mesrenyamedogbe) all speaking. We know we'll be walking away from this panel inspired! - -### 10:30--11:00 AM: Friends don't let friends package alone -*Hall C*\ -**[Leah Wasser](https://github.com/lwasser)** -
- - A puma leaning against a rock and flexing a front arm. Overlaid on the photo in meme text is the phrase 'so you want to talk about Python packaging.' - -
-We couldn't talk about PyCon US without talking about our Executive Director and Founder, Leah Wasser, who will be talking all things Python packaging! Python packaging can be a scary and confusing endeavor. The ecosystem can be thorny and filled with many decisions around which tool or approach to use. You’re also likely to get pricked by an approach or tutorial that doesn't work as expected. However, the packaging journey doesn’t have to be prickly - particularly for pure Python packages. By building community consensus around user goals and moderating healthy discussion, pyOpenSci is paving a smooth, thorn-free path for successful packaging. - -### 1:45--5:45 PM: Packaging Summit -*David L. Lawrence Convention Center*\ -The goal of the [Packaging Summit](https://us.pycon.org/2024/events/packaging-summit/) is to try to take advantage of the fact that, at PyCon, we can get a high concentration of these stakeholders in one room at the same time. This allows us to sync up on current and future best practices and to quickly come to agreements that would take months or even years over higher-latency media of discussion (e.g. mailing lists, forums, issue tickets). - -### 2:30--3:00 PM: NetworkX is Fast Now: Graph Analytics Unleashed -*Hall C*\ -**[Mridul Seth](https://github.com/MridulS) & [Erik Welch](https://github.com/eriknw)**\ -Have you ever wondered how to find connections in your data and to gain insights from them? Come discover how NetworkX makes this easy (and fast!). - -This talk is broadly divided into two parts. First we will talk about the power of graph analytics and how you can use tools like NetworkX to extract information from your data, and then we will talk about how we made the machinery behind NetworkX work with heterogeneous backends like GraphBLAS (CPU optimized) and cuGraph (GPU optimized). - -### 2:30--3:00 PM: Eternal sunshine of the spotless development environment -*Room 301--305*\ -**[Sarah Kaiser](https://github.com/crazy4pi314)**\ -In this talk, Sarah will briefly cover why setting up container infrastructure, which can be useful for isolating your project environments and dig into how you can extend that with Dev Containers to configure a complete development experience using VS Code. She'll also look at two common OSS project situations, onboarding and workshops, to see how workflows for using Dev Containers and other supporting tools make things easier. No container experience required, and while a brief familiarity with VS Code is helpful, it's not necessary. - -## Sunday, May 19th -### 10:00--3:15 PM: Documentation Summit -The [Documentation Summit](https://us.pycon.org/2024/events/hatchery/docs-summit/) will be a full-day summit including talks and panel sessions inviting leaders in documentation to share their experience in how to make good documentation, discussion about documentation tools such as sphinx, mkdocs, themes etc, what are the common mistakes and how to avoid them. - -And at 1:00 PM you can catch [Carol Willing](https://github.com/willingc) and [Ned Batchelder](https://github.com/nedbat) giving an update from the Python Docs Editorial Board! - -### 3:15--4:00 PM: Keynote -*Expo Hall B*\ -**[Sumana Harihareswara](https://github.com/brainwane)**\ -[Sumana Harihareswara](https://www.harihareswara.net/) is an open source contributor and leader who has managed work on pip, PyPI, GNOME, MediaWiki, HTTPS Everywhere, autoconf, GNU Mailman, and other projects---and who is working on a book to teach what she's learned along the way. Between 2016 and 2021, Harihareswara led fundraising for and managed the next-generation overhauls of PyPI and of pip's dependency resolver and user experience. Her work has earned her an Open Source Citizen Award and a Google Open Source Peer Bonus. - - -## Connect with us -### pyOpenSci at PyCon US 2024 -
- - A close-up photo of Juno, a black lab mix, chewing on sticks. In front of Juno is a collection of pyOpenSci stickers. - -
- Juno enjoying some sticks while showing off the latest pyOpenSci swag! -
-
-While there will be a whole host of pyOpenSci community members at PyCon US this year, be sure to make time to say hello to our Executive Director and Founder, Leah Wasser. Not only would she love to chat with you about Python, open source, and open science, she's got a whole new set of stickers (including holographic stickers!) to give away. And for those lucky few, she's also got a limited edition run of postcards to hand out! - -And we'd love to hear about your PyCon experience! Be sure to tag us on [LinkedIn](https://www.linkedin.com/company/pyopensci) and/or [Fosstodon](https://fosstodon.org/@pyOpenSci)! - -### pyOpenSci around the web -Stay up-to-date with all things pyOpenSci by following us on [LinkedIn](https://www.linkedin.com/company/pyopensci) and [Fosstodon](https://fosstodon.org/@pyOpenSci). And if you’re interested in our weekly newsletter where we share news, blog posts, and monthly updates, [subscribe on LinkedIn](https://www.linkedin.com/build-relation/newsletter-follow?entityUrn=7179551305344933888). We also send out a monthly recap newsletter to [our mailing list](https://eepurl.com/iM7SOM)! diff --git a/_posts/2024-05-23-pyos-call-for-editors.md b/_posts/2024-05-23-pyos-call-for-editors.md deleted file mode 100644 index e543b51e..00000000 --- a/_posts/2024-05-23-pyos-call-for-editors.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -layout: single -title: "Volunteers needed: pyOpenSci Editorial Board" -blog_topic: software -excerpt: "pyOpenSci is looking for volunteers from a wide array of scientific backgrounds to join our Editorial Board." -author: "Jesse Mostipak" -permalink: /blog/pyos-call-for-editors-may-2024.html -header: - overlay_image: images/blog/headers/call-for-editors.png -categories: - - blog-post - - community -classes: wide -toc: true -comments: true -last_modified: 2024-08-29 ---- -## Join the pyOpenSci Editorial Board -### We're currently in need of editors with domain expertise in climatology and/or energy, and also accepting applications from all scientific domains -Thanks to the efforts of our incredible team of volunteers, pyOpenSci is growing! As a result, we're currently seeing a large number of package submissions to [our open peer review process](https://www.pyopensci.org/about-peer-review/index.html). In order to continue supporting scientists in the development of open source scientific software, we're looking to add volunteer editors to our Editorial Board. - -Learn more about the Volunteer Editor role in our [Editor Guide](https://www.pyopensci.org/software-peer-review/how-to/editors-guide.html). -{: .notice} - -The pyOpenSci Editorial Board is comprised of a diverse group of volunteers, with each editor being responsible for the following tasks: - -* Finding reviewers from diverse backgrounds who have a mixture of scientific domain, software usability and Python experience. -* Overseeing the entire review process for a package ensuring it runs in a timely and efficient manner. -* Supporting the submitting authors and reviewers in answering questions related to the review. -* Determining whether that package should be accepted into the pyOpenSci ecosystem once the review has wrapped up. - -We often find that people interested in joining our Editorial Board are more than capable of performing the role, but sometimes doubt themselves. We want to strongly encourage you to apply if you're interested in the role. Not only do we offer support and guidance in your role as an Editor, we also have a warm, welcoming community that is always happy to help. Everyone starts somewhere, and we'd love for you to join us! - -It's also critical to the pyOpenSci mission that our Editorial Board have a combined expertise in various scientific domains, technical expertise in Python packaging, awareness of the importance of documentation in package usability, and awareness of the importance of CI/test suites in order to ensure robust software development. - -**IMPORTANT:** We do not expect any single editor to be an expert in all of these areas! - -We also appreciate when editors have experience working with or in the Python open source software community be it maintaining packages, contributing to packages, previously reviewing a pyOpenSci package, or supporting the community. This is certainly not a requirement, however, if you are interested in getting involved with pyOpenSci! - -If you're new to the pyOpenSci review process, we do also have Guest Editor volunteer roles, where you'll have more guidance or mentorship in order to complete the review. -{: .notice} - -### Apply to be a pyOpenSci editor -We're currently looking for editors from a wide variety of scientific backgrounds, with a current focus on climatology and energy. If you feel you'd be a good fit for the pyOpenSci Editorial Board, please fill out [this form](https://docs.google.com/forms/d/e/1FAIpQLScRQHQ7NKVEAG3BKAphiUdVFvQ5nkez0IpyXBMZDzXjuBPloQ/viewform). - -If you're interested in joining our Editorial Board, but have questions or would like to chat with someone from pyOpenSci about volunteering to be an editor, please email us at [media@pyopensci.org](mailto:media@pyopensci.org). - -## Meet our current Editorial Board -You can explore [our current directory of pyOpenSci editors](https://www.pyopensci.org/about-peer-review/index.html#meet-our-editorial-board) on our website, and also read [a series of short interviews](https://www.linkedin.com/pulse/meet-pyopensci-editorial-team-pyopensci-robpc/?trackingId=gE1IVxw%2BRX6P6NGP%2B%2BbM4Q%3D%3D) with many of our editors in a recent issue of [our weekly newsletter](https://www.linkedin.com/build-relation/newsletter-follow?entityUrn=7179551305344933888). - -## Connect with pyOpenSci -Stay up-to-date with all things pyOpenSci by following us on [LinkedIn](https://www.linkedin.com/company/pyopensci) and [Fosstodon](https://fosstodon.org/@pyOpenSci). Interested in our newsletters? We share news, blog posts, and monthly updates [in our LinkedIn newsletter](https://www.linkedin.com/build-relation/newsletter-follow?entityUrn=7179551305344933888), and a monthly recap newsletter to [our mailing list](https://eepurl.com/iM7SOM)! diff --git a/_posts/2024-05-24-oss-fall-festival-save-the-date-2024.md b/_posts/2024-05-24-oss-fall-festival-save-the-date-2024.md deleted file mode 100644 index d90a940e..00000000 --- a/_posts/2024-05-24-oss-fall-festival-save-the-date-2024.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -layout: single -title: "Save the Date: pyOpenSci's 2024 Open Science Fall Festival" -blog_topic: community -excerpt: "Shake off those post-PyCon blues by adding our Open Science Fall Festival to your calendar!" -author: "Jesse Mostipak" -permalink: /blog/save-the-date-oss-fall-festival-2024.html -header: - overlay_color: "#33205c" -categories: - - blog-post - - community -classes: wide -toc: true -comments: true -last_modified: 2024-08-29 ---- -## You're invited: pyOpenSci's Open Science Fall Festival 2024 -Happening ~~September 28--29th, 2024,~~ October 28th through November 1st, 2024, the pyOpenSci Open Science Fall Festival will be held entirely online, and be a fantastic opportunity to bring together members of the Python community that create open source, open science tools with the members of the Python community who use these tools. In building this festival, our focus is on a grassroots, community-led event with some big unconference vibes. - -While we're still ironing out some of the details, we know it'll be a combination of talks, trainings, and opportunities to connect with the community through networking and social events. And we're working hard to keep ticket costs as accessible as we possibly can. - -> UPDATE: Our [pyOpenSci Fall Festival event page](/events/pyopensci-2024-fall-festival.html) is LIVE! We'll continuously be updating this page with event news and details. - -## Connect with pyOpenSci -Stay up-to-date with all things pyOpenSci and our Open Source Fall Festival by following us on [LinkedIn](https://www.linkedin.com/company/pyopensci) and [Fosstodon](https://fosstodon.org/@pyOpenSci). And if you’re interested in our weekly newsletter where we share news, blog posts, and monthly updates, [subscribe on LinkedIn](https://www.linkedin.com/build-relation/newsletter-follow?entityUrn=7179551305344933888). diff --git a/_posts/2024-05-28-pyos-newsletter-june-2024.md b/_posts/2024-05-28-pyos-newsletter-june-2024.md deleted file mode 100644 index 4a6d3bc2..00000000 --- a/_posts/2024-05-28-pyos-newsletter-june-2024.md +++ /dev/null @@ -1,154 +0,0 @@ ---- -layout: single -title: "pyOpenSci Newsletter: June 2024" -blog_topic: updates -excerpt: "We're starting June with a record number of Python package submissions, a call for editors, a PyCon US reflection, and some SciPy meeting news!" -author: "Jesse Mostipak" -permalink: /blog/pyos-newsletter-june-2024.html -header: - overlay_image: images/headers/scipy-2024-workshop.png -categories: - - blog-post - - community -classes: wide -toc: true -comments: true -last_modified: 2024-08-29 ---- -## PyCon was incredible! -We feel so incredibly fortunate to have connected with so many of you at PyCon US! While we're still recovering from a week of summits, talks, and sprints, we wanted to share this sentiment from our Executive Director and Founder, [Leah Wasser](https://github.com/lwasser), which she shared on [her Fosstodon account](https://fosstodon.org/@leahawasser): -
- - Screenshot of a Fosstodon post from Leah Wasser, reading: In my first few moments at pycon I ran into some of my conda friends & was quickly reminded of why this conference is so powerful.  Python is more than a programming language. It's a community of people who I can geek out with about the most obscure topics. I belong. People care. They care a lot about their work. They care about helping each other learn.  While this is a tech conference, it's really people that make python the vibrant community that it is. - -
- -### pyOpenSci's PyCon US Open Space -This year, pyOpenSci's first Open Space brought together 17 attendees from various organizations, including Anaconda, NVIDIA, CPython, and more. The discussions focused on interoperability, scientific data workflows, and Python packaging for science, emphasizing the need for consistent tools and data formats to reduce cognitive load for scientists. A key topic was the gap between exploratory scripts and fully packaged code, highlighting the need for "in between" options for sharing code and outputs. These insights will guide pyOpenSci's efforts, with more discussions planned for the Fall Festival, scheduled for September 28-29, 2024. - -### pyOpenSci's PyCon US Sprint -This year we had a tremendous turnout of over 20 people from several countries for our 1-day pyConUS sprint. At last count this resulted in about [30 issues and pull requests](https://github.com/orgs/pyOpenSci/projects/12/views/1) where volunteers who were there to support scientists, helped us fix issues on our website, in our tutorials and our [packaging](https://www.pyopensci.org/python-package-guide/) and [peer review](https://www.pyopensci.org/software-peer-review/index.html) guidebooks. So many long-standing issues and bugs were fixed thanks to this wonderful Python community. - -## See you at SciPy -[SciPy 2024](https://www.scipy2024.scipy.org/) is just around the corner, and we can't wait to see you there! We're pulling together our pyOpenSci Guide to SciPy, similar to the guide we did for [PyCon 2024](https://www.pyopensci.org/blog/pyos-guide-to-pyconus-2024.html), and wanted to give you a preview of some of the tutorials and talks being given by pyOpenSci Community members! - -### Tutorials -#### [Interactive data visualizations with Bokeh (in 2024)](https://cfp.scipy.org/2024/talk/JRLMLD/) -Along with Timo Metzger and Bryan Van de Ven, pyOpenSci community member [Pavithra Eswaramoorthy]() will be covering everything you need to know to create beautiful and powerful interactive plots from scratch using Bokeh’s latest features. Starting with a quick introduction of Bokeh’s core concepts, the team will cover creating and customizing simple static plots like line and bar charts before introducing layers of interactivity, creating specialized plotting features like geographic maps, contour plots, Mathematical Text, and discussing new additions to Bokeh like ImageStacks. By the end, you will be able to create a complete interactive dashboard using Bokeh. - -#### [3D Visualization with PyVista](https://cfp.scipy.org/2024/talk/GKGRWE/) -Led by Tetsuo Koyama, Alexander Kaszynski, Bill Little, and Bane Sullivan, this tutorial demonstrates [PyVista's](https://github.com/pyvista/pyvista) latest capabilities and bring a wide range of users to the forefront of 3D visualization in Python, including: - -* Use PyVista to create 3D visualizations from a variety of datasets in common formats. -* Overview the classes and data structures of PyVista with real-world examples. -* Be familiar of the various filters and features of PyVista. -* Know which Python libraries are used and can be used by PyVista (meshio, trimesh etc). -* We see this tutorial catering to anyone who wants to visualize data in any domain, and this ranges from basic Python users to advanced power users. - -#### [From RAGS to riches: Build an AI document inquiry web-app](https://cfp.scipy.org/2024/talk/W3ZJWG/) -Pavithra Eswaramoorthy, a Developer Advocate at [Quansight](https://quansight.com/), is teaming up with Dharhas Pothina and Andrew Huang to cover how to use RAG to build document-inquiry chat systems using different commercial and locally running LLMs. The topics we’ll cover include: - -* **Introduction to RAG**, how it works and interacts with LLMs, and Ragna - a framework for RAG orchestration -* **Creating and optimizing a basic chat function** that uses popular LLMs (like GPT) answers questions about your documents, using a Python API in Jupyter Notebooks -* **Running a local LLM on GPUs** on the provided platform, and comparing its performance to commercial LLMs -* **Introduction to Panel**, by creating a basic chat UI for Ragna using Panel’s ChatBox widget -* **Building and deploying a Panel-based web-app**, which extends the basic chat UI and includes more application components - -By the end of this tutorial, you will have an understanding of the fundamental components that form a RAG model, and practical knowledge of open source tools that can help you or your organization explore and build on your own applications. This tutorial is designed to enable enthusiasts in our community to explore an interesting topic using some beginner-friendly Python libraries. - -#### [Create Your First Python Package: Make Your Python Code Easier to Share and Use](https://cfp.scipy.org/2024/talk/QT9GBY/) -Led by pyOpenSci's Executive Director and Founder, [Leah Wasser](https://github.com/lwasser), in this tutorial session you'll learn how to: - -* Create code that can be installed into different environments -* Use [Hatch](https://hatch.pypa.io/latest/) as a workflow tool, making setup and installation of your code easier -* Use Hatch to publish your package to (test) PyPI - -Creating code that can be shared and reused is the gold-standard of open science. But tools and skills to share your code can be tricky to learn. In this hands-on tutorial, you’ll learn how to turn your code into an installable Python module ( a file containing Python code that defines functions, classes, and variables), that can be shared with others. We will provide pre-built code and module examples for each step of the tutorial to make it more beginner-friendly. However, to get the most out of this tutorial, you should be familiar with writing Python code, writing and using functions and you should understand the concept of Python environments. You should also have Python and Hatch installed on your computer prior to attending. If you are newer to environments and don’t have a preferred tool, then we recommend that you use conda. However, any environment tool that you prefer works well. - -Interested in helping out with our Packaging workshop? Reach out to us at [media@pyopensci.org](mailto:media@pyopensci.org). We're looking for volunteers to assist learners throughout the workshop. -{: .notice} - -#### [Data of an Unusual Size (2024 edition): A practical guide to analysis and interactive visualization of massive datasets](https://cfp.scipy.org/2024/talk/UKLNLQ/) -Pavithra Eswaramoorthy, and Dharhas Pothina, both from [Quansight](https://quansight.com/), will help you learn the fundamentals of analyzing massive datasets with real-world examples on actual powerful machines on a public cloud provided by the presenters – starting from how the data is stored and read, to how it is processed and visualized. - -### Talks -#### [The power of community in solving scientific Python’s most challenging problems](https://cfp.scipy.org/2024/talk/AMTLJ7/) -This talk from our Executive Director and Founder, [Leah Wasser](https://github.com/lwasser), shares pyOpenSci’s knowledge in building a constructive open peer review process that supports maintainers, and also resources that make Python packaging more accessible and easier-to-navigate. Our insights are based on five years of experience working with over 200 community members, who have evaluated 58 packages developed by over 56 package maintainers. - -Leah's talk is about writing the [pyOpenSci Python Packaging Guide](https://www.pyopensci.org/python-package-guide/) via community! -{: .notice} - -#### [Great Tables for Everyone](https://cfp.scipy.org/2024/talk/BRMQRW/) -This talk from Rich Iannone, whose package [Great Tables](https://github.com/pyOpenSci/software-submission/issues/184) is currently under review with pyOpenSci, will provide a demonstration of Great Tables, showing how data can come alive in when in tabular form. We’ll use data from the fields of meteorology, chemistry, and the atmospheric sciences within our table-making examples. We’ll learn how the different components of a table (e.g., the column labels, the header, etc.) can be manipulated to best convey the data. We believe tables are worthy of being deemed data visualizations and we hope that Great Tables can become an indispensable tool in your data analysis workflow. - -#### [From Code to Clarity: Using Quarto for Python Documentation](https://cfp.scipy.org/2024/talk/GFGSTS/) -Isabel Zimmerman, a member of our triage team and an Emeritus Editor in Chief, will be speaking from her experiences as an open source developer who works with multilingual tools, and how Quarto + quartodoc helped to fill those gaps. This talk is intended for anyone who might have to communicate with stakeholders through code using a web-based format. She'll be focusing on Python package documentation websites, but the stories and examples expose the difficulties in many types of technical communication. The intent is to have a lighthearted talk filled with examples of how to make technical communication and publishing easier and more accessible. - -#### [Vector space embeddings and data maps for cyber defense](https://cfp.scipy.org/2024/talk/STUXTH/) -pyOpenSci community member Benoit Hamelin will cover how the vast amounts of information of interest to cyber defense organizations comes in the form of unstructured data; from host-based telemetry and malware binaries, to phishing emails and network packet sequences. All of this data is extremely challenging to analyze. In recent years there have been huge advances in the methodology for converting unstructured media into vectors. However, leveraging such techniques for cyber defense data remains a challenge. - -Imposing structure on unstructured data allows us to leverage powerful data science and machine learning tools. Structure can be imposed in multiple ways, but vector space representations, with a meaningful distance measure, have proven to be one of the most fruitful. - -In this talk, he will demonstrate a number of techniques for embedding cyber defense data into vector spaces, as well as discuss how to leverage manifold learning techniques, clustering, and interactive data visualization to broaden our understanding of the data and enrich it with expert feedback. - -### Sprints -We'll also be holding a pyOpenSci sprint at SciPy 2024! Be sure to follow us on [LinkedIn](https://www.linkedin.com/company/pyopensci) and [Fosstodon](https://fosstodon.org/@pyOpenSci) for more details! - -## Save the Date: pyOpenSci's Open Source Fall Festival -
- - Abstract shapes on a white background with the text: Save the Date. pyOpenSci Open Source Fall Festival. September 28 & 29, 2024. Entirely online! Follow us on socials for more info. fosstodon.org/@pyOpenSci linkedin.com/company/pyopensci pyopensci.org. - -
-We love a good get-together, and our Open Source Fall Festival is no exception! Held entirely online using Spatial Chat, our goals is to bring together members of the Python community that create open source tools with the members of the Python community who use the tools. In building this festival, our focus is on a grassroots, community-led event with some big unconference vibes. So mark your calendars, and save the date for September 28--29th, 2024! - -## Python packages for everyone! -pyOpenSci's [Open Peer Review process](https://www.pyopensci.org/about-peer-review/index.html) is experiencing an [unprecedented number of Python package submissions](https://github.com/pyOpenSci/software-submission/issues)! We're excited to see so many wonderful packages that are helping scientists help make the world a better place being submitted for review. Our open peer review process facilitates scientists getting credit and recognition for the work they’ve invested in developing scientific Python tools while also supporting them in building better software. The peer review process also supports scientists in finding vetted and maintained software, which drives their open science workflows. - -You can submit your package for review (or pre-submission inquiry to determine if it's in scope) by [submitting an issue](https://github.com/pyOpenSci/software-submission/issues/new/choose) to [our software-submission repository](https://github.com/pyOpenSci/software-submission). And because all of our reviews are open, [you can see our review process in action](https://github.com/pyOpenSci/software-submission). - -We're also accepting applications for [package reviewers](https://docs.google.com/forms/d/e/1FAIpQLSeVf-L_1-jYeO84OvEE8UemEoCmIiD5ddP_aO8S90vb7srADQ/viewform). We welcome reviewers from a diversity of background and with varying levels of Python technical expertise. - -Some of the basic things that we look for in a review include: -* Familiarity with using the Python programming language. -* Ability to evaluate a Python package for usability and documentation quality. -* Ability to provide a technical review of Python package structure and code quality / approach to solving the problems that the package seeks to address. - -We like to have a mix of technical and usability focus in our reviews so it’s ok if you don’t have all of the above skills! Not sure if you're ready to review on your own? Check out the [pyOpenSci mentorship program](https://www.pyopensci.org/software-peer-review/how-to/reviewer-guide.html#a-guide-for-new-reviewers)! - -## Get involved with pyOpenSci: call for editors -
- - Off-white square on a purple floral background with text that reads: Get involved with pyOpenSci! We're growing out editorial team, and are seeking volunteers from a wide variety of scientific backgrounds. Editors: lead the review process for 3-4 packages a year and assist with editorial and peer review policies. People from diverse backgrounds are especially encouraged to apply! Questions? Email media@pyopensci.org. Apply: bit.ly/pyOSEditor - -
- -If you're looking to give back to the broader Python community, consider joining our Editorial Board! The pyOpenSci Editorial Board is comprised of a diverse group of volunteer editors, each one responsible for the following tasks: - -* Finding reviewers from diverse backgrounds who have a mixture of scientific domain and Python experience. -* Overseeing the entire review process for a package ensuring it runs in a timely and efficient manner. -* Supporting the submitting authors and reviewers in answering questions related to the review. -* Determining whether that package should be accepted into the pyOpenSci ecosystem once the review has wrapped up. - -While we have a current need for editors with expertise in climatology and/or energy, we're also recruiting editors from all scientific disciplines. If you feel you'd be a good fit for the pyOpenSci Editorial Board, please fill out [this form](https://docs.google.com/forms/d/e/1FAIpQLScRQHQ7NKVEAG3BKAphiUdVFvQ5nkez0IpyXBMZDzXjuBPloQ/viewform). - -If you're interested in joining our Editorial Board, but have questions or would like to chat with someone from pyOpenSci about volunteering to be an editor, please email us at [media@pyopensci.org](mailto:media@pyopensci.org). - -## Upcoming Python events for scientists -### SciPy -With keynotes from [Julia Silge](https://juliasilge.com/), [Dr. Elizabeth (Libby) Barnes](https://sites.google.com/view/barnesgroup-csu/prof-barnes), and [Kyle Cranmer](https://www.physics.wisc.edu/directory/cranmer-kyle/), [SciPy 2024](https://www.scipy2024.scipy.org/) is going to be yet another conference you don't want to miss! Held at the Tacoma Convention Center, July 8-14, 2024, [tickets are still available](https://ti.to/scipy/scipy2024)! - -### PyData Eindhoven -With 14 captivating talks lined up, [PyData Eindhoven](https://pydata.org/eindhoven2024/) offers a diverse range of topics to expand your knowledge and inspire innovative thinking. From cutting-edge data analysis techniques to machine learning applications, our expert speakers will cover it all. Co-hosted with Day 2 of [JuliaCon](https://juliacon.org/2024/), [grab your tickets](https://pydata.org/eindhoven2024/) and join your peers in the Netherlands this July 11th! - -### BioC 2024 -Taking place Wednesday July 24 - Friday July 26 at the Van Andel Institute in Grand Rapids, MI,the BioC 2024: Where Software and Biology Connect hybrid conference still has tickets available. And what's more, all proceeds go to helping underrepresented minorities and individuals from low to middle-income countries, including students, fellows, and early career investigators. - -### PyData Vermont -[PyData Vermont 2024](https://pydata.org/vermont2024/) is a two day in-person event for the international community of data scientists, data engineers, and developers of data analysis tools to share ideas and learn from each other. The event will be in-person at the Main Street Landing Performing Arts Center, July 29 to July 30, 2024. [Register today](https://pydata.org/vermont2024/tickets)! - -### Tell us about your event! -Have an upcoming scientific Python event that’s open to the public? Email us at [media@pyopensci.org](mailto:media@pyopensci.org) with the details to have your event listed. - -## Connect with us -Stay up-to-date with all things pyOpenSci by following us on [LinkedIn](https://www.linkedin.com/company/pyopensci) and [Fosstodon](https://fosstodon.org/@pyOpenSci). And if you’re interested in our weekly newsletter where we share news, blog posts, and monthly updates, [subscribe on LinkedIn](https://www.linkedin.com/build-relation/newsletter-follow?entityUrn=7179551305344933888). We also send out a monthly recap newsletter to [our mailing list](https://eepurl.com/iM7SOM)! diff --git a/_posts/2024-05-30-pyos-pyconus-2024-recap.md b/_posts/2024-05-30-pyos-pyconus-2024-recap.md deleted file mode 100644 index b499c1c9..00000000 --- a/_posts/2024-05-30-pyos-pyconus-2024-recap.md +++ /dev/null @@ -1,244 +0,0 @@ ---- -layout: single -title: "pyOpenSci at PyCon US 2024 - Python packaging and community" -blog_topic: community -excerpt: "Learn about pyOpenSci's experience at PyCon US 2024, how pyOpenSci is making Python packaging more accessible and beginner friendly and how the Python community is helping us get there." -author: "Leah Wasser" -permalink: /blog/recap-pyos-pyconus-2024.html -header: - overlay_image: images/blog/headers/pycon-us-2024-header.png -categories: - - blog-post - - community -classes: wide -toc: true -comments: true -last_modified: 2024-08-29 ---- - -
-## TL;DR - -* pyOpenSci had a strong presence at [PyCon US](https://us.pycon.org/2024/) this year. I hope this continues for years to come! We held an open space, helped run the [Maintainers Summit](https://us.pycon.org/2024/events/maintainers-summit/) (lead by [Inessa Pawson](https://github.com/InessaPawson) for 5 years and counting), gave [a talk on Python packaging](https://us.pycon.org/2024/schedule/presentation/34/) and ran a 1 day sprint where over 16 people contributed to our efforts. -* pyOpenSci’s theme this year for PyConUS was people first: people first when trying to make technical concepts easier to understand, people first when trying to write good tutorials or documentation and people first when you are trying to solve the world’s hardest problems. -* Giving a talk on packaging at pyConUS triggered every ounce of the imposter in me. But in the end it was a rewarding experience. Having friends in the audience made a world of difference. It was calming to focus on people who I know support both me and this vibrant organization. Friends really should never let friends…package…or use Python…or do anything technical…alone. - -
- -## Another year, another incredible PyCon - -Wow! I wasn’t sure it would be possible to top [last year’s PyCon US 2023 experience](https://www.pyopensci.org/blog/pycon-2023-packaging-presentation-sprints-leah-wasser.html) -in Salt Lake City, but this year's [PyConUS 2024](https://us.pycon.org/2024/) *was even better*. People are learning about [pyOpenSci](https://www.pyopensci.org/). The community is excited to support our mission to **help -scientists create better software and make their science more open and -reproducible** so they can tackle the world's greatest challenges. - -PyCon US, run by the [Python Software -Foundation,](https://www.python.org/psf-landing/) is one of the biggest Python meetings in the world, with a record 2,700 registrations this year. -{: .notice } - -I also knew that I'd get to see a bunch of the friends who I met last year. I was returning to -this inclusive community, filled with Pythonistas like me -who care, who want to learn, and most importantly who want to help each other. - -And...thanks to our incredible Community Manager, -[Jesse (@kiersi on GitHub)](https://github.com/kiersi), we now have -a [pyOpenSci](https://www.pyopensci.org) visual brand -- and stickers galore. - -
- Image of a flat coated retriever chewing on a stick with a bunch of pyOpenSci stickers in front of her cut face. -
My rescue pup Juno prefers sticks, but also loves pyOpenSci .
-
- -### So different from last year - -My experience last year was a bit different. I walked into my first PyCon -US having no idea how I'd fit in at the meeting. I didn’t know -many people. I had never led a [mentored sprint](https://www.mentored-sprints.dev/). -And I wasn't sure how my packaging talk, [Friends Don't Let Friends Package Alone](https://us.pycon.org/2024/schedule/presentation/34/) would be received; you see, packaging is a notoriously thorny -topic. - -[Last year turned out pretty great - read more here.](/blog/pycon-2023-packaging-presentation-sprints-leah-wasser.html) - -However, rather than feeling like an outsider, I met a lot of new people -who became new Python friends and colleagues. I learned about a lot of cool -projects and organizations and our sprints were awesome with many new contributions. Yaas! - -My takeaway: if you are considering going to PyCon but are worried about not knowing -people YOU SHOULD STILL GO! You can feel the true spirit of open source (and open science) at PyCon US. No judgement. All "levels" of Pythonistas welcome. - -## My first main track PyCon US talk: Friends don't let friends package alone - -This year, I gave my first main track talk at PyCon, titled “Friends Don’t Let -Friends Package Alone.” Getting a main track slot means presenting on a big -stage to a huge room of people! It was real - headset mic, incredible -tech support, and even an “escort” from the speaker room to the main stage. Wow! - -You can check it out on youtube below: - -{% include video id="oA6-f7PtTQ4" provider="youtube" %} - -Speaking of friends, it was my friends who got me through this talk. I saw them sitting in -the front rows, smiling and supporting me. They both empowered me and gave me calm. - -As someone who often battles imposter syndrome, giving a talk on the "big" stage of a tech meeting -was unforgettable. - -
- Image of a woman on a big stage wearing a red tank top talking. On the side is a slide that says pyOpenSci - Save the Date. Open science fall festival september 28-29 2024 -
Me talking on the stage! I was so nervous before but once I got up there, everything calmed in my mind. .
-
- -### A talk about making Python packaging more beginner friendly - -My talk focused on how pyOpenSci helps beginners by breaking down -complex packaging concepts into simple, digestible pieces. I leaned into decades of -experience and study of teaching data science to various audiences, from big-ten university students -to students at under-resourced and under-served tribal colleges. The essence of my talk was about the importance of -community and collaboration in the thorny Python packaging ecosystem, and also why documentation should focus on fundamental concepts that users need to understand, rather than tools. - -Our pyOpenSci community has created a comprehensive, [beginner-friendly Python -package guide](https://www.pyopensci.org/python-package-guide/) with contributions from over 50 community members. Key takeaways -from my talk: - -* leverage community support, -* create early wins for users, focus on -fundamental concepts, and -* avoid “Too Many Options and Opinions” (TMO) to keep things simple -for beginners. - -
- A cute image of a black flat coated retriever laying on the floor with a purple ice pack on it's head looking right at you. -
Whether it's too many options or too many opinions, TMO will push beginner users away. Too many options create cognitive load that prevent users from having successful experiences.
-
- - - -Giving a talk at PyConUS experience reinforced my belief in the power of community to tackle complex -problems and support each other in our scientific journeys. - - - -## The PyCon US maintainers Summit - -This year, I had the honor of helping [Inessa Pawson](https://github.com/InessaPawson) and [Kara Sowles](https://github.com/karasowles) organize the -[PyCon US Maintainers Summit 2024](https://us.pycon.org/2024/events/maintainers-summit/), an event that has grown from an experimental -“hatchery” to its 6th year at PyCon US. Last year, I had the [privilege of giving -a talk at the summit](https://www.youtube.com/watch?v=Qxy7bxW72iA). This year, I co-organized, learning the ropes of proposal -calls, speaker selection, and navigating a summit that filled a room of 80 -people within a conference boasting 2,700 registrations. The prep leading up to -the summit was manageable, but the days before and the day of were quite the -adventure! - -The summit featured amazing speakers, including: - -* [Leonard Richardson](https://www.linkedin.com/in/leonardr/), who shared -insights on maintaining core tools like [Beautiful Soup](https://pypi.org/project/beautifulsoup4/) for 20 years and tips on -handling burnout. -* [Jessica Tegner](https://github.com/JessicaTegner). Jessica became a maintainer of one of the top 1% of all downloaded packages on PyPI - [pypandoc](https://github.com/JessicaTegner/pypandoc). She talked about her experiences taking over a package with over 3 million monthly downloads. Oh and did I mention that Jessica does all this while being visually challenged? The backgrounds (and dedication) of the maintainers in our open source ecosystem always blows my mind. - -Working alongside Inessa Pawson, Kara Sowles and Chris Rose who are all also rockstars in the open source world was a fantastic -experience. Despite some logistical challenges, like the summit filling up -quickly and a waitlist, the event was a success. Next year (if we are invited back), will be even better as we try to open the doors to include aspiring maintainers and contributors! - -{% include video id="L-Ok_89QJOM" provider="youtube" %} - -Also if you were wondering, yes that Monstera Deliciosa (plant) in the background is real! AND YES it is ginormous! - -## Our second pyOpenSci sprint - -This year we had a tremendous turnout of over 20 people from several countries for our 1-day pyConUS sprint. At last count this resulted in about [30 issues and pull requests](https://github.com/orgs/pyOpenSci/projects/12/views/1) where volunteers who were there to support scientists, helped us fix issues on our website, in our tutorials and our [packaging](https://www.pyopensci.org/python-package-guide/) and [peer review](https://www.pyopensci.org/software-peer-review/index.html) guidebooks. So many long-standing issues and bugs were fixed thanks to this wonderful Python community. - -
- Image of the puycon us sprint board where projects signed up for different rooms. - Image of the puycon us sprint board where projects signed up for different rooms. -
The pyConUS sprint sign-up board had lots of projects. Because the rooms are large, projects tend to share spaces. We ended up in the packaging room which was great as it allowed us to do some more difficult work around Python packages with C extensions!
-
- -If you haven’t been to a sprint before, it’s an experience that every open source enthusiast should have. Sprints are where a bunch of people come together to work on a project. If you are running sprints that support software development in a professional environment (i.e. at a company) this might mean a team of people working together on releasing a new software feature. But for the open source community, sprints can also mean volunteers coming to a space to help maintainers work on various parts of a project that they care about - like pyOpenSci! - -pyOpenSci supports other people's software through it's [open community-lead peer review process](https://www.pyopensci.org/about-peer-review/index.html) and it's online, free [packaging resources](https://www.pyopensci.org/python-package-guide/). But it also has it's own software too. We have tools that help us keep track of our review process and volunteer contributions so we can acknowledge everyone for their effort. - -Acknowledging contributions is so so so (did I mention SO?) important. And we value them so much. - -
- Image of a round table of people talking and working at computers during our sprints. - -
We had people working at multiple tables on pyOpenSci issues. A handful of people worked through our Create Your First Python Package tutorials and provided input and found issues! Included in this image is Jeremiah Paige who is making our packaging guide so much better by adding actual package examples (including on that has C extensions!) using Hatch!
-
- -### How to run a sprint - -I've learned a lot about running sprints over the past 2 years. At pyOpenSci, we have a [help-wanted project board](https://github.com/orgs/pyOpenSci/projects/3) where we add issues and things that we could use help with across our organization. Some of the tasks that we need help with are beginner friendly. For example it's useful for beginners to test drive our [online packaging tutorials](https://www.pyopensci.org/python-package-guide/tutorials/intro.html) and then report bugs as GitHub issues. Your feedback might be errors in a lesson, typos or things that were confusing / didn’t work. - -Some of our help-wanted issues are more technical too. pyOpenSci is a non-profit organization whose heart is a community of volunteers. We have infrastructure that any other open source project would have, and as such we need technical help working on CI (continuous integration) workflows, guidebook infrastructure, our website and more. - -As an Executive Director and Founder of pyOpenSci, I created most of this infrastructure to support our mission myself, early on. This means that sometimes, its not the prettiest code . But it works. - -While I cherish times when I have bandwidth to work on technical things, it’s hard to keep up. So I really appreciate when community members can help us tick off open issues - big and small. - -## Our first pyOpenSci PyCon Open Space - -
- Image showing a room with round tables and people working at each table most of who have laptops sitting in front of them. -
I was new to leading open spaces but learned a lot for next year. We had 3 tables of people talking about different science related pain points in the Python ecosystem.
-
- -This year, we ran our first pyOpenSci Open Space, bringing together 17 attendees -from organizations like Anaconda, NVIDIA, CPython, PyPA, Million Concepts, -CapitalOne, Moderna, and MongoDB. Our discussions spanned interoperability, -scientific data workflows, and Python packaging for science. We focused on the -importance of consistent tools and data formats, reducing cognitive load for -scientists, and maintaining backwards compatibility. Key takeaways included the -need for shared data formats, simple reproducibility practices, and opinionated -packaging guides. - -A topic that came up a lot was the gap between exploratory code / scripts and -fully packaged / published code. There is a need for more "in between" options that support scientists sharing their code and code outputs. - -The discussions in this year’s open space gave pyOpenSci much to consider moving -forward. The complexity and diversity of tools in scientific computing can -create significant cognitive load. - -We need consistent and straightforward tools and -workflows that allow allowing scientists to focus on their research -rather than tools. We plan to tackle some of these and other topics during our Fall festival on September 28-29, -2024 so save the date and come ready to learn and share! - -## Packaging summit - -Last but not least, pyOpenSci had a strong presence at the PyConUS packaging summit this year. The summit was organization by [Pradyun Gedam](https://github.com/pradyunsg), [Jannis Leidel](https://github.com/jezdez), [CAM Gerlach](https://github.com/CAM-Gerlach), [Filipe Laínes](https://github.com/FFY00). As I have mentioned several times, packaging is one of the more thorny topics in our Python ecosystem. However, this year, things felt different compared to last. - -For one, there were a lot more people in the room, and people with different perspectives. For starters, last year I was 1 of 3? female-identifying people in the room - this year there were people from many backgrounds and identities in the room! Last year also felt more technical whereas this year was a perfect mix of discussing technical topics combined with a strong theme of considering user experience in both installing Python and creating packages. PLUS - documentation - yes PLEASE! - -The Executive Director of the Python Software Foundation was in the room and shared that there could be financial support in the future to make packaging better! - -I'm hopeful. - -From my perspective, the biggest challenges in our ecosystem revolve around: - -* too much focus on tools and not enough focus on user experience and documentation, and -* too many options and opinions that prevent users from have early success. - -There is a path forward. And people who are working on tools in the ecosystem really do care -- a lot. So please thank them - thank the maintainers and people who work on the tools that you use - or might use in the future. It's all volunteer time. - -A few other highlights for me included meeting Ofek, the maintainer of [Hatch](https://ofek.dev/projects/hatch/). It was a big deal for Ofek to attend this meeting in person! He also gave a talk on his end-to-end packaging tool Hatch. - -## Wrap up - -
- Image of Juno a black dog that looks like a flat coated retriever border collie mix with the PyCon US swag thank you bag and a pyOpenSci sticker on it with the name Leah Wasser Maintainers Summit. -
Image of Juno my pup with my PyConUS thank you gifts. PyCon provides letters and gifts for people who either give talks or lead / organize events. This year I did both and got a pint glass to remember it all - #rescuedogapproved.
-
- -I left pyCon US 2024 overwhelmed by the support that pyOpenSci received. People at pyCon generally care a lot and are invested in making scientists' experience in the Python ecosystem better. I also needed a week to recover. I am an introvert by nature so even the best days of interacting with friends makes me tired! - -I look forward to attending the meeting again next year - once again in Pittsburgh, Pennsylvania, USA. And I look forward to seeing my newly expanded Python family again too. - -See you there! diff --git a/_posts/2024-06-03-sleplet-slepian-wavelets.md b/_posts/2024-06-03-sleplet-slepian-wavelets.md deleted file mode 100644 index 8990a423..00000000 --- a/_posts/2024-06-03-sleplet-slepian-wavelets.md +++ /dev/null @@ -1,166 +0,0 @@ ---- -layout: single -title: "SLEPLET: Slepian Scale-Discretised Wavelets in Python" -blog_topic: software -excerpt: SLEPLET is a tool to create Slepian scale-discretised wavelets that has recently passed the PyOpenSci review. -author: Patrick J. Roddy -permalink: /blog/sleplet-slepian-wavelets.html -header: - overlay_image: images/blog/headers/sleptlet-header.png - caption: "Figure credit: [**The Planck Collaboration 2018**](https://www.aanda.org/articles/aa/full_html/2020/09/aa33881-18/F38.html)" -categories: - - blog-post - - manifolds - - signal-processing - - wavelets -comments: true -last_modified: 2024-08-29 ---- - -[SLEPLET](https://github.com/astro-informatics/sleplet) is a Python package for -the construction of Slepian wavelets in the spherical and manifold (via meshes) -settings. In contrast to other codes, `SLEPLET` handles any spherical region as -well as the general manifold setting. The API is documented and easily -extendible, designed in an object-orientated manner. Upon installation, -`SLEPLET` comes with two command line interfaces - `sphere` and `mesh` - that -allow one to easily generate plots on the sphere and a set of meshes using -[plotly](https://github.com/plotly/plotly.py). Whilst these scripts are the -primary intended use, `SLEPLET` may be used directly to generate the Slepian -coefficients in the spherical/manifold setting and use methods to convert these -into real space for visualisation or other intended purposes. The construction -of the sifting convolution was required to create Slepian wavelets. As a result, -there are also many examples of functions on the sphere in harmonic space -(rather than Slepian) that were used to demonstrate its effectiveness. `SLEPLET` -has been used in the development of several papers. - -## Background - -Wavelets are widely used in various disciplines to analyse signals both in space -and scale. Whilst many fields measure data on manifolds (i.e. the sphere), -often data are only observed on a partial region of the manifold. Wavelets are a -typical approach to data of this form, but the wavelet coefficients that overlap -with the boundary become contaminated and must be removed for accurate analysis. -Another approach is to estimate the region of missing data and to use existing -whole-manifold methods for analysis. However, both approaches introduce -uncertainty into any analysis. Slepian wavelets enable one to work directly with -only the data present, thus avoiding the problems discussed above. Applications -of Slepian wavelets to areas of research measuring data on the partial sphere -include gravitational/magnetic fields in geodesy, ground-based measurements in -astronomy, measurements of whole-planet properties in planetary science, -geomagnetism of the Earth, and cosmic microwave background analyses. - -## Statement of Need - -Many fields in science and engineering measure data that inherently live on -non-Euclidean geometries, such as the sphere. Techniques developed in the -Euclidean setting must be extended to other geometries. Due to recent interest -in geometric deep learning, analogues of Euclidean techniques must also handle -general manifolds or graphs. Often, data are only observed over partial regions -of manifolds, and thus standard whole-manifold techniques may not yield accurate -predictions. Slepian wavelets are designed for datasets like these. Slepian -wavelets are built upon the eigenfunctions of the Slepian concentration problem -of the manifold: a set of bandlimited functions that are maximally concentrated -within a given region. Wavelets are constructed through a tiling of the Slepian -harmonic line by leveraging the existing scale-discretised framework. Whilst -these wavelets were inspired by spherical datasets, like in cosmology, the -wavelet construction may be utilised for manifold or graph data. - -To the author's knowledge, there is no public software that allows one to -compute Slepian wavelets (or a similar approach) on the sphere or general -manifolds/meshes. [SHTools](https://github.com/SHTOOLS/SHTOOLS) is a `Python` -code used for spherical harmonic transforms, which allows one to compute the -Slepian functions of the spherical polar cap. A series of `MATLAB` scripts exist -in [slepian_alpha](https://github.com/csdms-contrib/slepian_alpha), which -permits the calculation of the Slepian functions on the sphere. However, these -scripts are very specialised and hard to generalise. - -Whilst Slepian wavelets may be trivially computed from a set of Slepian -functions, the computation of the spherical Slepian functions themselves are -computationally complex, where the matrix scales as 𝒪(𝐿⁴). Although symmetries -of this matrix and the spherical harmonics have been exploited, filling in this -matrix is inherently slow due to the many integrals performed. The matrix is -filled in parallel in `Python` using -[concurrent.futures](https://docs.python.org/3/library/concurrent.futures.html), -and the spherical harmonic transforms are computed in `C` using -[SSHT](https://github.com/astro-informatics/ssht). This may be sped up further -by utilising the new [ducc0](https://github.com/mreineck/ducc) backend for -`SSHT`, which may allow for a multithreaded solution. Ultimately, the -eigenproblem must be solved to compute the Slepian functions, requiring -sophisticated algorithms to balance speed and accuracy. Therefore, to work with -high-resolution data such as these, one requires high-performance computing -methods on supercomputers with massive memory and storage. To this end, Slepian -wavelets may be exploited at present at low resolutions, but further work is -required for them to be fully scalable. - -## Example Usage - -`SLEPLET` may be interacted with via the API or the CLIs. - -### API Usage - -The following demonstrates the first wavelet (ignoring the scaling function) of -the South America region on the sphere. - -```python -import sleplet - -B, J, J_MIN, L = 3, 0, 2, 128 - -region = sleplet.slepian.Region(mask_name="south_america") -f = sleplet.functions.SlepianWavelets(L, region=region, B=B, j_min=J_MIN, j=J) -f_sphere = sleplet.slepian_methods.slepian_inverse(f.coefficients, f.L, f.slepian) -sleplet.plotting.PlotSphere( - f_sphere, - f.L, - f"slepian_wavelets_south_america_{B}B_{J_MIN}jmin_{J_MIN+J}j_L{L}", - normalise=False, - region=f.region, -).execute() -``` - -![Slepian Wavelet j=2]({{ site.url }}/images/sleplet/slepian_wavelets_south_america_3B_2jmin_2j_L128_res512_real.png) - -### CLI Usage - -The demonstrates the first wavelet (ignoring the scaling function) of the head -region of a Homer Simpson mesh for a per-vertex normals field. - -```sh -mesh homer -e 3 2 0 -m slepian_wavelet_coefficients -u -z -``` - -![Slepian Mesh Wavelet Coefficients j=2]({{ site.url }}/images/sleplet/slepian_wavelet_coefficients_homer_3B_2jmin_2j_zoom.png) - -## Citing - -If you use `SLEPLET` in your research, please cite the paper. - -```bibtex -@article{Roddy2023, - title = {%raw%}{{SLEPLET: Slepian Scale-Discretised Wavelets in Python}}{%endraw%}, - author = {Roddy, Patrick J.}, - year = 2023, - journal = {Journal of Open Source Software}, - volume = 8, - number = 84, - pages = 5221, - doi = {10.21105/joss.05221}, -} -``` - -Please also cite [S2LET](https://doi.org/10.1051/0004-6361/201220729) upon which -`SLEPLET` is built, along with [SSHT](https://doi.org/10.1109/TSP.2011.2166394) -in the spherical setting or [libigl](https://doi.org/10.1145/3134472.3134497) in -the mesh setting. - -## Acknowledgements - -The author would like to thank Jason D. McEwen for his advice and guidance on -the mathematics behind `SLEPLET`. Further, the author would like to thank Zubair -Khalid for providing his `MATLAB` implementation to compute the Slepian -functions of a polar cap region, as well as the formulation for a limited -colatitude-longitude region. `SLEPLET` makes use of several libraries the author -would like to acknowledge, in particular, -[libigl](https://github.com/libigl/libigl-python-bindings), -[NumPy](https://github.com/numpy/numpy), `plotly`, `SSHT`, -[S2LET](https://github.com/astro-informatics/s2let). diff --git a/_posts/2024-06-05-friends-dont-let-friends-package-alone-pyconus-24.md b/_posts/2024-06-05-friends-dont-let-friends-package-alone-pyconus-24.md deleted file mode 100644 index a141f756..00000000 --- a/_posts/2024-06-05-friends-dont-let-friends-package-alone-pyconus-24.md +++ /dev/null @@ -1,287 +0,0 @@ ---- -layout: single -title: "Friends don't let friends package alone- my Python packaging talk at PyCon 2024" -blog_topic: education -excerpt: "A blog post from the Executive Director of pyOpenSci on Python packaging, technical things and imposter syndrome in data science." -author: "Leah Wasser" -permalink: /blog/python-packaging-friends-dont-let-friends-package-alone.html -header: - overlay_color: "#33205c" -categories: - - blog-post - - community -classes: wide -toc: true -comments: true -last_modified: 2024-08-29 ---- - -## Python packaging and community: my first talk at PyCon US 2024 - -This year I gave my first talk in the [PyCon US](https://us.pycon.org/2024/) main track! For context, PyCon US is a big meeting—with over 2,700 registrations this year—a record for the event! My talk was about my experiences navigating the packaging ecosystem from a few perspectives: - -* Teaching scientists how to share their code, -* As a maintainer of a smaller Python package—stravalib and -* Founder and Executive Director of a non profit organization - pyOpenSci - which strives to support scientists in tackling the world's greatest challenges using open software and workflows. - -The irony of these three different perspectives is that the challenges that users face in each category are similar! Below I explore my experiences pulling together the talk. - -You can also watch the talk here: - -{% include video id="oA6-f7PtTQ4" provider="youtube" %} - -### My early experiences with Python packaging - -I started thinking about Python packaging in 2018 during our [early pyOpenSci -community meetings](https://www.pyopensci.org/handbook/reference/index.html). In these meetings, we were trying to create an [open peer -review process for scientific Python software](https://www.pyopensci.org/about-peer-review/index.html). A process that is running today -with 17 scientific Python packages currently [in some state of review](https://github.com/pyOpenSci/software-submission/issues) as write -this post. - -As someone who was teaching Python to beginners and dabbling in creating -and maintaining packages to support teaching, packaging was this somewhat -undefined space. I wasn’t sure where to begin or what sources were truly -authoritative. Generally, my approach to packaging was copying the -infrastructure that other package maintainers I trusted used—mainly GeoPandas. - -*I am a spatial data nerd at heart.* - -
- Image that says  have you ever felt confused about python packaging. Below is the pyopensci flower logo. And the graphic shows a bunch of different cartoon like hands being raised. -
Every time I ask this question, a majority of people in the room raise their hand.
-
- -But as I thought about creating pyOpenSci's open peer review process, I knew -scientists would need a process that: - -* Enforced “good enough” practices in scientific Python packaging -* Was not overly-binding, but also set users up for success - -I also knew that: - -* We needed to create some sort of packaging guide that set up expectations for our pyOpenSci open peer review of software process, and that -* I wasn't the lead expert in the room. Thus, we needed people from across the (scientific) Python ecosystem and beyond to build consensus and define what -were “good enough” practices. - -But how could pyOpenSci in these early years, as a not-yet-created community, empower people to -navigate a tricky, if not complex, ecosystem empowered by choice? - -## Choice is a value in the Python ecosystem - -In the Python ecosystem, choice is a value. - -Why? - -Because the Python language is used across many different domains and for lots of different reasons. And further, Python is a “glue” language. This means that it can wrap around other languages like C—making it even more flexible. - -This flexibility warrants choice. It’s almost by design. - -And I value choice too. - -## Too much of a good thing? Too many choices? - -**Choice is great but it's not necessarily beginner friendly.** Choice leaves too much room for uncertainty as a beginner. And uncertainty just makes learning more stressful. - -As my mentor and colleague Carol likes to say: - -> choice is an opportunity for someone to make the wrong decision - -So if you think about that "opportunity to make a wrong decision", imagine what happens if you have multiple choices that lead you down a terrifying path of uncertainty! - -
- Image of a flat coated retriever lying down with a purple ice bag on its head looking sad. -
Too many choices can lead to frustration for many users that are learning a new technical thing. More biscuits—or early wins—are more empowering for a learner and set them up for success.
-
- -### The evolution of the pyOpenSci Python packaging guide - -Years later, what started out as a generic Python development guide, was -transformed into what’s now the [pyOpenSci Python package guide](https://www.pyopensci.org/python-package-guide/). - -* A guidebook that has 58 contributors as I’m writing this post today. -* A guidebook that has color and graphics and that leads Pythonistas down a - singular, opinionated path to Python packaging success. -* A guidebook that breaks down pieces and processes into their fundamentals and - explains them using digestible words. - -The pyOpenSci path is not the only path. It is just a path that works and -enables early wins for scientists and others using Python. - -If you're reading this and you have your own path, that's OK too. But if you -want to worry less about decisions and have a definitive way of doing things -that follows modern standards - - -we've got you. - -### A packaging guidebook created by community - -This guidebook was created by a community of people with beginner to expert packaging experience with the goal of building consensus -around a single approach to pure Python packaging that would **yield user success**. - -Together we created a guidebook that acknowledges a suite of fantastic tools across the ecosystem, while -still leading users down a streamlined path. - -
- Image with the pyOpenSci flower logo in the upper right hand corner. The image shows the packaging lifecycle. The graphic shows a high level overview of the elements of a Python package. the inside circle has 5 items - user documentation, code/api, test suite, contributor documentation, project metadata / license / readme. In the middle of the circle is says maintainers and has a small icon with people. on the outside circle there is an arrow and it says infrastructure. -
The generalized scientific Python package development process. .
-
- -## Getting accepted to talk about packaging... - -I was thrilled and terrified to be accepted to give a talk at [PyCon US](https://us.pycon.org/2024/), in Pittsburgh, Pennsylvania, on the process of creating the pyOpenSci Python packaging guidebook . My talk was called: - -“Friends don’t let friends package alone” - -It was all about the process of navigating the Python packaging ecosystem and -then creating the guide. While you might assume such a talk about be technical, -in reality, it was all about people. - -### Me and the child-like imposter inside - -Giving this talk was a big deal for me. Imposter syndrome—the feeling that I -didn't belong, that I wasn't technical enough to build pyOpenSci—has always been -my reality. As a trained Landscape Architect, I spent most of my college and early graduate years -practicing perfect lettering, using markers, fancy pens and colored pencils to bring creative -landscape visions to life. I think moved on to get an advanced degree in ecology. - -I remember vividly almost crying when I tried to read and understand my first scientific paper. - -...talk about jargon and technical terms! - -Now, I consider myself a Pythonista, coder, data scientist, and tech -geek. I've bounced around across different data science languages. - -AND, I’m very aware of how much more others know than I do about lots of -different topics—including packaging. - -I wasn’t sure what it would be like to be on that big pyCon US presentation stage, in that big room full of chairs. Would they be empty? Luckily, I was able to focus on my friends sitting in the front rows. - -Carol, Hugo, Jeremiah, and Inessa—they were all there. - -Carol told me to lean into the teacher inside of me. I know that teacher best. My body calmed as I remembered why I was there. To help the community move forward. - -And similar to how we created our packaging guide—my friends got me through it. - -## The core messages in my talk - -In my talk, I shared my experiences as a maintainer of [stravalib](https://github.com/stravalib/stravalib). How -challenging it was to figure out the best packaging infrastructure for our team. -However, that experience of rebuilding stravalib also empowered me to better understand the pain points -in the ecosystem from a "beginner and real world application lens." From a lens of "I just need to create a -package and publish / release it to PyPI regularly." - -From a lens of—I am technical but I need to learn new-to-me packaging tools -and workflows. - -
- Image of a stressed out looing stick figure typing at a computer. On the left is says Maintainers Perspective. -
Even as technical maintainers, the folks on the stravalib team weren't packaging experts. Why would they be? They are focused on writing good usable code (and tests). .
-
- -## Fundamental first—How to make a technical ecosystem beginner accessible - -How do we collectively support and empower people who don't want to become -packaging gurus to be successful? - -My approach to helping beginners tackle hard technical problems has always been -about breaking things down into their simplest forms first. - -I thrive on those “ah ha” moments when a tough concept clicks. - -And someone gets it. - -Sometimes, that someone is me — before I can teach the topic to others! - -## So what about packaging with friends? - -The essence of my Python packaging talk was the work that I have lead alongside the pyOpenSci community to demystify and help *people* navigate the complex Python packaging ecosystem. - -This work was really about people helping people tackle challenging things. - -Python packaging matters because scientists need open software to create open and reproducible workflows to build upon each other's work, and to tackle the world's hardest challenges more efficiently and effectively. - -Among other things, pyOpenSci reviews software as a constructive way to help scientists both create better software and get credit for the important work they are doing. - -The pyOpenSci community has created a Python package guide that harnesses the expertise, opinions, and experiences of 61+ people. - -This guide provides: - -* A beginner-friendly overview of the packaging ecosystem combined with a -* Start-to-finish tutorial that uses [Hatch](https://hatch.pypa.io/) to create a Python package. - -Python is known for its incredible community, so it only made sense to leverage that collective knowledge and experience to create a packaging guide. Packaging doesn’t have to be complex. Friends can help. - -The talk can be broken down into 4 takeaways: - -### People can help (*surprise*!) - -When tackling some of the most thorny technical Python problems, people can help. This might mean: - -* Learning together by working in a small supportive group to create your first Python package; -* Bringing in experts to review and vet online resources to ensure they are accurate and accessible that support scientists worldwide in their packaging journey. -* Also including beginners to ensure the content is accessible, understandable and usable. -* Running a constructive peer review process where people volunteer their time to provide feedback on a package’s usability, function, and structure. - -In creating the guide, we knew we were tackling hard, complex technical problems. But also there is a tremendous amount of collective knowledge in this community that can be harnessed and focused on the problem at hand. By focusing people on the users of the guide first, rather than tools, we were able to build consensus. - -We were able to move forward together. - -
- Image on a dark purple background that says—people can help. below are two people stick figure like loking at each other and smiling while chatting. -
When tackling Python's thorniest challenges, people can help. .
-
- -### Create early wins for users - -As an educator, I’ve always known that the best way to empower someone new to any topic is to create a learning environment where they quickly make progress. I’ve [collected data on what approaches work better than others](https://osf.io/preprints/osf/xdj4z). I’ve explored how certain presentations of information empower, while others break people down, triggering imposter syndrome and even isolation within a science field. - -The key is early wins. - -Early wins empower users to: - -* Want to do more -* Feel like they belong and can be successful learning the skill at hand -* Try again and expand their knowledge -* Even help their colleagues, which is the biggest win of all. - -High quality documentation (or tutorials) helps users achieve this by providing quickstarts and resources that ensure they can get up and running quickly and have early positive experiences. - -
- Image on a dark purple background that says Create Early Wins in your docs. on the left is a stressed out stick figure typing at a computer. on the right is a relaxed stick figure typing away. -
By creating get-start workflows, tutorials and resources that users can easily reproduce and make progress using quickly, you are empowering beginners to stick with the task at hand. And you are building user confidence in the ecosystem. .
-
- -### Fundamentals First - -When teaching beginners, break things down to their fundamental concepts. In the case of packaging, this means explaining pure Python packaging in its most basic form, without immediately discussing tools. Notice that the diagram below doesn’t discuss tools at all! - -
- Dark purple image that says Fundamentals First. Below are 4 steps in the packaging process that say 1 create package structure, 2 add code, 3 add metadata to pyproject toml file and 4 pip install package. -
By breaking down packaging in to it's most basic steps you are making the concepts immediately more digestable to users. Once they understand the basics you can slowly layer in additional complexity. .
-
- -While some may think that this approach is over simplified, it’s not. The idea here is that you begin with digestible information that empowers a learner. You help them understand that they can do this! And then, and only then, you add more complexity when they are ready to absorb it. - -This also opens the door wide to early wins. - -### Just say no to TMO - -Avoid TMO—Too Many Options or Too Many Opinions at all costs when teaching beginners. Options are great, but when someone is new to a topic, it’s best to give them a straightforward path to success. - -Early on in creating our packaging guide, we found contributors, often quite expert in the packaging ecosystem, arguing about the best tools and best practices. Since we invited many people with different levels of experience to review our content, these arguments pushed some people away and created an unhealthy environment. Moderating these conversations to focus on the users and their needs, rather than debating tools, was critical. This approach helped create a truly beginner-friendly guide and fostered a review environment that was safe and inclusive, where both beginners and experts could contribute. - -
- Dark purple background image that says no TMO. Below is a yellow lab retriever with a purple ice bag on it's head looking sad and directly at you. -
When it comes to packaging, too many options and too many opinions get in the way of a positive user experience. We need to work towards breaking things down and providing opinionated paths for those who want and need them. .
-
- -### Friends don’t let friends package alone - -When I finished my talk, I felt this sense of relief but also I felt love, support and deep passion for this topic and the community. It’s more than just packaging. It is about empowering people with skills that will help them tackle the biggest scientific challenges that we face as humans. Science is about data. And to process data we need tools - open tools that are accessible for scientists to learn and use. It’s about carving out space for scientists to build and use these tools. And also to make space for people who may feel like outsiders, because of who they are, to participate. - -I’m excited to have the privilege of moving the pyOpenSci mission forward, and of getting to connect with so many amazing people. I’m excited to continue to facilitate and support better science through better software and to empower more people with skills. And maybe, through this talk, a little piece of that child-like imposter within me has been chipped away! - -I’m human too. I need friends too. - - diff --git a/_posts/2024-06-06-get-involved-with-pyos.md b/_posts/2024-06-06-get-involved-with-pyos.md deleted file mode 100644 index 1fe033cd..00000000 --- a/_posts/2024-06-06-get-involved-with-pyos.md +++ /dev/null @@ -1,92 +0,0 @@ ---- -layout: single -title: "Get Involved with pyOpenSci" -blog_topic: community -excerpt: "Fun fact: there are a multitude of ways for you to get involved with pyOpenSci! From social media to in-person events to joining our editoral team, this guide walks you through every opportunity available." -author: "Jesse Mostipak" -permalink: /blog/get-involved-with-pyos.html -header: - overlay_color: "#33205c" -categories: - - blog-post - - community -classes: wide -toc: true -comments: true -last_modified: 2024-08-29 ---- -## The pyOpenSci mission -Founded in 2018 by our Executive Director, [Leah Wasser](https://github.com/lwasser), pyOpenSci builds diverse community that supports free and open Python tools for processing scientific data. We also build technical skills needed to contribute to open source and that support open science. While a diverse, inclusive community is at our core, radiating out from it are the three petals–how we accomplish our community goals–of pyOpenSci. These are: - -1. [Open peer review of scientific software](https://www.pyopensci.org/about-peer-review/index.html) -2. [Community partnerships](https://www.pyopensci.org/partners.html) -3. [Training and open educational resources](https://www.pyopensci.org/blog/pyos-education-announcement.html) - -## Promote pyOpenSci online and in-person -pyOpenSci is active on both [LinkedIn](https://www.linkedin.com/company/pyopensci) and [Fosstodon](https://fosstodon.org/@pyOpenSci), and you’re welcome to tag us on social media, as well as to [reach out with questions](mailto:media@pyopensci.org)! We also have [a series of brand assets and guidelines](https://docs.google.com/presentation/d/1glwf3BEPxo5H6pOHp6d55r2sbw8x-8kQZ9GzoLXwEDc/edit?usp=drive_link) if you need a copy of our logo, information on our fonts, and/or access to various illustrations we use. - -Are you attending a large, recognized, Python meeting that we will be unable to attend? Reach out to us! Dependent on timing, cost, and location, we’d also be more than happy to send you a set of pyOpenSci stickers to share! You can email us at [media@pyopensci.org](mailto:media@pyopensci.org) to inquire about the event you'll be attending. - -Please feel free to use any information from [our website](https://www.pyopensci.org/) to share about the work pyOpenSci is doing, and to reach out via [email](mailto:media@pyopensci.org) or on [LinkedIn](https://linkedin.com/company/pyopensci -) or [Fosstodon](https://fosstodon.org/@pyOpenSci) with any questions. And don’t forget to share your personal experiences with pyOpenSci as well! - -## Volunteer with pyOpenSci -If you're looking to be more hands-on with pyOpenSci, consider joining our [Review Team](https://www.pyopensci.org/software-peer-review/how-to/reviewer-guide.html) or [Editorial Board](https://www.pyopensci.org/software-peer-review/how-to/editors-guide.html), digging into some code and working from our Help Wanted board, or writing a guest blog post! - -### pyOpenSci's Review Team -pyOpenSci welcomes reviewers from a diversity of backgrounds and with varying levels of Python technical expertise. Some of the basic things that we look for in a reviewer include: - -* Familiarity with using the Python programming language. -* Ability to evaluate a Python package for usability and documentation quality. -* Ability to provide a technical review of Python package structure and code quality/approach to solving the problems that the package seeks to address. - -We like to have a mix of technical and usability focus in our reviews so it’s ok if you don’t have all of the above skills! Reviewing code is an important skill, especially if you are working in a collaborative team environment (professionally or in academia). And if you are interested in peer review but have never reviewed before, we offer a [mentorship program](https://www.pyopensci.org/software-peer-review/how-to/reviewer-guide.html#a-guide-for-new-reviewers), where we will pair you up with someone who has more experience reviewing code. From this experience you can learn more and empower yourself with code review skills. Software review skills are generally useful in data science, so they are skills worth investing in! - -To join our review team, [fill out our reviewer form](https://docs.google.com/forms/d/e/1FAIpQLSeVf-L_1-jYeO84OvEE8UemEoCmIiD5ddP_aO8S90vb7srADQ/viewform). We will contact you if we have a package that we need reviewers for. It’s OK if you’ve never reviewed a package before! We’ll walk you through it. - -### pyOpenSci's Editorial Team -The pyOpenSci [Editorial Board](https://www.pyopensci.org/about-peer-review/index.html#meet-our-editorial-board) is comprised of a diverse group of volunteers, with each editor being responsible for the following tasks: - -* Finding reviewers from diverse backgrounds who have a mixture of scientific domain and Python experience. -* Overseeing the entire review process for a package ensuring it runs in a timely and efficient manner. -* Supporting the submitting authors and reviewers in answering questions related to the review. -* Determining whether that package should be accepted into the pyOpenSci ecosystem once the review has wrapped up. - -It's critical to our mission that our Editorial Board have a combined expertise in various scientific domains, technical expertise in Python packaging, awareness of the importance of documentation in package usability, and awareness of the importance of CI/test suites in order to ensure robust software development. We do not expect any single editor to be an expert in all of these areas! - -### Apply to be a pyOpenSci editor -As of May 2024, pyOpenSci looking for editors from a wide variety of scientific backgrounds due to an increased number of package submissions. If you feel you'd be a good fit for the pyOpenSci Editorial Board, please fill out [this form](https://docs.google.com/forms/d/e/1FAIpQLScRQHQ7NKVEAG3BKAphiUdVFvQ5nkez0IpyXBMZDzXjuBPloQ/viewform). We're looking to fill several volunteer spots, and will be accepting applications through June and July. - -If you're interested in joining our Editorial Board, but have questions or would like to chat with someone from pyOpenSci about volunteering to be an editor, please email us at [media@pyopensci.org](mailto:media@pyopensci.org). - -### Contribute to pyOpenSci -do you want to contribute to an open source project that is supporting other open science projects? We need your help! Check out our [GitHub Help Wanted board](https://github.com/orgs/pyOpenSci/projects/3), where we have a wide variety of issues, ranging from highly technical to beginner-friendly. The status column makes it easy to identify the issue category, and the label column helps to further clarify any needs around the issue. - -All contributors are credited and recognized on the [pyOpenSci website](https://www.pyopensci.org/). - -### Write a guest blog post for pyOpenSci -We are always looking for guest bloggers on [the pyOpenSci blog](https://www.pyopensci.org/blog/index.html)! If you’d like to write about a pyOpenSci package that has been accepted into our ecosystem, your experiences with pyOpenSci, or how you’re using free and open Python tools in your scientific endeavors, we’d love to hear from you! Email our Community team at [media@pyopensci.org](mailto:media@pyopensci.org) for more information. - -## Submit a package to pyOpenSci -pyOpenSci’s suite of packages are contributed by community members with a great diversity of skills and backgrounds. This diversity of developer backgrounds enables us to vet and promote a broad ecosystem of high quality tools that supports scientists across domains with a suite of different data types and structures. - -pyOpenSci packages are reviewed for quality, fit, scope, documentation and usability. The review process is similar to a manuscript review, however it has a stronger focus on Python packaging best practices. - -Unlike a manuscript review, our peer review process is an ongoing conversation. Once all major issues and questions are addressed, the review editor package will make a decision to accept, hold, or reject the package. - -Rejections are usually done early in the process, before the review process begins. In rare cases a package may also not be on-boarded into the pyOpenSci ecosystem after review & revision. - -To submit a package to pyOpenSci, [open an issue in our peer review GitHub repository](https://github.com/pyOpenSci/software-submission/issues/new/choose). Not sure if your package is in-scope? Then submit a [pre-submission inquiry](https://github.com/pyOpenSci/software-submission/issues/new/choose), and an editor will be in touch! - -Learn more about our Open Peer Review process in our [Peer Review Guide!](https://www.pyopensci.org/software-peer-review/about/intro.html) - -## Run a pyOpenSci sprint -Consider hosting a sprint, which is a time to come together with colleagues to collaborate on contributing to pyOpenSci. - -Get in touch with us at least two weeks in advance so we know when the sprint is happening, and can support you in holding it! You can contact us at [media@pyopensci.org](mailto:media@pyopensci.org). - -We'll be sharing more information in a separate post with more details on how to run a pyOpenSci sprint, either online or in-person! - -## Connect with pyOpenSci -​​We are thrilled, honored, and humbled that you’re interested in getting involved with pyOpenSci, and created this guide with the intention of providing a resource on all the ways available to get involved with us. You can stay up-to-date with all things pyOpenSci by following us on [LinkedIn](https://linkedin.com/company/pyopensci -) and [Fosstodon](https://fosstodon.org/@pyOpenSci). And if you’re interested in our weekly newsletter where we share news, blog posts, and monthly updates, [subscribe on LinkedIn](https://www.bit.ly/pyOSNewsletter). diff --git a/_posts/2024-06-20-pyos-guide-to-scipy.md b/_posts/2024-06-20-pyos-guide-to-scipy.md deleted file mode 100644 index 7222efc4..00000000 --- a/_posts/2024-06-20-pyos-guide-to-scipy.md +++ /dev/null @@ -1,85 +0,0 @@ ---- -layout: single -title: "pyOpenSci's Guide to SciPy" -blog_topic: education -excerpt: "We've got another incredible conference on the horizon, and wanted to share the talks, workshops, and sprints where you can find members of the pyOpenSci community!" -author: "Jesse Mostipak" -permalink: /blog/pyos-guide-to-scipy-2024.html -header: - overlay_color: "#33205c" -categories: - - blog-post - - community -classes: wide -toc: true -comments: true -last_modified: 2024-08-29 ---- -## See you at SciPy! - -[SciPy 2024](https://www.scipy2024.scipy.org/) is just around the corner. We can’t wait to see you there! We’re pulling together our pyOpenSci Guide to SciPy, similar to the guide we did for [PyCon 2024](https://www.pyopensci.org/blog/pyos-guide-to-pyconus-2024.html), and wanted to give you a preview of some of the tutorials and talks being given by pyOpenSci Community members! - -## Tutorials - -### [Interactive data visualizations with Bokeh (in 2024)](https://cfp.scipy.org/2024/talk/JRLMLD/) -Along with Timo Metzger and Bryan Van de Ven, pyOpenSci community member [Pavithra Eswaramoorthy](https://www.linkedin.com/in/pavithraes/) will be covering everything you need to know to create beautiful and powerful interactive plots from scratch using Bokeh’s latest features. Starting with a quick introduction of Bokeh’s core concepts, the team will cover creating and customizing simple static plots like line and bar charts before introducing layers of interactivity, creating specialized plotting features like geographic maps, contour plots, Mathematical Text, and discussing new additions to Bokeh like ImageStacks. By the end, you will be able to create a complete interactive dashboard using Bokeh. - -### [3D Visualization with PyVista](https://cfp.scipy.org/2024/talk/GKGRWE/) -Led by Tetsuo Koyama, Alexander Kaszynski, Bill Little, and pyOpenSci peer review spatial editor [Bane Sullivan](https://www.linkedin.com/in/bane-sullivan/), this tutorial demonstrates [PyVista’s](https://github.com/pyvista/pyvista) latest capabilities and bring a wide range of users to the forefront of 3D visualization in Python, including: -* Use PyVista to create 3D visualizations from a variety of datasets in common formats. -* Overview the classes and data structures of PyVista with real-world examples. -* Be familiar of the various filters and features of PyVista. -* Know which Python libraries are used and can be used by PyVista (meshio, trimesh etc). -* We see this tutorial catering to anyone who wants to visualize data in any domain, and this ranges from basic Python users to advanced power users. - -### [From RAGS to riches: Build an AI document inquiry web-app](https://cfp.scipy.org/2024/talk/W3ZJWG/) -Pavithra Eswaramoorthy, a Developer Advocate at [Quansight](https://quansight.com/), is teaming up with Dharhas Pothina and Andrew Huang to cover how to use RAG to build document-inquiry chat systems using different commercial and locally running LLMs. The topics we’ll cover include: -* **Introduction to RAG**, how it works and interacts with LLMs, and Ragna - a framework for RAG orchestration -* **Creating and optimizing a basic chat function** that uses popular LLMs (like GPT) answers questions about your documents, using a Python API in Jupyter Notebooks -* **Running a local LLM on GPUs** on the provided platform, and comparing its performance to commercial LLMs -* **Introduction to Panel**, by creating a basic chat UI for Ragna using Panel’s ChatBox widget -* **Building and deploying a Panel-based web-app**, which extends the basic chat UI and includes more application components - -By the end of this tutorial, you will have an understanding of the fundamental components that form a RAG model, and practical knowledge of open source tools that can help you or your organization explore and build on your own applications. This tutorial is designed to enable enthusiasts in our community to explore an interesting topic using some beginner-friendly Python libraries. - -By the end of this tutorial, you will have an understanding of the fundamental components that form a RAG model, and practical knowledge of open source tools that can help you or your organization explore and build on your own applications. This tutorial is designed to enable enthusiasts in our community to explore an interesting topic using some beginner-friendly Python libraries. - -### [Create Your First Python Package: Make Your Python Code Easier to Share and Use](https://cfp.scipy.org/2024/talk/QT9GBY/) -Led by pyOpenSci’s Executive Director and Founder, [Leah Wasser](https://github.com/lwasser), along with pyOpenSci editor Isabel Zimmerman and Jeremiah Page, who leads work on the [pyOpenSci packaging guide](https://www.pyopensci.org/python-package-guide/index.html), this tutorial session will teach you how to: -* Create code that can be installed into different environments -* Use [Hatch](https://hatch.pypa.io/latest/) as a workflow tool, making setup and installation of your code easier -* Use Hatch to publish your package to (test) PyPI - -Creating code that can be shared and reused is the gold-standard of open science. But tools and skills to share your code can be tricky to learn. In this hands-on tutorial, you’ll learn how to turn your code into an installable Python module ( a file containing Python code that defines functions, classes, and variables), that can be shared with others. We will provide pre-built code and module examples for each step of the tutorial to make it more beginner-friendly. However, to get the most out of this tutorial, you should be familiar with writing Python code, writing and using functions and you should understand the concept of Python environments. You should also have Python and Hatch installed on your computer prior to attending. If you are newer to environments and don’t have a preferred tool, then we recommend that you use conda. However, any environment tool that you prefer works well. - -> Interested in helping out with our Packaging workshop? Reach out to us at [media@pyopensci.org](mailto:media@pyopensci.org). We’re looking for volunteers to assist learners throughout the workshop. - -### [Data of an Unusual Size (2024 edition): A practical guide to analysis and interactive visualization of massive datasets](https://cfp.scipy.org/2024/talk/UKLNLQ/) - -Pavithra Eswaramoorthy and Dharhas Pothina, both from [Quansight](https://quansight.com/), will help you learn the fundamentals of analyzing massive datasets with real-world examples on actual powerful machines on a public cloud provided by the presenters – starting from how the data is stored and read, to how it is processed and visualized. - -## Talks - -### [The power of community in solving scientific Python’s most challenging problems](https://cfp.scipy.org/2024/talk/AMTLJ7/) -This talk from our Executive Director and Founder, [Leah Wasser](https://github.com/lwasser), shares pyOpenSci’s knowledge in building a constructive open peer review process that supports maintainers, and also resources that make Python packaging more accessible and easier-to-navigate. Our insights are based on five years of experience working with over 200 community members, who have evaluated 58 packages developed by over 56 package maintainers. - -> Leah’s talk is about writing the [pyOpenSci Python Packaging Guide](https://www.pyopensci.org/python-package-guide/) via community! - -### [Great Tables for Everyone](https://cfp.scipy.org/2024/talk/BRMQRW/) -This talk from [Richard Iannone](https://www.linkedin.com/in/richard-iannone-a5640017/), whose package [Great Tables](https://posit-dev.github.io/great-tables/articles/intro.html) is currently [under review with pyOpenSci](https://github.com/pyOpenSci/software-submission/issues/202), will provide a demonstration of Great Tables, showing how data can come alive in when in tabular form. We’ll use data from the fields of meteorology, chemistry, and the atmospheric sciences within our table-making examples. We’ll learn how the different components of a table (e.g., the column labels, the header, etc.) can be manipulated to best convey the data. We believe tables are worthy of being deemed data visualizations and we hope that Great Tables can become an indispensable tool in your data analysis workflow. - -### [From Code to Clarity: Using Quarto for Python Documentation](https://cfp.scipy.org/2024/talk/GFGSTS/) -[Isabel Zimmerman](https://www.linkedin.com/in/isabel-zimmerman/), a member of our triage team, editor, and an Emeritus Editor in Chief, will be speaking from her experiences as an open source developer who works with multilingual tools, and how Quarto + quartodoc helped to fill those gaps. This talk is intended for anyone who might have to communicate with stakeholders through code using a web-based format. She’ll be focusing on Python package documentation websites, but the stories and examples expose the difficulties in many types of technical communication. The intent is to have a lighthearted talk filled with examples of how to make technical communication and publishing easier and more accessible. - -### [Vector space embeddings and data maps for cyber defense](https://cfp.scipy.org/2024/talk/STUXTH/) -pyOpenSci community member [Benoit Hamelin](https://www.linkedin.com/in/benoit-hamelin-43a44668/) will cover how the vast amounts of information of interest to cyber defense organizations comes in the form of unstructured data; from host-based telemetry and malware binaries, to phishing emails and network packet sequences. All of this data is extremely challenging to analyze. In recent years there have been huge advances in the methodology for converting unstructured media into vectors. However, leveraging such techniques for cyber defense data remains a challenge. -Imposing structure on unstructured data allows us to leverage powerful data science and machine learning tools. Structure can be imposed in multiple ways, but vector space representations, with a meaningful distance measure, have proven to be one of the most fruitful. -In this talk, he will demonstrate a number of techniques for embedding cyber defense data into vector spaces, as well as discuss how to leverage manifold learning techniques, clustering, and interactive data visualization to broaden our understanding of the data and enrich it with expert feedback. - -## Sprints -We’ll also be holding a pyOpenSci sprint at SciPy 2024! Be sure to follow us on [LinkedIn](https://www.linkedin.com/company/pyopensci) and [Fosstodon](https://fosstodon.org/@pyOpenSci) for more details! - -## Connect with pyOpenSci - -Stay up-to-date with all things pyOpenSci by following us on [LinkedIn](https://linkedin.com/company/pyopensci -) and [Fosstodon](https://fosstodon.org/@pyOpenSci). If you’re interested in our weekly newsletter where we share news, blog posts, and monthly updates, [subscribe on LinkedIn](https://www.bit.ly/pyOSNewsletter). diff --git a/_posts/2024-07-10-automata.md b/_posts/2024-07-10-automata.md deleted file mode 100644 index 734e195e..00000000 --- a/_posts/2024-07-10-automata.md +++ /dev/null @@ -1,127 +0,0 @@ ---- -layout: single -title: "automata: Simulation and manipulation" -blog_topic: software -excerpt: automata is a package implementing structures and algorithms for manipulating finite automata, pushdown automata, and Turing machines, that was recently accepted into the pyOpenSci ecosystem. -author: Eliot W. Robson -permalink: /blog/automata.html -header: - overlay_image: /images/blog/headers/automata-header.png -categories: - - blog-post - - automata - - formal-languages - - models-of-computation - - pyos-accepted -classes: wide -comments: true -last_modified: 2024-08-29 ---- - - -Automata are abstract machines used to represent models of computation, and are a central object of study in theoretical computer science. Given an input string of characters over a fixed alphabet, these machines either accept or reject the string. A language corresponding to an automaton is -the set of all strings it accepts. Three important families of automata in increasing order of generality are the following: - -1. Finite-state automata -2. Pushdown automata -3. Turing machines - -The [`automata`](https://caleb531.github.io/automata/) package facilitates working with these families by allowing simulation of reading input and higher-level manipulation -of the corresponding languages using specialized algorithms. For an overview on automata theory, see [this Wikipedia article](https://en.wikipedia.org/wiki/Automata_theory), and -for a more comprehensive introduction to each of these topics, see [these lecture notes](https://jeffe.cs.illinois.edu/teaching/algorithms/#models). - -## Statement of need - -Automata are a core component of both computer science education and research, seeing further theoretical work -and applications in a wide variety of areas such as computational biology and networking. -Consequently, the manipulation of automata with software packages has seen significant attention from -researchers in the past. The similarly named Mathematica package [`Automata`](https://www.cs.cmu.edu/~sutner/automata.html) implements a number of -algorithms for use with finite-state automata, including regular expression conversion and binary set operations. -In Java, the [Brics package](https://www.brics.dk/automaton/) implements similar algorithms, while the [JFLAP package](https://www.jflap.org/) places an emphasis -on interactivity and simulation of more general families of automata. - -[`automata`](https://caleb531.github.io/automata/) serves the demand for such a package in the Python software ecosystem, implementing algorithms and allowing for -simulation of automata in a manner comparable to the packages described previously. As a popular high-level language, Python enables -significant flexibility and ease of use that directly benefits many users. The package includes a comprehensive test suite, -support for modern language features (including type annotations), and has a large number of different automata, -meeting the demands of users across a wide variety of use cases. In particular, the target audience -is both researchers that wish to manipulate automata, and for those in educational contexts to reinforce understanding about how these -models of computation function. - -## Package features - -The API of the package is designed to mimic the formal mathematical description of each automaton using built-in Python data structures -(such as sets and dicts). This is for ease of use by those that are unfamiliar with these models of computation, while also providing performance -suitable for tasks arising in research. In particular, algorithms in the package have been written for tackling -performance on large inputs, incorporating optimizations such as only exploring the reachable set of states -in the construction of a new finite-state automaton. The package also has native display integration with Jupyter -notebooks, enabling easy visualization that allows students to interact with [`automata`](https://caleb531.github.io/automata/) in an exploratory manner. - -Of note are some commonly used and technical algorithms implemented in the package for finite-state automata: - -- An optimized version of the Hopcroft-Karp algorithm to determine whether two deterministic finite automata (DFA) are equivalent. - -- The product construction algorithm for binary set operations (union, intersection, etc.) on the languages corresponding to two input DFAs. - -- Thompson's algorithm for converting regular expressions to equivalent nondeterministic finite automata (NFA). - -- Hopcroft's algorithm for DFA minimization. - -- A specialized algorithm for directly constructing a state-minimal DFA accepting a given finite language. - -- A specialized algorithm for directly constructing a minimal DFA recognizing strings containing a given substring. - -To the authors' knowledge, this is the only Python package implementing all of the automata manipulation algorithms stated above. - -## Example usage - -![A visualization of `target_words_dfa`. Transitions on characters leading to immediate rejections are omitted.]({{ site.url }}/images/automata/finite_language_dfa.png) - -The following example is inspired by the use case described in [this blog post](http://blog.notdot.net/2010/07/Damn-Cool-Algorithms-Levenshtein-Automata). -We wish to determine which strings in a given set are within the target edit distance -to a reference string. We will first initialize a DFA corresponding to a fixed set of target words -over the alphabet of all lowercase ascii characters. - -```python -from automata.fa.dfa import DFA -from automata.fa.nfa import NFA -import string - -target_words_dfa = DFA.from_finite_language( - input_symbols=set(string.ascii_lowercase), - language={'these', 'are', 'target', 'words', 'them', 'those'}, -) -``` - -Next, we construct an NFA recognizing all strings within a target edit distance of a fixed -reference string, and then immediately convert this to an equivalent DFA. The package provides -builtin functions to make this construction easy, and we use the same alphabet as the DFA that was just created. - -```python -words_within_edit_distance_dfa = DFA.from_nfa( - NFA.edit_distance( - input_symbols=set(string.ascii_lowercase), - reference_str='they', - max_edit_distance=2, - ) -) -``` - -Finally, we take the intersection of the two DFAs we have constructed and read all of -the words in the output DFA into a list. The library makes this straightforward and idiomatic. - -```python -found_words_dfa = target_words_dfa & words_within_edit_distance_dfa -found_words = list(found_words_dfa) -``` - -The DFA `found_words_dfa` accepts strings in the intersection of the languages of the -DFAs given as input, and `found_words` is a list containing this language. Note the power of this -technique is that the DFA `words_within_edit_distance_dfa` -has an infinite language, meaning we could not do this same computation just using the builtin -sets in Python directly (as they always represent a finite collection), although the -syntax used by [`automata`](https://caleb531.github.io/automata/) is very similar to promote intuition. - -## Citing - -This post is adapted from [our JOSS paper](https://joss.theoj.org/papers/10.21105/joss.05759), which should be used for citations. diff --git a/_posts/2024-07-31-pyconus-pyopensci-sprints.md b/_posts/2024-07-31-pyconus-pyopensci-sprints.md deleted file mode 100644 index a81dea18..00000000 --- a/_posts/2024-07-31-pyconus-pyopensci-sprints.md +++ /dev/null @@ -1,385 +0,0 @@ ---- -layout: single -title: "pyOpenSci beginner-friendly sprints at PyCon US 2024" -blog_topic: community -excerpt: "pyOpenSci holds beginner-friendly sprints with the goal of making new contributors feel welcome. Learn about our latest sprint at PyCon US 2024, and tips we use to make sprints accessible to more people." -author: "Leah Wasser" -permalink: /blog/pyopensci-pyconus-2024-sprints.html -header: - overlay_image: images/blog/headers/pycon-us-2024-header.png -categories: - - blog-post - - community -classes: wide -toc: true -comments: true -last_modified: 2024-08-29 ---- - -## pyOpenSci's approach to beginner-friendly sprints - -In previous posts, I talked about: - -* Our [incredible experience at PyCon US 2024](/blog/recap-pyos-pyconus-2024.html). -* My [personal experiences with the Python packaging ecosystem and building consensus - within the scientific Python community](/blog/python-packaging-friends-dont-let-friends-package-alone.html). - -Here, I will share with you what we have learned at pyOpenSci through holding -beginner-friendly sprints over the past two years. -Specifically, I want to -explore the varied motivations and barriers associated with contributions to -open source, and how pyOpenSci is addressing them. - -
- -## TL;DR - -* I believe that we can get more people involved in Open Source if it's done - the right way. Despite the word "open" in the name, open source is not - necessarily open to all people. -* By setting up infrastructure such as project boards and tagging issues as - beginner-friendly, you are on your way towards a beginner-friendly sprint. -* Be prepared to support people who are newer to Git and GitHub. -* Create opportunities for beginners to contribute to documentation. The - beginner lens reviewing your docs is critical to ensuring the documentation - is usable! Documentation contributions can also be implemented entirely on - GitHub without needing to use the command line. - - - -
- -## PyOpenSci's [PyCon US 2024](https://us.pycon.org/2024/) sprint - -I've been running pyOpenSci sprints for about two years now. This year was my -second year leading a PyCon US sprint for pyOpenSci. I've learned a lot! While -last year we had two sprinters, this year we had a tremendous turnout of 18 -people from several countries and across the United States for our 1-day -sprint. This resulted in over 35 issues and pull requests, where volunteers -helped fix issues on our website, in our tutorials, and in our packaging and -peer review guidebooks. Many long-standing issues and bugs were fixed thanks to -this wonderful Python community. - -We had so much activity that I'm still working on closing up issues and pull -requests now—weeks after the sprint ended! - -After such a tremendous experience this year, I wanted to share with you the -things that we've learned at pyOpenSci about running beginner-friendly sprints. - - -## What is a sprint like? - -If you haven’t been to a sprint before, it’s an experience every open source -enthusiast should have. Sprints are where people come together to work on a -project. The word sprint can also be used in a professional development setting. This might describe a team of developers working -together to release a new feature. For the volunteer open source community, sprints often mean volunteers -helping maintainers work on various parts of a project. And it's important to keep in mind that often the maintainers are -also volunteers themselves. - -### What do people sprint on for pyOpenSci? - -pyOpenSci is a nonprofit organization that is devoted to building diverse and -inclusive community that supports making science more open and collaborative. -Among other things that we do, we run [open peer review of scientific Python software](/about-peer-review/index.html). - -Running an volunteer-lead open peer review process for software is a challenge. As we grow, -it requires infrastructure to support tracking the process. We also create -community driven beginner-friendly - [Python packaging resources](https://www.pyopensci.org/python-package-guide/) and - [tutorials](https://www.pyopensci.org/python-package-guide/tutorials/intro.html). -High quality learning resources also require maintenance and review. -As such, we always need a mixture of packaging experts and those newer to packaging -to help catch errors and identify points of confusion. - -As the Executive Director and Founder of pyOpenSci, I created most of our -pyOpenSci GitHub infrastructure to support our mission. While I cherish times -when I can work on technical things, it’s hard to keep up. -Mission, vision and community work will always come first. - -I really appreciate when community members help us tick off open issues, -big and small. These contributions propel the pyOpenSci mission of making science -more open and collaborative forward. - -### Contribution opportunities - -While barriers to contribution are abundant and hard, there are also many opportunities, -including: - -* Learning and growing skills that may not be accessible in your day job. -* Connecting with other developers, data scientists, and scientists in the community. -* Engaging and being part of an online community or maintainer team. -* Growing your professional network. -* Learning about a new project. - -Or, maybe you're like me —- an Executive Director of a community organization. -Coding and development aren't in my job description, but to teach these topics, -I need to keep my skills fresh. And, I love to code. That's where open source -comes into my life! - -
- A graphic that at the top says - Why people attend sprints. Below are a series of boxes with text in them and white stick figures standing or sitting on top or to the side of the boxes as decoration. The boxes say Learn new technical skills, connect with the community, help a project they care about, build their network, learn about a new tool, build confidence, +many other motivations. The graphic is on a dark purple background with the pyOpenSci logo on it. -
People have many different motivations for attending sprints. Understanding those motivations can go a long way in the sprinter and the sprint leader(s) having a collectively - successful experience.
-
- -## Barriers in contributing to open source - -While sprints are a great way to engage the community in supporting an -organization's (or a project's) mission, there are many contributor barriers -that sprint organizers need to consider. - -These barriers include: - -* Time to contribute. -* Skills to contribute. -* Confidence in skills / fear of contributing the wrong thing. -* Privilege: This is a loaded one. Open source can't be diverse if it requires privilege. - to participate. - -And last but not least: - -* GitHub: Using Git and GitHub is always one of the biggest technical barriers -that contributors encounter in their open source and data science journeys. - - -### Challenges vs opportunities - -So how do we align the challenges that contributors face with the potential opportunities? - -While new contributor sprints are not for everyone, and assume some level of privilege -in having the time and bandwidth to participate, they can be great for many. - -But a sprint will only be a good experience if the contributor's needs and goals are also considered. Contributors need to gain something -from contributing, and this requires: - -* time and care in designing the sprint, and -* time spent with newcomers during the sprint. - -It's also important to note that the time required to participate is also a privilege for -many, if not most, maintainers who are already volunteering their time to maintain -a project! As such we can't expect all open source packages to be able to -support beginner contributions in a comprehensive way. - -However, if you do have the bandwidth to hold a sprint that embraces, empowers and uplifts those who are newer to open source, there -is a lot to be learned from understanding learner motivations and types. And a few good experiences might mean that a beginner decides to stick around. - -## Contributing vs learning - -The educator inside of me can't help but align my experience in open source with -learner motivations. For me personally, contributing to open source met two of my goals -and interests: - -* **Applied (project-based) learning:** I love to learn. Coding and data science are my - happy places. But the learning needs to be directly applicable. If it isn't, I get bored. Moreover, if I can't see the application of the skill, I have little motivation to learn that skill! -* **Student-directed learning:** I love to learn on my own time, following my own - processes that work for me. Open source allows me to do just that (and without the - pressures of a specific deadline in most cases). - -If you read the education literature, you will find both project-based learning and student-directed learning to be -a commonly discussed topic—especially as it relates to empowering people who are underrepresented in a space. - -More on that next... - -### The power of project-based learning - -[Project-based learning](https://www.wpi.edu/project-based-learning/why-pbl) is -founded on the idea that meaningful, applied projects are the best way to teach -a topic. This works especially well in the data science space and has been found -to be particularly effective with underrepresented groups. In fact, I implemented -and collected data on this in a previous project -that I designed and ran when I was in academia. - -The idea behind project-based learning is that students select a topic they are -personally interested in. If they are learning data science, they can find data -to analyze and support their project. For underrepresented groups in STEM (and -most learners), applied learning resonates because it involves a topic they care -about and are motivated to pursue. Skills are learned in the process of achieving -a meaningful outcome. - -It's similar to how you can immediately engage a diverse audience in GIS -(spatial data) with Google Earth by asking them to zoom into the area where they -live (place-based learning). - -As a volunteer maintainer of [Stravalib](https://github.com/stravalib/stravalib), -I am motivated to work on Stravalib because I learn in a very applied way. - -Sure, a sprint does not have the powerful outcome of a student understanding the -impacts of climate change on their local tribal lands. But the concept is the same: - -> The learning motivation comes from a meaningful outcome that a student wants or -> cares about. - -In leading a sprint, asking the question of "what are your goals for today?" will -help you as a sprint leader to direct sprinter efforts down a successful path. - - -### The power of student-directed learning - -Every person has different learning preferences. For many years, educators -taught people the same way: in a classroom, using the same books and approaches. -However, alternatives to this model allow learners to adapt their environment to -their personal and learning goal needs. -Student-directed learning -is based on the idea that people learn differently and have different needs. By -creating a hybrid learning environment where students are allowed to pick their -learning path and approach, you empower them to own their experience. - -Some learners benefit from hands-on demos, whether in a classroom or at the -sprint table. They may pick things up quickly or need to watch and digest the -demo. Others prefer reading quietly on their own, absorbing information, and -trying things out until they figure it out. - -If a sprint is set up correctly, learners can parse through available issues. -Carefully tagged issues direct them to ones that are beginner-friendly. These -issues should not require extensive Git and GitHub expertise, ensuring a good -first experience and an early win to build confidence. - -Contributors at the sprint can decide if they need direction, mentorship, or -just a short tutorial/guidebook for making their first contribution using the -GitHub interface. - - -### Collaborative Git and GitHub lessons - -This past year, I was awarded the [Better Scientific Software (BSSw) -Fellowship](https://bssw.io/pages/bssw-fellowship-program), in which I proposed -lessons on these lesser-known collaborative Git and GitHub skills—the same skills -that will support people attending sprints! I am looking forward to having these -lessons on hand for next year's sprints, in order to support those who are still -fighting to learn Git and GitHub (it can be an uphill process!). - -## The anatomy of a pyOpenSci sprint - -Based on my understanding of the learning motivations and challenges that new -contributors face, I've developed a pyOpenSci sprint approach that I've found to -be successful in empowering new contributors to make their first contributions. - -### Create opportunities for first-time contributors - -When someone joins your sprint, start by asking: - -1. What are you looking to achieve today? -2. How comfortable are you with Git and GitHub? - -If the contributor is new to Git and GitHub, it can be empowering to direct them -to review documentation with a fresh perspective. In our pyOpenSci sprints, -beginners often find bugs and issues in our online packaging tutorials. Fixing -these bugs is empowering for new contributors and helps pyOpenSci curate useful, -current, and beginner-friendly content. - -If you have a package, let the user review your tutorials or docs. They can then -either open issues or pull requests to address the issues. - -The beauty of documentation reviews is that any issues and PRs submitted can be -done entirely using the GitHub interface. This is a true win/win for both the -maintainer and the contributor. - -### Write useful constructive issues - -
- Image that says 'have you ever felt confused about python packaging.' Below
-  is the pyopensci flower logo. And the graphic shows a bunch of different cartoon
-  like hands being raised. -
This is an issue opened in our pyConUS 2024 sprint that could be - addressed in a future sprint. Often before a sprint, our team will go into the - existing issues and add more information related to next steps associated with the - sprint. In this case - a beginner could take on testing out the Hatch installation - instructions. They could also add a note that Hatch installs Python for a user if - they don't already have it installed.
-
- -A good sprint begins with useful, labeled issues. The information in the body of -an issue is critical. Be sure to include the specific details of the issue to be -addressed with the lens of someone who is new to your project. Give potential -contributors everything they need to start working, which helps reduce -administrative questions during the sprint. For a table of 10-20 people sprinting -for pyOpenSci, this means the sprint leader will have fewer questions to answer -during the sprint event. - -### Label issues as help-wanted and/or sprintable - -
- Graphic says Label issues with help-wanted / sprintable. Below it is a list of issues from the GitHub interface. One of the issues in the list is well labeled. It has 3 labels that say beginner-friendly, help wanted and then sprintable. Each has a different color. -
We like to label issues as help-wanted and sprintable. A sprintable issue is a discrete issue that could be tackled (and ideally completed) during a sprint. A sprint's duration can span from a half day to multiple full days depending on the event. What is most important is that the issue is confined in some way so a single person could tackle it in a reasonable amount of time.
-
- -You never know when a contributor might be looking for a data science project to -work on. It's always helpful to tag issues that could be completed or started -during a sprint as `sprintable` and `help-wanted` if you think someone outside -of your team could tackle the issue. This tip came from -[Inessa Pawson](https://github.com/InessaPawson), who has extensive experience -with contributors through her work on the -[Contributor Experience Project](https://contributor-experience.org/). - -### Collect issues in a single place—the help-wanted board - -
- A graphic that shows the pyOpenSci GitHub project board. The graphic shows two columns of the project board. The first is Python Packaging and it has 7 issues in that column. The second column says Beginner-friendly / non technical. The point of this graphic is just to show a user that a project board is a nice way to organize issues that contributors might want to challenge during a sprint. At the bottom there is text that says includes a beginner-friendly column and should have easy-to-understand issue titles. -
Project boards help you organize your sprintable tasks into categories that might help a contributor find something that interests them. Here we have a beginner-friendly column for beginners, but we also include columns that are topical such as Python programming, CI / DevOps and Sphinx. Also notice that the issues are generally clearly titled.
-
- -Curate a `help-wanted` board that contains all the issues that could be completed -by an outside community member in one place. - -Our pyOpenSci `help-wanted` board is a -[GitHub project board](https://github.com/orgs/pyOpenSci/projects/3/views/2) -that allows us to organize issues that we think others could work on by type, -including: - -* Beginner-friendly -* Python -* DevOps / CI - -### Infrastructure and automation of issues - -PyOpenSci has many GitHub repositories where people might label an issue as -`help-wanted` and forget to add it to our sprint help-wanted project board. To -address this, we set up -a [CI workflow](https://github.com/pyOpenSci/pyopensci.github.io/blob/main/.github/workflows/add-help-wanted.yml) -that can be added to any repository. This workflow moves any issue labeled -`sprintable` or `help-wanted` to our help-wanted GitHub project board. - -**GitHub project boards support project workflows** that auto-add issues to a -project board with a specific label. However, our GitHub organization's open source -subscription only allows for one project workflow of this kind associated with one -repository, which is why we set up the GitHub action. We also have things setup so an issue is removed / archived from the GitHub project board once it is closed. -{: .notice} - - -### Make it truly beginner-friendly - -To support beginners contributing to open source, consider having one or two -helpers (depending on the size of your sprint) who can assist with: - -1. Understanding the issues -2. Using Git/GitHub -3. Directing novice sprinters to beginner-friendly items - -In all of our sprints to date, we have had a mixture of people who: - -1. Know Git and GitHub but haven't contributed to open source before. Sometimes - these people are developers and can tackle coding challenges! -2. Are seasoned and just need enough information in the issue to be successful - (see above section on issue content). - -Other times, you have people who really want to contribute but are uncomfortable -with Git and GitHub and have never contributed to open source. These people will -be incredibly empowered if you can create a smooth path to their first -contributions. - -## Wrap up - -Well-led sprints offer both new and seasoned contributors an opportunity to -contribute to a project, while also learning new skills, connecting with new -people, and learning about new projects. If you plan to support new -contributors in your sprint, then it's ideal to do some legwork before the -sprint begins to ensure sprint attendees feel supported and have the help that -they need when they get stuck. - -pyOpenSci is looking forward to our next sprint—to be held at the [SciPy 2024 -meeting](https://www.scipy2024.scipy.org/) in Tacoma, Washington. Last year we -had three people make their first contributions to open source in our SciPy -2023 sprint. I'm hoping that this year is even better! - -Come join us if you are there! diff --git a/_posts/2024-08-02-community-news-august-2024.md b/_posts/2024-08-02-community-news-august-2024.md deleted file mode 100644 index 00591ee9..00000000 --- a/_posts/2024-08-02-community-news-august-2024.md +++ /dev/null @@ -1,68 +0,0 @@ ---- -layout: single -title: "pyOpenSci Community News: August 2024" -blog_topic: updates -excerpt: "Welcome to the first edition of pyOpenSci's Community News newsletter, where we summarize and share conversations, contributions, and news related to our diverse and vibrant community! Read on for insights into running pyOpenSci sprints, Python packaging guide Spanish translation efforts, and using Hatch to create and maintain Python." -author: "Jesse Mostipak" -permalink: /blog/pyos-community-news-aug-2024.html -header: - overlay_image: images/blog/headers/pycon-us-2024-header.png -categories: - - blog-post - - community -classes: wide -toc: true -comments: true -last_modified: 2024-08-29 ---- - -## pyOpenSci community news - -As the pyOpenSci community has continued to grow, we wanted to dedicate at least one newsletter issue a month to sharing conversations, contributions, and news related to our diverse and vibrant community! This month we’re celebrating everyone who’s participated in a pyOpenSci sprint, sharing the exciting news around the Spanish translation of our Python Packaging Guide, and elevating a discussion from GitHub around the use of Hatch for Python package creation. - -## Hello, Hatch! - -Education is one of the [three petals of pyOpenSci](https://www.pyopensci.org/blog/pyos-education-announcement.html), and to that end we strive to create resources that help learners navigate the Python packaging ecosystem with ease. This means that when we find our community members and scientists encountering consistent issues with trying to make their workflows more open and reproducible, or trying to build a Python package, we look for ways to lower (or ideally remove!) the barrier for learners. And this was exactly the scenario we were faced with in our beginner-friendly packaging tutorials. We decided that Hatch is a great user-friendly tool that scientists can use to package and share their code. However, in developing and teaching our [Get to know Hatch tutorial](https://www.pyopensci.org/python-package-guide/tutorials/get-to-know-hatch.html), we found scientists struggling to install a tool called [`pipx`](https://github.com/pypa/pipx) that can be used to install Hatch globally, rather than in a Python environment. This was causing a host of problems (in large part because `pipx` can be tricky to install on Windows machines!) - -
- a circle and three petals representing the pillars of pyOpenSci. The circle at the center represents a diverse, inclusive community, and radiating out from it are the three petals–how pyOpenSci accomplishes its community goals. Those are: open peer review, community partnerships, and training and open educational resources. -
The three petals of pyOpenSci.
-
- -We know that it can be challenging to wrap your head around packaging, but what it boils down to is that packaging is really just a process of making your code installable so that it can be reused in different places. And while we decided to use Hatch because it makes packaging life easier, it turns out that Hatch has its own nuanced pain points that we’re still filing down! And we want to share this, because we firmly believe that even someone who is brand new to packaging can get value out of our materials. - -> ...packaging is really just a process of making your code installable so that it can be reused in different places. - -If you’d like to dig into the details of our Hatch installation adventures, we recommend reading through the discussions in the associated issue and PR: - -* [Fix: update hatch install instructions to use the hatch installers rather than pipx in our tutorial #301](https://github.com/pyOpenSci/python-package-guide/issues/301) -* [All Hatch PRs: [Tuesday july 2 merge] Fix: update hatch install instructions](https://github.com/pyOpenSci/python-package-guide/pull/308) - -And for those of you who are interested in the end result, after a robust and lively community discussion, we’ve updated our Get to know Hatch tutorial with the following changes: - -1. Suggest users default to the Hatch installers rather than `pipx`. -2. Suggest Linux users use `pipx` as the install there is simpler. - -It’s important to us to elevate this conversation, because all of our materials are community–developed and go through extensive technical review not only before they’re published for the first time, but also for the entire lifetime of the content! We value openness and transparency, and because of the open discussions with community members, we’ve been able to make it even easier to get started with Python packaging! - -## Coming soon: a Spanish translation of the pyOpenSci Python Package Guide - -This past May, during our [PyCon US sprints](https://www.pyopensci.org/blog/pyopensci-pyconus-2024-sprints.html), pyOpenSci community member [Felipe Moreno](https://github.com/flpm) submitted [an issue to our Python Package Guide GitHub repository](https://github.com/pyOpenSci/python-package-guide/issues/287), asking if we had considered translating the guide to Spanish. Felipe is an experienced security engineering manager with a strong background in Python software development, data science and user-centered design, who leads the Security Data Science team in the CISO Office at [Bloomberg](https://www.bloomberg.com/company/). Felipe has worked with other projects, setting up infrastructure support through [Sphinx](https://www.sphinx-doc.org/en/master/) for this kind of work. We were, of course, immediately thrilled with the idea, and took Felipe up on the offer! From there, Felipe built a PR that created a [translation guide for contributors](https://github.com/pyOpenSci/python-package-guide/pull/304), along with another PR that [provides internationalization support](https://github.com/pyOpenSci/python-package-guide/pull/298). - -Following Felipe’s initial efforts, several of our SciPy sprint attendees continued to work on translation of the pyOpenSci Python Packaging Guide, and you can see their progress in the [guide repository on GitHub](https://github.com/pyOpenSci/python-package-guide/issues?q=is%3Aopen+is%3Aissue+label%3Ascipy-24). This has resulted in 13 PRs to our packaging guide. Our next steps are to create an editorial team composed of members who are fluent in Spanish/native speakers. This team will be responsible for reviewing and merging these contributions. Stay tuned for updates on this incredible community-driven project! - -> Following Felipe’s initial efforts, several of our SciPy sprint attendees continued to work on translation of the pyOpenSci Python Packaging Guide...resulting in 13 PRs to our packaging guide. - -If you have questions about how to get involved with pyOpenSci, please reach out to [media@pyopensci.org](media@pyopensci.org) so that we can connect you with the right people! - -## All the credit for our contributors - -We have been absolutely floored by the community involvement with pyOpenSci over the last few months, and wanted to take a moment to recognize everyone who has been a part of our sprints at both [PyCon US](https://us.pycon.org/2024/) and [SciPy](https://www.scipy2024.scipy.org/)! In total we’ve had over 30 contributors submit 65 issues to six different pyOpenSci repos between the two events, which is incredible. - -### Props to all of our PyCon contributors, including: -Jesse Bobish, [Patrick Byers](https://github.com/pb-413), [Sarah Kaiser](http://github.com/crazy4pi314), [Ryan Keith](https://github.com/ryanskeith), [Jon Kiparsky](https://github.com/jonkiparsky), [Filipe Laíns](https://github.com/FFY00), [Cheng Lee](https://github.com/chenghlee), [Felipe Moreno](https://github.com/flpm), [Matthew Ngoy](https://github.com/Vaunty), [Jeremy Paige](https://github.com/ucodery), [Ken Seehart](https://github.com/kenseehart), [Steven Silvester](https://github.com/blink1073), [Megan Sosey](https://github.com/sosey), [Zack Weinberg](https://github.com/zackw), [Brianne Wilhelmi](https://github.com/BSuperbad), [Carol Willing](https://github.com/willingc), [Sneha Yadav](https://github.com/sn3hay), and [Bradon Zhang](https://github.com/BradonZhang). - -> In total we’ve had over 30 contributors submit 65 issues to six different pyOpenSci repos between the two events, which is incredible. - -### Shout out to all of our SciPy contributors, including: -[Naty Clementi](https://github.com/ncclementi), [Geoff Cureton](https://github.com/gpcureton), [John Drake](https://github.com/John-Drake), [Han](https://github.com/ayhanxian), [Elise Hinman](https://github.com/ehinman), [hpodzorski-USGS](https://github.com/hpodzorski-USGS), [Sarah Kaiser](https://github.com/crazy4pi314), [kaiyamag](https://github.com/kaiyamag), [Felipe Moreno](https://github.com/flpm), [Roberto Pastor Muela](https://github.com/RobPasMue), [Olek](https://github.com/yardasol), [Santiago Soler](https://github.com/santisoler), and [Revathy Venugopal](https://github.com/Revathyvenugopal162). diff --git a/_posts/2024-08-02-pyopensci-at-scipy-2024.md b/_posts/2024-08-02-pyopensci-at-scipy-2024.md deleted file mode 100644 index 7d4fd0a6..00000000 --- a/_posts/2024-08-02-pyopensci-at-scipy-2024.md +++ /dev/null @@ -1,476 +0,0 @@ ---- -layout: single -title: "pyOpenSci @ SciPy 2024 - Python Packaging Tutorials, Talks and Community :heart:" -blog_topic: community -excerpt: "pyOpenSci had an incredibly impactful SciPy conference this year in Tacoma Washingon. I gave a talk and we lead a workshop on Python packaging and we had 39 issues and pull requests submitted during our sprints. Learn more about both the SciPy meeting and my expeirence SciPy in Tacoma, Washington this year." -author: "Leah Wasser" -permalink: /blog/pyos-scipy-2024-recap.html -header: - overlay_color: "#33205c" -categories: - - blog-post - - community -classes: wide -toc: true -comments: true -last_modified: 2024-08-29 ---- - - -
-## TL;DR - -* pyOpenSci lead 3 incredibly successful events at [SciPy](https://www.scipy2024.scipy.org/) this year: A tutorial, -a talk and a 1.5 day sprint. -* During our SciPy 2024 meeting sprint we had over 35 GitHub issues and pull requests submitted by XX new contributors. -* Our tutorial had over 30 attendees. Almost all of the learners had never created a Python package before, and most of them were successful creating their first Python package. -* I thoroughly enjoyed connecting with new and old colleagues and friends. - -
- -## pyOpenSci's fourth year attending SciPy - my experience - -This year was my fourth time attending the annual SciPy meeting—a meeting -organized by [NumFocus](https://www.numfocus.org) that celebrates the scientific -Python ecosystem. My first experience was in 2019, where we held the very first -pyOpenSci Birds of a Feather (BoF) session. - -A Birds of a Feather session, also known as a BoF, is a community-organized -event where people lead a discussion around a specific topic. Our 2019 SciPy BoF was about -our peer review process, which we had just launched that year. -{: .notice } - -One of the biggest differences this year has been how much pyOpenSci has grown, which means we saw many familiar faces, including: - -* Maintainers of packages that we have reviewed and accepted through our - [scientific Python software peer review process](/about-peer-review/index.html), -* Colleagues who I have met at other meetings, such as [PyCon](https://pycon.org/), -* Reviewers and editors from our community, -* Members of our advisory council, contributors, and -* Friends—so many friends. - -This year, I was busy at SciPy, and spent my days: - -1. Running an in-person tutorial: [Create Your First Python Package](https://cfp.scipy.org/2024/talk/QT9GBY/). -2. Giving a talk, [The power of community in solving scientific Python’s most challenging problems](https://cfp.scipy.org/2024/talk/AMTLJ7/), in the Maintainers Track (what an honor!). -3. Working the hallway track. -4. Running a 1.5-day sprint that resulted in over 35 issues and pull requests. - Wow. - -Admittedly, I started off this conference behind and frazzled. You see, pyOpenSci -has been growing in recent months. With that growth comes more wonderful people -to support and engage with! More people getting involved does mean more work for -pyOpenSci. However, the time is worth it, as all of this effort is moving -pyOpenSci's mission of supporting open science forward. - -
- A bar plot with purple bars titled - number of submissions by quarter per year. The plot shows the number of submissions that pyOS has received since it started peer review in 2019. We started with only a few submissions per quarter but the numbers have grown to about 13 per quarter on average in the 2024. -
pyOpenSci runs an open software peer review process. Above you can see how submissions have increased since we started in 2019. I started full time on pyOpenSci in late 2022. .
-
- -In the end, even though I wasn't quite as prepared as I wanted to be, my talk -and the workshop went great! This was a lesson for me in understanding that -things can be less than perfect and still go well. - -More on all of that below. But first--what is the SciPy Conference? - -## A brief history of the annual SciPy conference - -If you haven't attended this meeting before, let me give you the rundown. The -SciPy Conference is an annual event dedicated to celebrating and learning more -about scientific computing, all using the Python programming language. The meeting -started back in [~2002](https://web.archive.org/web/20021030003450/http://www.scipy.org/site_content/scipy02/). The early meetings were run by EnThought and -were smaller in size and scope. Over the years, SciPy has evolved significantly, -mirroring the [🚀 explosive growth of the Python ecosystem 🚀](https://stackoverflow.blog/2023/01/26/comparing-tag-trends-with-our-most-loved-programming-languages/) -in scientific computing and data science. - -Today the SciPy Conference, hosted by [NumFocus](https://numfocus.org/), -attracts over 700 attendees from diverse scientific fields such as astronomy, -biology, geophysics, and more. SciPy is much more than just a meeting. - -SciPy features: - -* Incredible keynote speakers, such as the inspirational - [Carol Willing](https://www.youtube.com/watch?v=s-W-UvGgDco). -* Talks from the community about tools and approaches. -* Poster sessions that allow presenters to directly engage and discuss cool - work with the meeting attendees. -* Tutorials led by community members (including pyOpenSci) that cover important - scientific data processing and analysis-related Python skills. -* Sprints: collaborative coding sessions where people come to contribute to - and learn about open-source projects. - -I'd be remiss if I didn't mention the lightning talk sessions. During lightning -talks, people throw their names into a literal bucket. If their name is selected, -they are given 5 minutes (or less if the dice are rolled) to talk about a topic -they love. Last year the talks featured a large stuffed dice that you would roll. the number that was rolled determined how much time you have to present and what distractions the moderators would apply to you. :laughing: - -You see, at SciPy, there are often off-beat distractions from the moderators -along the way, such as the infamous grab claw (see below). Yes, it is as -ridiculously hilarious as it sounds. - -
- Photograph showing a man with a beard holding a red lobster claw - while deviously looking at a blonde woman at a podium who is smirking at him. She is wearing a blue top and beige pants -
It's well-known that lightning talk organizers enjoy heckling presenters. Sometimes they do so using props, such as the lobster claw seen here . Photo Credit: SciPyConf Fosstodon
-
- -[Did someone mention sharks? More here on my experience last year giving a SciPy lighting talk.](/blog/scipy-2023-community.html) -{: .notice } - -Another cool thing about SciPy is that the entire meeting is community-driven. The scientific Python community really -shows up to make this meeting what it is. All of the sessions are volunteer -organized and run. Volunteers also spend considerable time engaging with -presenters, ensuring there are social activities that everyone is invited to in -the evenings, and generally making sure that everyone feels included. - -Since the pandemic, the SciPy meeting has adapted to support virtual/hybrid -participation, which has further increased its reach. In fact, some remote -attendees also helped organize the meeting! - -If you are a Pythonista who loves science, this meeting is for you! - -## pyOpenSci's first SciPy tutorial was a huge success: Create your first Python package - -My adventure at the SciPy meeting kicked off with a 4-hour tutorial entitled: -_[Create Your First Python Package: From Code to Module](https://cfp.scipy.org/2024/talk/QT9GBY/)_. - -I have to start by saying the tutorial went great. Here is what one person -had to say about it: - -> The content and the crew! The team was so kind, patient and approachable. I appreciate the amount of support and reassurance given during this tutorial. The content of the tutorial was also spot on. Everything we covered felt relevant and useful, and gave me the confidence to feel capable of creating my own packages. - -This tutorial was an -expanded version of the [Create Your First Python Package tutorial that -pyOpenSci ran in April 2024](/blog/pyos-education-announcement.html). In our first -workshop, we had over 20 people create their first Python package. We had -similar success in our SciPy tutorial. - -I realized when I got to SciPy that I hadn't actually taught in person since -2020! I vividly remember the last in-person class that I led for the [Professional -Graduate Program in Earth Data Analytics that I created at CU -Boulder](https://catalog.colorado.edu/graduate/colleges-schools/interdisciplinary-programs/earth-data-analytics-foundations-graduate-certificate/). -That class happened on the Wednesday before the pandemic lockdown started in the -United States. It was a sad day to say "goodbye for now" to in-person teaching -and to my students. Teaching and working with learners is, after all, one of my -favorite things. However, luckily we had been running the program using a -hybrid online and in-person approach, so the transition was sad, but didn't -impact our learners too much. - -But back to SciPy, it was a rush to be back in the "classroom" at SciPy 2024! - -### A pyOpenSci tutorial room full of eager learners and tech that worked - -Our workshop room was full of people eager to better understand Python packaging. -We also had great helpers, and all the tech in the room just worked seamlessly. -The SciPy tutorial organizers did an amazing job of ensuring everything ran -smoothly. - -After a really tricky internet situation at PyCon this past May, I truly -appreciated the smooth setup of the room. - -A special shout-out to pyOpenSci community members -[Jeremiah](https://www.github.com/ucodery) and -[Isabel Zimmerman](https://github.com/isabelizimm), who were committed to -helping all the learners have a successful experience. Their support and -expertise were invaluable. - -
- A photograph showing a large open and well-lit meeting room with two rows of horizontal tables. People are working at laptops in the room. An instructor is at the front with their laptop screen projected on the screen -
This image was taken about halfway into the workshop. We had lost a few folks to other conference activities, but most were still engaged! Source: SciPyConf Fosstodon.
-
- -### pyOpenSci is helping the community navigate Python packaging challenges together - -Of course, there were a few challenges, too. - -1. Many participants came without working through the setup instructions. This - was particularly problematic for those with government-issued laptops, where - they couldn't install software. - -1. While most workshops could use the Jupyter-Hub cloud platform called Nebari, hosted by - Quansight Labs, it didn't support our development use case. Thus, we didn't - have a "backup" platform for participants who couldn't get things set up. - However, I later worked with [Sarah Kaiser](https://github.com/crazy4pi314) - from GitHub, who set us up with a [Codespace](https://github.com/features/codespaces) - that we can use for future workshops. - -1. Because we practiced publishing to (test) PyPI, we should have suggested that participants create an account in advance! - -1. Installing things across operating systems (Linux, Mac and windows) is always - tricky. In this workshop, we switched from suggesting - [pipx](https://pipxproject.github.io/pipx/) to using - [Hatch installers](https://hatch.pypa.io/latest/install/) for Mac and - Windows users. We made this change because Windows users previously had - significant issues installing `pipx` both during our previous workshop, and - during our [PyCon 2024](https://us.pycon.org/2024/) sprint. - - The glitch we encountered this time with the installers was that [Hatch](https://hatch.pypa.io/latest/) would - initiate an update on some computers that already had it installed when users - ran `hatch --version` for the first time. This is something we need to - address in the future, or at least warn users about. - -### Our pyOpenSci tutorial at SciPy 2024 was hugely successful - -Challenges aside, we also had a lot of successes to celebrate! - -Similar to our online workshop in April, many attendees created their -first Python package! - - - -
- An image showing a question: 'Have you created a Python package before?' Below is a bar plot with 3 colored bars. 3 people answered Yes, 10 people answered No, and 4 answered 'I struggled trying.' - An image showing a question: 'Were you able to create a package during this workshop?' 10 people answered Yes, 1 person answered No, and no one answered 'I am not sure.' -
We used Mentimeter during the workshop to get feedback from participants along the way. Next workshop, I think I will use this tool more to gather feedback. Feedback is crucial for iteratively improving any event, especially when teaching, as it allows you to address pain points dynamically, improving both online lessons and in-person trainings.
-
- -The verbal feedback from participants was overwhelmingly positive. Comments like - -> "One of the best workshops I've been to" - -and - -> "I'm always behind in workshops like this. This was the first workshop where I could actually - keep up" - -made all the effort worthwhile. - -Needless to say, I left the event with a full heart. 🫶 - -Several participants enjoyed the process so much that they joined our sprint -afterwards to help build out the packaging guide. More on that below. - -## pyOpenSci, Python packaging and community--my talk at SciPy 2024 - -The day after the workshop discussed above, and the first day of the conference, -I gave my first-ever talk, [The power of community in solving scientific Python’s most challenging problems](https://cfp.scipy.org/2024/talk/AMTLJ7/), in the maintainers track at SciPy. - -What an honor to be selected as a speaker. - -This year, the SciPy maintainers track was organized and hosted by -[Inessa Pawson](https://github.com/InessaPawson), -[Brigitta Sipőcz](https://github.com/bsipocz), -[Matt Craig](https://github.com/mwcraig), and -[Mridul Seth](https://github.com/MridulS). It kicked off with a fantastic talk -by [Eric Mah](https://github.com/ericmjl). Eric discussed how he has been -bringing open-source values and workflows to the corporate environment at -Moderna (yes, the same company that makes one of the more popular COVID -vaccines). Eric's talk carefully navigated both the benefits of open source for -corporate teams while also acknowledging some of the tensions. - -My talk closed out the session. - -
- Image of a room of round tables with black covers filled with people listening to my talk. At the front is a speaker wearing a red tank top. On the screen is a slide that says 'The Power of Community Review.' -
Me, giving my talk about community, packaging, and peer review at SciPy 2024. The coolest part of the talk was meeting people afterwards who I hadn't met in real life before. Photo Credit: Inessa Pawson
-
- -### pyOpenSci is leveraging and working with the community to solve scientific Python's challenges - -My talk was about how pyOpenSci has been carving out space and coordinating -community efforts to address several core challenges in our scientific Python -ecosystem. These include: - -1. Helping scientists find and use the right open-source tools. -2. Encouraging scientists to write better code, share their code, and build better software. -3. Ensuring scientists get credit for their open-source work. -4. Addressing the ongoing challenges of packaging in the Python ecosystem-—a topic - I discussed in my [PyCon talk in April](/blog/python-packaging-friends-dont-let-friends-package-alone.html), - which was also available on YouTube (video/playlist may have been moved). I'd love for you to check out the [SciPy 2024 conference site](https://www.scipy2024.scipy.org/) for current talks and tutorials. - -### pyOpenSci's Python packaging guidebook is having a positive impact on the scientific Python ecosystem - -During my talk, I addressed critical pain points I've experienced as an -educator teaching spatial and earth data science, and as a maintainer of -[stravalib](https://stravalib.readthedocs.io/en/latest/)—a package that supports -my pre-getting-COVID obsession with ultra mountain -running. - -Things kicked off with a bang, thanks to the best meme ever, created by -[Filipe Fernandes](https://github.com/ocefpaf). Filipe, a conda-forge -maintainer, introduced me to conda environments when I was struggling with -creating consistent spatial data environments for my students. Anyone who's -spent time wrestling with GDAL in a Python environment knows the struggle. -Thanks to folks like Filipe and the conda-forge community, managing spatial -environments has become much easier for everyone! - -
- Image of me with my eyes closed chuckling as people laugh at the meme on the screen. The meme has Boromir, played by Sean Bean in the Lord of the Rings Trilogy, making a circle with his right hand with the words 'One does not simply pip install GDAL overlaid on the image.' -
Filipe's meme is always a hit. Thinking about pip installing GDAL is not for the faint of heart. In the early years of using Python for spatial data, it was incredibly challenging to create a successful environment that contained spatial libraries (which often depend on GDAL)... Photo Credit: Luis López
-
- -I discussed how pyOpenSci is working to make things easier for -scientists by improving access to the right packages, maintaining community-vetted -tools in one place, helping maintainers step down and transition out, and making documentation -more beginner-friendly. - -
- A slide on a dark purple background that says - What If... finding the right package was easier. Community vetted tools were maintained in one place? We could help maintainers step down? Docs were more beginner friendly. At the bottom, there are two stick figures at computers - one is frustrated, one is happily typing away. -
pyOpenSci asks and addresses the questions - what would happen if things were easier for scientists? .
-
- -### pyOpenSci is running community-led inclusive, open software peer review - -I also talked about how pyOpenSci is using an inclusive, -[community-led peer review process](/about-peer-review/index.html) to achieve -several goals: - -* Helping scientists find vetted, trusted, and maintained software. -* Helping scientists build better software. -* Providing maintainers with credit for the important work they do to support open - science. - -
- Slide from my talk. It says 'The Power of Constructive Community Review' at the top. Below is a graphic showing the 4 core steps from left to right: submit your package, pre-review checks, review, and then accepted. The pyOpenSci accepted badge is below the accepted box. At the end of the row is a box that says 'Maintenance checks and stats.' -
pyOpenSci runs an open, community-led software peer review process. The process is overseen by a volunteer editor-in-chief, a rotating position, and includes an editor who leads the review and at least two reviewers. pyOpenSci cares about the long-term maintenance of software, so packages in our ecosystem undergo checks to ensure they are still actively maintained. The review itself is constructive and designed to support the maintainer(s) in improving the quality and usability of their package.
-
- -I also discussed how pyOpenSci [partners with communities](https://www.pyopensci.org/partners.html) like the [Journal of Open Source Software](https://joss.theoj.org/) and [Astropy](https://www.astropy.org/) to leverage resources. In the spirit of open source, our goal is to learn as much as we can from other communities. - -Where ever we can, pyOpenSci partners with other communities and leverages their -work. Building on top of and leveraging each other's work IS the true spirit of -open source and open science. By working together, we can move forward together. - -
- - A diagram on a light purple background showing the three peer review steps: prereview checks, review, and pyOpenSci acceptance. Below the review box, it says 'Your community standards,' indicating that a partner community can add their own affiliated package standards to our review process. To the right of this process is an arrow leading to three boxes: 'JOSS Published,' 'community affiliated,' and 'maintenance checks and stats.' -
Through our partnership with the Journal of Open Source Software (JOSS), a package submitted to us can also be published by JOSS if it's in scope. JOSS accepts our review and only reviews the paper, providing a huge benefit to the maintainer team. We also partner with communities such as Astropy. Through the Astropy partnership, an Astropy-related package can also become Astropy-affiliated. Members of the Astropy community volunteer on our editorial board and support these reviews.
-
- -### pyOpenSci and how we created a truly community-driven Python packaging guidebook - -I also discussed our work in the Python packaging ecosystem, specifically around -making packaging more beginner-accessible and friendly. pyOpenSci leans into -community expertise and review as a way to leverage community expertise when -making packaging decisions. People who have reviewed our packaging content -include both those in the scientific Python ecosystem and those working on core -Python challenges. These people include: - -* Python Packaging Authority members (PyPA) -* Packaging tools maintainers -* Packaging experts -* Scientists -* People new to packaging - -All of these people have helped pyOpenSci create a [Python packaging guide](https://www.pyopensci.org/python-package-guide/index.html) that is both -accurate and beginner-friendly. - -
- Image of the pyOpenSci package guide review process. There are three main boxes on the slide labeled 'expert feedback,' 'community review,' and 'sprints and bug bashes.' The top of the image is titled 'The Power of Community Review.' -
pyOpenSci engages people from all parts of the Python community, with all levels of expertise, in our packaging guide review process. This engagement ensures a high-quality and beginner-friendly guidebook.
-
- -One of the decisions we made as a community early on was to use Hatch as an -end-to-end packaging tool. We believe Hatch is user-friendly and supports many -of the use cases scientists have when sharing their code. - -
- Image titled 'Creating an Opinionated Tutorial.' Below it lists four bullet points about Hatch: 1. Responsive maintainer, 2. User friendly, 3. Adheres to standards, 4. Doesn't lock you in. -
pyOpenSci is teaching with Hatch now because we have had good success with using Hatch and engaging with its forward-thinking maintainer.
-
- -I met some great people and had good discussions about peer review and Python -packaging. The presentation will be on YouTube at some point, and I will update -this post with the link when it's live. In the meantime, my slides are available -on [pyOpenSci's Zenodo community](https://zenodo.org/records/8045448). - -## pyOpenSci and reconnecting with the scientific Python community--the hallway track - -It seems like this year has been the year of the hallway track. The hallway -track refers to the time spent in the hallways of a meeting, talking to people -you don't normally get to work with in person. Sometimes, the hallway track can -be even more valuable than attending the talks. You can always watch the talks -later on YouTube, but you can't always collaborate in real life like you can at -a meeting like SciPy, which attracts people from around the world. - -I spent a lot of time talking with colleagues, friends, and community members -about all things Python, open source, and open science. - -* I worked with Sarah Kaiser on our new GitHub container to support workshops. -* I had an _ad hoc_ sprint with [Angus](https://github.com/agoose77) and [Rowan](https://github.com/rowanc1) from the [MyST Markdown](https://mystmd.org/) community - to develop our [pyOpenSci peer review metrics dashboard](https://www.pyopensci.org/metrics/). - -Additionally, I caught up with colleagues, chatting about packaging and scientific Python. - - -## pyOpenSci sprints at SciPy--over 39 pull requests and issues - -Every year at SciPy, we spend the last two days of the meeting sprinting. -This year was the first time I stayed for both days of the sprints. - -### A brief overview of open-source sprints - -If you haven't been to an open-source sprint before, a sprint is an open -session where contributors join a project and help address specific issues and -tasks that the project needs help with. I wrote [more about how pyOpenSci runs -beginner-friendly sprints here](/blog/pyopensci-pyconus-2024-sprints.html). - -Sprints are a lot of fun because, -given enough time, you can make significant progress on a project and support -first-time and new contributors in making their first contributions! - -This is what the second sprint room looked like this year at SciPy. There was a -scramble to find a new location for our sprints due to some power issues -stemming from an unexpected accident in Tacoma. Our space was much smaller -than usual, but we made it work! - -
- - Photograph showing a tan room filled with long tables and people sitting
-    in front of laptops working together on specific projects or tasks. -
The sprint setup isn't always perfect at meetings, but we made it - work! This year, the rooms were full with little space to spread out. However, - we made the best of it. The pyOpenSci sprint table is in the middle row, - closest to the camera.
-
- -We had a great group this year who worked on a variety of tasks, including: - -* Tackling a longstanding issue to sync our labels across all repositories. We - have many labels, and they vary across repositories. The work is now done, and - next, we will implement a GitHub action to clean up labels across all of our - GitHub repos. -* Working on our [Python Packaging Guide](https://www.pyopensci.org/python-package-guide/), - which is now being translated into Spanish! -* Someone enthusiastic about Sphinx worked on our [pyos-sphinx-theme](https://github.com/pyOpenSci/pyos-sphinx-theme). - pyOpenSci has several online "books" that would benefit from using the same - theme and colors that follow the pyOpenSci brand. -* A handful of people contributed to our MyST Markdown [peer review metrics dashboard](https://www.pyopensci.org/metrics/). - -
- - An image of about 10 people sitting at a long table working on laptops. - - Another image similar to the above, but from the other side of the table
-    on the second day. -
People sitting at the pyOpenSci sprint table. We had a slightly - different group each day, but the energy was equally enthusiastic both days. -
-
- -Several people made their first-ever contributions to open source during our -pyOpenSci sprints, which was fantastic. We had a lot of great people get -involved and support us. The positive vibes were contagious! - -## pyOpenSci's most successful SciPy meeting yet, and we're just getting started - -It's amazing to think about how far pyOpenSci has come in the past five years. -In 2019, pyOpenSci was just kicking off its peer review process, and only a -small number of people knew about us. By 2023, we had some funding to begin -traveling and spreading the word about our work, but we were still somewhat -unknown in the community. Fast forward to 2024, and people not only know about -us but are also excited to contribute and support our mission. We aim to help -scientists find and build better software and develop better open science skills. - -After this spectacular year, I can't wait to see what 2025 brings! - -### Get involved with pyOpenSci - -If you are interested in getting involved with us, there are many ways to do so! -Check out our [volunteer](/volunteer.html) page as a starting place. Or shoot -an email to [media at pyopensci.org](mailto:media@pyopensci.org). - -I can't wait to see you next year at PyCon US and SciPy 2025! diff --git a/_posts/2024-08-30-pyopensci-monumental-growth-2024.md b/_posts/2024-08-30-pyopensci-monumental-growth-2024.md deleted file mode 100644 index 5489cebf..00000000 --- a/_posts/2024-08-30-pyopensci-monumental-growth-2024.md +++ /dev/null @@ -1,398 +0,0 @@ ---- -layout: single -title: "It's Been a Long Short Road: The Monumental Past 2 Years of pyOpenSci" -blog_topic: community -excerpt: "Learn about what pyOpenSci has accomplished in the last two years, including the evolution of our packaging guide, the expansion of our peer review process, and the vibrant community we've built." -author: "Leah Wasser" -permalink: /blog/what-pyopensci-accomplished-with-two-years-of-funding.html -header: - overlay_image: images/blog/headers/pyos-two-years.png -categories: - - blog-post - - community -classes: wide -toc: true -comments: true -last_modified: 2024-09-08 ---- - -## It's been two years since I started working full-time on pyOpenSci - -I have been working full-time building pyOpenSci for two years now, thanks to funding from the [Sloan Foundation](https://sloan.org/) and [CZI (Chan Zuckerberg Initiative)](https://chanzuckerberg.com/). pyOpenSci has come SO FAR in two years. - -It's time to take a breath and celebrate everything the pyOpenSci community has accomplished. Before we move on to the next big thing—[our pyOpenSci Fall Festival](/events/pyopensci-2024-fall-festival.html) (more on that below)—I want to take a moment to reflect on: - -* where we've been, -* what we've accomplished, and -* the incredible community of practice that we've built. - -I'll wrap up by discussing what's next for pyOpenSci. - -## A brief history of pyOpenSci - -I founded pyOpenSci in 2018 because I saw the pain points that scientists were -facing in creating open, reproducible workflows using Python. My experience -inspired me to establish a vibrant, inclusive pyOpenSci community of practice -fueled by making open science best practices more accessible to scientists. - -pyOpenSci makes open science more accessible by [developing educational resources](/learn.html), [running training events](/events.html), running an [open software peer review process](/about-peer-review/index.html) and [partnering with other communities](/partners.html). From humble beginnings characterized by small community meetings, pyOpenSci has blossomed into a thriving community marked by: - -* a robust editorial team, -* hundreds of contributors, and -* numerous valuable community partners and friends. - -Our software peer review program has seen **over 50 packages**; we've [accepted 35 scientific Python packages into our growing library of trusted scientific Python packages](https://www.pyopensci.org/python-packages.html) and have **17 packages** in [active stages of review](https://github.com/orgs/pyOpenSci/projects/7) as I write this post. - - - -
- - A flower petal image with three flower petals and a flower center. In each petal, there is text. The first petal says software peer review; the second says community partnerships; the third says education & training. The center circle of the flower says diverse, inclusive community. - -
pyOpenSci supports scientists developing open-source software through three programs: 1) peer review of scientific software, 2) community partnerships, and 3) education & training. A diverse and inclusive community that cares about the open-source software that drives open science supports all three of these programs. -
-
- -### pyOpenSci and iterative, data-driven program design - -pyOpenSci's development and design is data-driven. I collected survey data -from scientists and Pythonistas at meetings and conferences to identify community pain points and needs. I used this data to inform the iterative development of pyOpenSci programs, events, and resources and to define pyOpenSci's mission, vision, values, and structure. - -The survey data I've collected shows what I've seen in the classroom-- scientists face many challenges when -processing, analyzing, and sharing their workflows. While reproducible -science is critical to accelerating science, sharing and publishing code is hard. - -This blog post includes quotes and data I've collected over the past 5+ years; these data have shaped the vibrant community of practice that pyOpenSci is today. - -
- - A round graphic that shows the cycle of iterative program development. The pyOpenSci logo is at the top left. The graphic title reads iterative program development. The first step says, 'A good idea gets better and better', the second says  'survey community needs,' the third says, 'evaluation based program design,' and the fourth says, 'evaluate results and make iterative improvements'. In the center is th - -
Iterative data-driven program design is a fusion of evaluation and program development that utilizes community feedback to build a program using an iterative and community-responsive approach. You test approaches and collect data about the program's effectiveness. This type of development allows an organization to grow dynamically, following community needs. -
-
- -
- -### Responses to the question: *How could pyOpenSci help you with your science, code, and software?* -{: .not_toc} - -{% include pyos-blockquote.html quote="[*I want to know...*] What are the best practices for sharing the code?" author="Anonymous" event="AGU 2019 Townhall" class="highlight" event="2019 AGU Town Hall" %} - -{% include pyos-blockquote.html quote="[*I want to... *] Streamline the development of good quality, socially responsible, and easily shareable software." author="Anonymous" event="AGU 2019 Townhall" class="highlight magenta" %} - -{% include pyos-blockquote.html quote="[*I want more *] Bullet-proof, well-documented packages for Earth science." author="Anonymous" event="AGU 2019 Townhall" class="highlight" %} - -Many earth scientists attend AGU. It's a different crowd than who you meet -at the SciPy meetings. -
- -### Why pyOpenSci tackled Python packaging - -pyOpenSci's design is also based on our experiences developing software peer review guidelines for Python packages. According to our surveys, 80% of our maintainers and reviewers identify strongly as scientists. - -As we developed our [peer review guide](https://www.pyopensci.org/software-peer-review/), it became clear that a beginner-friendly packaging guide was essential to support scientists in sharing their code because: - -1. [Our pre-review software checks](https://www.pyopensci.org/software-peer-review/how-to/editor-in-chief-guide.html#editor-checklist-template) require basic package infrastructure. Scientists must be clear about the core elements of a Python package. -2. We want to help scientists make their packages more maintainable over time by adding tests and [continuous integration (CI)](https://www.pyopensci.org/python-package-guide/tests/tests-ci.html#run-tests-with-continuous-integration) checks that run when someone submits a suggested change (or a pull request). We want to set scientists up for success. -3. We realize that Python packaging is a thorny ecosystem to navigate. I knew that pyOpenSci could help file down those thorns. - -
-### *"How could pyOpenSci help you with your science, code, and software?"* - -{% include pyos-blockquote.html quote="Training for people who can code for themselves but want to start developing software for others. Topics include style, documentation, testing, git, etc." author="Anonymous" event="AGU 2019 Townhall" class="highlight magenta" %} - -
- -#### Helping scientists navigate a complex and difficult-to-understand Python packaging ecosystem - -The packaging ecosystem has evolved rapidly. Numerous tools and approaches are available to create Python packages. Further, recent changes to ecosystem standards have led to an explosion of packaging tools like [Hatch](https://hatch.pypa.io/latest/), [Flit](https://flit.pypa.io/en/stable/), [PDM](https://pdm.fming.dev/latest/), and [Poetry](https://python-poetry.org/); other tools like Pixi and Rye are also on the horizon. Scientists often feel overwhelmed by the sheer number of options and have begged for clear guidance for years. - -
- -

QUESTION: "How could pyOpenSci help you with your science, code, and software?"

- - -{% include pyos-blockquote.html quote="[*I want pyOpenSci to*] clarify Python packaging. There are too many different mechanisms floating around..." author="" event="2021 SciPy BoF" class="highlight magenta" %} - -
- -
- This is a giant word cloud with dozens of words. The biggest words are Build, Poetry, Conda, Setuptools, Sphinx, pip, Hatch, Mamba, Twine PyPI, venv, and GitHub Actions. -
This word cloud represents around 100 responses to the question: What Python packaging tools do you most often use? It demonstrates both 1) how many tools there are to choose from and 2) how divided the ecosystem is in terms of which tools they are using. Yes, a few tools there aren't explicitly Python packaging tools. :)
-
- -There are some excellent, more advanced guides and tutorials available now, such as [PyPA's packaging tutorial](https://packaging.python.org/tutorials/) and the [scientific Python development guide](https://learn.scientific-python.org/development/). However, our resources serve a different audience. - -1. The people using our resources are often folks who find the packaging ecosystem to be overwhelming. -1. Scientists are also only sometimes familiar with various terms. For example, it's easy to confuse a build backend with a build frontend, especially when both have the word "hatch" in them (e.g., Hatch vs. Hatchling). -1. Scientists often want to avoid deciding what tools to use. pyOpenSci intends to alleviate this burden and provide a robust and practical approach. - -From a beginner's perspective, every decision is a potential opportunity to go down the wrong path, adding cognitive load. Armed with a clear understanding of the packaging pain points, I knew that pyOpenSci could simplify the packaging journey for scientists. - -If you want to learn more, my [talk at PyCon dove into the Python packaging challenges of too many options and cognitive overload.](/blog/python-packaging-friends-dont-let-friends-package-alone.html) - -### pyOpenSci guided the community towards a single way to create a Python package in under a year - -In just under a year, pyOpenSci created a [comprehensive packaging guide](https://www.pyopensci.org/python-package-guide/) that includes an end-to-end Python packaging / share your code tutorial that walks scientists through creating a package and publishing it to both [PyPI](https://www.pyopensci.org/python-package-guide/tutorials/publish-pypi.html) and [conda-forge](https://www.pyopensci.org/python-package-guide/tutorials/publish-conda-forge.html) using [Hatch](https://www.pyopensci.org/python-package-guide/tutorials/get-to-know-hatch.html). Together, the pyOpenSci community built consensus around which packaging approaches should be adopted as best practices. Building consensus around packaging decisions was challenging, but we were successful because we focused on our users first. We decided on which tools we thought would create the best packaging experience for scientists creating pure Python packages. - -[Check out the GitHub pull requests for packaging guide pages](https://github.com/pyOpenSci/python-package-guide/pulls?q=is%3Apr+sort%3Acomments-desc+is%3Aclosed) if you're curious about the feedback and discussions that we had when writing the packaging guide. Notice the number of comments on the most popular PRs. The sheer volume of comments on some of the early PRs associated with packaging build tools speaks to the various issues, complexity, and decisions we needed to make. - -## **Building Python packaging consensus and developing the pyOpenSci Python Packaging Guide** - -The success of our Python Packaging Guide is due to extensive community input. -We directed Python Packaging Guide contributors towards the objective of simplifying Python packaging for beginners. Focusing on a specific audience allowed contributors to make decisions about the content in the Guidebook more easily. It also ensured that the outcome text is easy to understand for beginners and for scientists who prefer to avoid delving into packaging nuances. In each review, we made sure to include a diverse group of reviewers, including: - -* **packaging tool maintainers:** We asked each maintainer from Flit, PDM, Hatch, and Poetry to provide feedback on our overviews of their tools, -* **[PyPA](https://www.pypa.io/en/latest/) members** who have already developed packaging resources that serve the broader Python community, -* **core scientific Python community members** who are maintaining tools like Matplotlib, Numpy, and Pandas that require more technical complex builds, and -* **scientists with varying levels of packaging expertise:** This group is most important as packaging beginners provide the lens of what is confusing and ensure that the technical content is more beginner-friendly. -The truly collaborative effort of creating the Python Packaging Guide resulted in a beginner-friendly and accurate guide that the pyOpenSci community now maintains and continuously updates. - -It was essential to have a mix of content reviewers with a range of packaging expertise to make the guide accurate and accessible. Some were packaging experts building packaging tools, and others were beginners, the audience for whom we wrote the guide. - -### What's in the pyOpenSci Python Packaging Guide? - -The pyOpenSci packaging guide provides an overview of the Python packaging ecosystem to support users who want to understand the packaging landscape. These users may want to decide on their own which tools best serve their needs. The guide also defines core packaging terms that can make the ecosystem seem more complex than it is, such as: - -* [build backend](https://www.pyopensci.org/python-package-guide/package-structure-code/python-package-build-tools.html#build-back-ends), -* [build frontend](https://www.pyopensci.org/python-package-guide/package-structure-code/python-package-build-tools.html#python-package-build-front-ends), -* [wheel](https://www.pyopensci.org/python-package-guide/package-structure-code/python-package-distribution-files-sdist-wheel.html), and -* [sdist](https://www.pyopensci.org/python-package-guide/package-structure-code/python-package-distribution-files-sdist-wheel.html). - -Our Python Packaging Guide aims to translate the technical jargon that gets in the way of new users having a successful packaging experience. Over time, we -hope that this will make packaging less confusing and more accessible to more people. - -
- Diagram that has the pyOpenSci logo in the upper left-hand corner with the pyOS flower. The diagram has four steps for making a package: create package structure, add code, add metadata to pyproject.toml, and finally, pip install package. The four steps are within arrows that point to two building shapes representing the PyPI and conda-forge warehouses. Below each of those steps, represented as storage houses, the code instructions for installing using a package from that warehouse is written out--python -m pip install yourpackage and conda install -c conda-forge yourpackage. -
Our opinionated Python packaging tutorials demonstrate one way to create a Python package using modern packaging best practices. Through these tutorials, pyOpenSci has reduced the cognitive load for scientists who want to share their code. This graphic represents the steps a user needs to understand to make their code installable and create a basic Python package.
-
- - -### Creating opinionated Python packaging tutorials - -
- This is a graphic that shows the results to a survey question. The question is--have you created a Python package before? The three options are Yes (18%), no (59%), and I struggled trying (24%) -
This response ratio of most people either struggling to create a package or never having created a Python package before is common when I ask this questions at events. More often than not, people want help learning packaging.
-
- -In addition to the ecosystem overview provided in the guide, I knew that we needed to have opinionated, complete Python packaging tutorials that guided users through the entire packaging process including: - -* [creating a package](https://www.pyopensci.org/python-package-guide/tutorials/create-python-package.html), -* publishing it on [PyPI](https://www.pyopensci.org/python-package-guide/tutorials/publish-pypi.html), and -* [conda-forge](https://www.pyopensci.org/python-package-guide/tutorials/publish-conda-forge.html). - -Most scientists (and packaging beginners) want to get the job done; they don't want to become packaging experts. A community presenter made this statement directly at the PyCon US 2023 Python packaging summit: - -> I just want to create a Python package. Where is the tutorial or -> documentation that teaches me how to do that? -{: .highlight-quote } - -Our Python packaging tutorials went through the same community development and review process as the rest of our Python Packaging Guide. We have also begun teaching the lessons; most workshop attendees have successfully created their first Python packages! - -
- Another survey result image. The question is: were you able to create a package? 91% responded yes (10 people), 9% (1 person) said no; no one responded 'I'm not sure' -
It's always hard to collect data at the end of a long workshop. Here, 11 out of 30+ people responded. Most people in our workshop were successful. We worked with the one person who responded "no" after the survey at the end of the workshop. They successfully made their first package with our support! This feedback was similar to the success rates at our first online workshop, held in May 2024.
-
- - -### Teaching online lessons is the best way to ensure they stay current - -I have created hundreds of open online lessons both for ecologists [NEON](https://www.neonscience.org/resources/learning-hub/teaching-modules/quantifying-drivers-and-impacts-natural-disturbance-events) and earth and environmental scientists (formally earthdatascience.org). One of the biggest challenges is that it's easy for data science lessons to become dated quickly --especially in the rapidly evolving data science space. By teaching the lessons, we can update them regularly as the ecosystem evolves. We also often have users review the lessons at our [annual sprint events](/blog/pyopensci-pyconus-2024-sprints.html) to test them out; more on pyOpenSci beginner-friendly sprints below. - - - - -{% include pyos-blockquote.html quote="Hi Leah. Thanks for the course today. I really enjoyed it. I heard about it from your post here in the Python channel so I'm glad you shared it here. I'll keep my eye out for more coming up and will be referring to the tutorials and guides on your site. Hopefully you work out the spatial chat because that seemed to have a lot of potential! I also want to let you know that I got a ton of value out of your materials on the CU Open Earth data analytics site, and it's still my go-to resource to point people to when they ask me how to get started learning the open source spatial stack. So thank you!" author="Michael Harty, Software Underground Community" class="highlight" %} - - - - -In addition to the valuable feedback we receive from our community, the data we collect through [Matomo web analytics](https://matomo.org/) shows our guide's usage and growth. -I will leave web and social media growth, which have also shown extraordinary growth for future posts! - -### The power of community: Translating the guide & tutorials to other languages - -The success of our packaging guide has been remarkable, thanks to the tremendous input and feedback from the community. What began as a simple guide has evolved into a collaborative project, with enthusiastic participation from contributors worldwide. - -{% include pyos-blockquote.html quote="@leahawasser @pyOpenSci clicking through and eventually found myself looking at 'what is a Python package' and involuntarily performed a standing ovation. bookmarked it as an example of great docs for an incredibly complex subject with many meanings in many different contexts" author="Mastodon Toot" class="highlight" %} - -[View toot on mastodon](https://circumstances.run/@hipsterelectron/112557545629456885) - -At our last PyCon sprint, a then-new contributor, [Felipe Moreno](https://www.github.com/flpm), set up translation infrastructure for our guide. The translation infrastructure uses a Sphinx extension that allows contributors to translate as much or as little of the guide as they wish. This process supports -small iterative pull requests that any contributor with little time can make. - -```markdown -#: ../../documentation/hosting-tools/intro.md:3 -msgid "" -"The most common tool for building documentation in the Python ecosystem " -"currently is Sphinx. However, some maintainers are using tools like " -"[mkdocs](https://www.mkdocs.org/) for documentation. It is up to you to " -"use the platform that you prefer for your documentation!" -msgstr "" -"Sphinx es la herramienta más común para construir documentación en el " -"ecosistema de Python. Sin embargo, algunos mantenedores están usando " -"herramientas como [mkdocs](https://www.mkdocs.org/) para generar la " -"documentación. ¡Es tu decisión usar la herramienta que prefieras para tu " -"documentación!" -``` - -Felipe's translation infrastructure contribution has sparked significant community momentum. As I write this, pyOpenSci contributors are actively translating our Python Packaging Guide into Spanish and Japanese; we have had dozens of pull requests. One contributor may even teach the pyOpenSci Python packaging lessons at [PyCon Japan](https://2024.pycon.jp/en) next year! This entire effort underscores the power of community when guided in the right direction. - -Our guide is becoming a global resource! - -You may be wondering where all of these new contributors are -coming from. We get many new active contributors when we lead events at core meetings. - -I'll talk about that next. - -## pyOpenSci at PyCon and SciPy 2024 -- sprints, talks and new contributors - -Sprints are one of my most favorite types of events to hold because they involve this [cool mix of learning, helping and community]({{ site.baseurl }}/blog/pyopensci-pyconus-2024-sprints.html). - -### pyOpenSci sprints at Python meetings - -We started holding sprints at PyCon US in 2023. -Engagement at pyOpenSci sprints has grown significantly since we started leading these events. This year alone, we engaged with over 50 contributors at sprints who have submitted 86 issues and pull requests! - -What is most impressive is many of our sprint participants have continued to contribute to pyOpenSci after our in-person sprint events. - -While pyOpenSci operates mainly as an online community, I find there is no better way to build a core community than by holding in-person events at large meetings. While we get plenty of expert Pythonistas at our sprints, our sprint events are always beginner-friendly. Many first-time contributors help pyOpenSci with various open issues on our [help-wanted GitHub project board](https://github.com/orgs/pyOpenSci/projects/3) while submitting their first pull requests and issues to GitHub. - -### pyOpenSci beginner-friendly sprints help us and also the community - -Beginner-friendly sprints represent a true win-win for both contributors and pyOpenSci. Contributors learn new skills, and pyOpenSci gets help with the vital work that we are doing. - -
- -### What is a community sprint? -{: .no-toc} - -Community sprints are collaborative coding and documentation update sessions where new and experienced contributors work together on open-source projects. These sprints provide a supportive environment with guidance from project maintainers or experienced developers, helping participants contribute effectively. They are an excellent opportunity for learning, networking, and making meaningful contributions to the open-source community. [You can read more about that here.](https://www.pyopensci.org/blog/pyopensci-pyconus-2024-sprints.html) -
- - -| Year | PRs & Issues | Contributors | -|----------|----------|----------| -| 2023 | 41 | 12 | -| 2024 | 86 | 39 | - - -### My talks at SciPy and PyCon US 2024 - -Last year, I gave a talk at PyCon US at the [Maintainers Summit](https://www.pyopensci.org/blog/pycon-2023-packaging-presentation-sprints-leah-wasser.html#python-packaging-packaging-packaging). -This year, I gave talks about both [SciPy's Maintainers Summit](https://www.youtube.com/watch?v=uCLlshkTBM0) and at [PyCon US - in the main track](https://www.youtube.com/watch?v=uCLlshkTBM0)! At PyCon, I spoke about how pyOpenSci is leveraging the community and building consensus on the thorniest topic: Python packaging. At SciPy, I focused on bringing the scientific community together to help solve Python packaging. I also overviewed our community-run scientific Python software peer review program and the success that we've -had collaborating with [other partner communities, such as Astropy](https://www.pyopensci.org/software-peer-review/partners/astropy.html). - -Here is my SciPy video if you want to check it out now. :) - -{% include video id="uCLlshkTBM0" provider="youtube" %} - - -## pyOpenSci's software peer review program is growing, too - -Funding has propelled pyOpenSci's peer review process forward. While pyOpenSci has run peer review since 2019, funding allowed us to document, formalize, and make the peer review program more sustainable and scalable. The [peer review guide](https://www.pyopensci.org/software-peer-review/) was one of the first things I worked on in the Fall of 2022. The goal of this guide was to -define each role in the software peer review process, -define policies around our review package scope, how we review, how we determine what is in scope, and more. -The guide also documents our [community partnership program](https://www.pyopensci.org/partners.html), which we launched in 2024 through our collaboration with [Astropy](https://www.pyopensci.org/software-peer-review/partners/astropy.html). - -In four months, we published a shiny new peer review guide. In January 2023, we re-launched peer review again. Peer review submissions increased dramatically starting in January 2024 (see below). - -
- A cumulative sum plot with months on the x-axis and number of review issues submitted on the y-axis. The curve starts with a small slope in 2019. Then, around January 2023, the slope becomes significantly steeper, indicating a dramatic increase in submissions when we reopened the peer review. At that point in the plot, there is a label that says 'Full-time funding'. -
It is clear the impact that 1) working on our peer review guide and governance plus 2) funding, which allowed me to devote all of my professional time to pyOpenSci, had on our peer review program. It has been incredibly successful in the past year! And true success lies in the hands of the community that leads the review process. Today, on average, pyOpenSci has 10-14 packages in our software review queue. -
-
- - - -You can see more of our [peer review metrics on our MyST Markdown metrics dashboard here](https://www.pyopensci.org/metrics/peer-review/peer-review-status-dashboard.html) -{: .button } - - -### Is pyOpenSci's peer review program diverse? - -Diversity, equity, inclusion, and accessibility (DEIA) are core to everything that pyOpenSci does. But how do we know that we are aligning with that core value? - -Data can help us here, too. Here are a few insights from 93 people who have participated in our peer review program survey over the past two years. Our peer review participants represent a healthy mix of students, industry professionals, and academics. - -Almost half of the participants identify as having some computer science application in their work; 80% self-identify as scientists (as mentioned above). - -About 40% of our peer review participants identify as female, non binary, or non conforming. - -#### Open Source metrics - -Some of the open-source responses surprised me: - -* **60% of people maintainers have funding** to do their work! (*This number surprised me given the challenges I have seen in funding open source work.*), and -* **70% of maintainers** report that outside contributors contribute to their projects through issues and pull requests. - -I'll dedicate another blog post to examining this data more thoroughly. Still, you can get the gist from the above summary: We have a strong representation of people from different career stages. However, we still need to do more to support increased gender and identity diversity. - -## A thoughtful, kind, and supportive community is what makes pyOpenSci special - -Every morning, I wake up and am excited to begin my work. It doesn't matter if I'm working on budgets and other Executive Director-type tasks or developing educational content and teaching (my two favorite things); I can't wait to see what messages pop up in my inbox, be it email or GitHub. I love my job because the pyOpenSci community is extraordinary. - - People care about our organization's mission to help scientists make their science more open and collaborative. So they can solve the world's greatest challenges. - - The community wants to help--be it each other or pyOpenSci as an organization. - - The community is kind; the discussions are engaging and people help each other. - -{% include pyos-blockquote.html quote="I love the friendliness and positive energy around the combination of science+computing (in Python, too)! It's a lovely community of practice!" author="Titus Brown, Faculty - University of California, Davis" class="highlight magenta" %} - -The community also cares a lot about the scientific Python ecosystem and wants to see more robust software. - -{% include pyos-blockquote.html quote="As maintainers of the Scientific Python library, we see great potential in pyOpenSci. Test, code, documentation, and internationalization. These are things we have gradually accumulated know-how for in order to create a robust library on GitHub. Now, these are about to be integrated by the pyOpenSci project." author="Tetsuo Koyama - PyVista maintainer" class="highlight" %} - -They care about the packaging guidelines we have worked so hard to co-create and the impact those guidelines and online resources have on scientists who are just trying to get their jobs done. - -{% include pyos-blockquote.html quote="I'm hopeful that the standards established by pyOpenSci will help alleviate the reproducibility crisis, increasing public trust in science, accelerating progress, and making all our jobs a little bit easier!" author="Jackson Burns, MIT" class="highlight magenta" %} - -And, like me, the community cares deeply about education. The community wants to help -eliminate the packaging and open science thorns. They want to make packaging (and using Python) more -accessible to more people. - -{% include pyos-blockquote.html quote="I love the educational aspect of pyOpenSci and how it focuses on removing the technical friction that prevents people from contributing to open source and open science. The community makes a conscious effort to create a welcoming culture and provide a safe place to learn for beginners. It helps prepare people for even bigger future contributions to the open science and open source movements." author="Felipe Moreno - Tech Industry Professional" class="highlight" %} - -Carol below was more than just a participant! She also helped many students -in their workshop learning experience. - -{% include pyos-blockquote.html quote="As a participant in the first packaging workshop, I found that individual attention and focus on success were outstanding. The organization and approach to teaching a scientist how to create a package helped build skills and ongoing productivity." author="Carol Willing - Open Source & Open Science Leader, Project Jupyter, Python Core Dev" class="highlight purple" %} - -## Building a sustainability model for pyOpenSci so we can continue to thrive - what's next - -There’s a strong demand for the work pyOpenSci is doing. Still, every nonprofit or fiscally sponsored project has to ask: - -*How do we keep this going long-term?* - -Writing grants to bring in funding is essential, but I need to do more than just write grants. Writing grants takes tremendous energy, with only a ~10-20% chance of success. Also, grant calls usually focus on innovation. They do not often support project maintenance and daily operations. - -To ensure that pyOpenSci is sustainable, we’re rolling out paid online training events. Paid events are a revenue model used by other nonprofits in our ecosystem: - -NumFocus runs PyData and SciPy meetings -The Python Software Foundation runs PyCon as a significant fundraising event. -These paid events will support the low-cost training, event scholarships, free online tutorials, and resources we will continue to create and publish online. - -## What's next for pyOpenSci - The Road Ahead -Our next training event is the inaugural pyOpenSci Fall Festival--a week-long event that teaches skills needed to: - -* write cleaner, more modular code, -* package and share code, -* publish and cite code, and -* create reproducible reports that connect code, data, and outputs into a dynamically produced interactive publication. - -We also will develop collaborative GitHub lessons following the BSSw fellowship I received this year. - -
-## About Me -My name is Leah, and I'm the executive director and founder of pyOpenSci. I have over 20 years of experience in both academic and nonprofit spaces and have dedicated my career to helping scientists overcome the challenges of open science. I've built and led two successful data science programs: - -1. **NEON Data Skills Program** at NEON, and -2. **Earth Analytics Program** at CU Boulder. - -I'm now building **pyOpenSci**--a vibrant, active, and diverse community of practice that supports open science and the open-source software that drives that science. - -The programs I build have consistently stayed at the cutting edge of technology through continual evaluation and a data-driven approach. Throughout my career, I've observed significant gaps between the innovative tools being developed and the training scientists receive, which has driven my work in bridging these gaps. -
diff --git a/_posts/2024-09-17-pyopensci-sustainability-plan-2024.md b/_posts/2024-09-17-pyopensci-sustainability-plan-2024.md deleted file mode 100644 index 35807c15..00000000 --- a/_posts/2024-09-17-pyopensci-sustainability-plan-2024.md +++ /dev/null @@ -1,173 +0,0 @@ ---- -layout: single -title: "A Blueprint for the Future: pyOpenSci's Sustainability Model" -blog_topic: community -excerpt: "pyOpenSci is exploring various sustainability methods to supplement our valued grant funding. Learn more about what we are exploring." -author: "Leah Wasser" -permalink: /blog/pyopensci-funding-sustainability.html -header: - overlay_image: images/blog/headers/pyopensci-sustainability.png -categories: - - blog-post - - community -classes: wide -toc: true -comments: true -last_modified: 2024-10-15 ---- - - -## Introduction - -Funding is critical to the success of any nonprofit organization. In this blog post, I'll outline pyOpenSci's sustainability plan. We define sustainability as building a diverse funding "portfolio" that includes grants and internally generated revenue sources to support our mission, growth, and core activities. - -## How funding has propelled pyOpenSci forward - -Funding from [Sloan Foundation](https://sloan.org/) catalyzed pyOpenSci's growth by enabling me to dedicate full-time effort to growing the organization's impact in September 2022. This full-time commitment, paired with support from a fiscal sponsor, [catalyzed pyOpenSci's recent growth and success](/blog/what-pyopensci-accomplished-with-two-years-of-funding.html). - -
- An image titled pyOpenSci timeline. showing key milestones from 2018 to 2024. The timeline is marked with flower icons and a small rocket to represent significant events. Key points include: 2018: Community Meetings Begin, 2019: Software Peer Review Launch,2021-2022: Sloan Funding, Fall 2022: Packaging Guide Rewrite Begins, January 2023: Peer Review Relaunched, Fall 2023: CZI funds pyOpenSci, Spring 2024: pyOpenSci launches training and tutorials. The timeline progresses diagonally from the bottom left to the top right, with a rocket heading toward a bright light in the top right corner. -
I founded pyOpenSci in 2018. It was mostly a side effort at the time driven forward by small community meetings and a pilot of a scientific Python peer review process. -
-
- -For example, our scientific Python [software peer review program](https://www.pyopensci.org/software-peer-review/) significantly expanded after we reopened it in early 2023, following our governance and documentation improvements. Today, we average 10-14 packages in our review queue at any given time and have 18 volunteer editors and dozens of reviewers who drive peer review forward. - -You can see this profound impact in the plot below. - - -
- A cumulative sum plot with months on the x-axis and number of review issues submitted on the y-axis. The curve starts with a small slope in 2019. Around January 2023, the slope becomes significantly steeper, indicating a dramatic increase in submissions when we reopened the peer review. At that point in the plot, there is a label that says 'Full-time funding'. -
It is clear the impact that 1) working on our peer review guide and governance plus 2) funding, which allowed me to devote all of my professional time to pyOpenSci, had on our peer review program. It has been incredibly successful in the past year! And true success lies in the hands of the community that leads the review process. Today, on average, pyOpenSci has 10-14 packages in our software review queue. -
-
- - - - -{% include pyos-blockquote.html quote= "The Sloan Foundation's early support has propelled pyOpenSci's growth, creating opportunities that ultimately helped us secure additional funding from CZI." class=" highlight magenta" %} - -Today, the results of this momentum are clear. pyOpenSci has over **250 contributors** and runs a vibrant global Python software peer review process led by [**18 volunteer editors**](https://www.pyopensci.org/about-peer-review/index.html#meet-our-editorial-board), 74 reviewers, and 78 open source maintainers. - -### Funding supports beginner-friendly open source sprints - -Funding has also allowed us to run events. In the past year, over 51 people have participated in our beginner-friendly sprints, submitting 39 issues and 88 pull requests to pyOpenSci. Many of these pull requests and issues are first-time contributions from scientists and researchers who were excited about and nervous about contributing to an open source repository. - -Contributions to pyOpenSci have also skyrocketed in the past two years. - -
- Bar chart titled 'Staff vs. Volunteer Contributor by Quarter.' The chart shows counts of contributions made by staff and volunteers from 2018 Q4 through 2024 Q3. Contributions are stacked, with purple representing staff and green representing volunteers. The chart reveals a significant growth in contributions over time, particularly from 2022 onward, with a notable increase in both staff and volunteer contributions in 2023 and 2024. The pyOpenSci logo is in the bottom-right corner. -
A bar plot showing staff vs volunteer contributions on GitHub over time. Funding has allowed us to support more volunteer activity over the past 2 years. -
-
- - -### Our Python packaging guide is making a splash - -One of our most impactful activities has been developing our community-vetted [Python packaging guide](https://www.pyopensci.org/python-package-guide/index.html), which includes beginning-to-end Python [packaging tutorials using Hatch](https://www.pyopensci.org/python-package-guide/tutorials/get-to-know-hatch.html). Developing our packaging guide has been a genuinely collaborative effort. We have built consensus around critical packaging decisions with numerous opinions on what is "best." In just under a year, our packaging guide's use has doubled, with ~20,000 unique page views in 2024. - -{% include pyos-blockquote.html quote=" @leahawasser @pyOpenSci clicking through and eventually found myself looking at 'what is a Python package' and involuntarily performed a standing ovation. bookmarked it as an example of great docs for an incredibly complex subject with many meanings in many different contexts" author=" Mastodon Toot" class=" highlight" %} - -Our work around Python packaging has led pyOpenSci to take a more active role in broader Python packaging discussions, contributing to conversations at events like the annual [PyCon US packaging summit ](https://us.pycon.org/2024/events/packaging-summit/) and the [PyCon US maintainers summit](https://www.pyopensci.org/blog/pycon-2023-packaging-presentation-sprints-leah-wasser.html#python-packaging-packaging-packaging). - -Our work has not only made packaging more accessible to more scientists and researchers but also fostered a collaborative and inclusive community of practice where scientists, software maintainers, and Pythonistas engage, chat about open source and open science challenges, and support each other. - - -## Diversification of funding sources - -Diversifying our funding sources is essential to pyOpenSci's success. We've been fortunate enough to receive two grants. -This runway has given us ~three years to grow and demonstrate impact. As of September 2024, we are ~two years into that runway. We now need funding to become sustainable. - -Grant-based funding is tricky as it rarely supports project operations or "maintenance." Our long-term plan is to combine grant funding with paid training and events to support pyOpenSci's operations. - -In the short term, we need funding to develop our paid training model while continuing to develop the governance that allows our free programs, such as software peer review and development and maintenance of open education resources, to continue. - -More on that below. - -## Building sustainability: pyOpenSci's training program - -pyOpenSci is building a paid training event program that supports sustainability. This training will be delivered predominately online, but we will also continue to hold in-person events such as the [packaging workshop that we taught at SciPy 2024](/blog/pyos-scipy-2024-recap.html#pyopenscis-first-scipy-tutorial-was-a-huge-success-create-your-first-python-package). - -While we will charge for some training, access is integral to our training program. We will continue to develop ways to support broad participation and engagement in our programs, which is core to the pyOpenSci mission. - -We offer scholarships to ensure that our events are inclusive. Scholarships worked well for our [first successful online packaging training event](/events/april-2024-create-python-package-pyopensci-online-workshop.html) and allowed five people to attend our training at no cost. For our upcoming [Fall Festival](/events/pyopensci-2024-fall-festival.html) (more on that below), we are also developing a sponsorship program that will allow companies, organizations, and individuals to support scholarships directly. - -### Open education will always be a priority for pyOpenSci - -We will also continue to offer free, community-vetted, open education resources. If you know me, you also know that I am deeply committed to creating high-quality open education resources. Open education has been a part of every program that I've built (for example, www.earthdatascience.org) and NEON Data Skills. - -Our [Python packaging guide](https://www.pyopensci.org/python-package-guide/), which has ~80 contributors as I write this, is one successful example of community-vetted open education resources that the pyOpenSci community has co-created. It now has thousands of users. - -## With growth comes a need for governance - -As pyOpenSci's growth welcomes new contributors and community members, we must create space by creating processes, governance, and documentation around our programs to support continued community engagement. Doing this well requires require time and resources. - -Reflecting on these accomplishments, I am committed to ensuring that pyOpenSci's core programs and vibrant community of practice continues to thrive. The longevity of our mission hinges on developing a durable sustainability model that enables pyOpenSci to: - -* continue supporting the vibrant pyOpenSci community of practice, and -* expand our programs to develop education resources that teach open science, reproducibility, and packaging topics. - - -### How pyOpenSci allocates funds to support efficient growth - -I like to think of pyOpenSc as a scrappy, efficient organization. Scrappy means we are deeply committed to managing our resources as efficiently as possible. Our small, core paid team optimizes our time and online infrastructure to reduce costs and maximize impact. - -We minimize overhead costs by: - -* leveraging & building open source tools, -* focusing funds on automating workflows using tools like CI (continuous integration) to reduce manual effort, and -* prioritizing support for essential, high-impact activities that drive our mission forward. - -Looking to the future, we will only grow our core team as it aligns with our core budget. This strategic approach enables pyOpenSci to maintain the quality and reach of pyOpenSci programs while building a sustainable future. - -### Other nonprofits have done this well - -I am inspired by other organizations that have found their sustainability balance such as NumFOCUS and the Python Software Foundation. These organizations use events such as: - -* SciPy meetings, -* PyData meetings, and -* PyCon US - -These meetings are all community-driven. Each event funds the organization's core mission, vision, and values. - -I am further inspired by the membership model that The Carpentries has successfully employed to build core sustainability. - -pyOpenSci will experiment to find the balance between freedom, openness, and sustainability in our training program. I'm confident that we will succeed. I'm excited to pilot new approaches and continue evaluating how each approach impacts our sustainability goals while further developing and maintaining a diverse and inclusive community. - -As always, we want to hear from you. Your feedback helps shape our programs and ensures the long-term success of pyOpenSci. - -## What's next for pyOpenSci - The Road Ahead - -So, what's next for pyOpenSci's training program? We will continue to run different types of pilot training events, experimenting with various platforms, scales and approaches. These events will include both larger event like our upcoming Fall Festival and smaller workshops like the [packaging workshop that we taught in the Spring 2024](https://www.pyopensci.org/events/april-2024-create-python-package-pyopensci-online-workshop.html). - -We will experiment with a suite of training opportunities held at various price points using different types of sponsorship and scholarship models. - -Along the way, we want to hear from you! We know you want pyOpenSci to succeed too. We want to understand: - -* what's working well, -* what could be better, -* what topics should we cover, -* where do you see opportunities to combine sustainability and training? - -As always, we'll collect your feedback as a part of each event. Your feedback and ideas are critical to iteratively improving upon and building out pyOpenSci so we can better serve the community. We look forward to embarking on this training program path with you. - -### Our next event - Fall Festival - -The next pilot event in our training series is our [virtual Open Science Fall Festival](/events/pyopensci-2024-fall-festival.html), October 28 - November 1 2024. This event is a combination of free talks, paid workshops, community engagement, and different sponsorship and scholarships models to ensure broad participation. - -We've wrapped a lot together into one event! - -Our goals for the Fall Festival are to: - -* empower you with technically-relevant open science skills, -* call attention to and celebrate new and upcoming tools that support open reproducible science -* build community, and -* help raise funds for pyOpenSci operations that allow us to continue supporting community work. - -We're excited for this event! I'm excited to continue to test out these different approaches. And i'm excited about building and learning together with you and the broader scientific Python open science community. - -You can learn more about the [Open Science Fall Festival, here](/events/pyopensci-2024-fall-festival.html). Please reach out if you have any questions. - -We look forward to seeing you there! - -~ Leah with support from the [pyOpenSci Executive Council - Tracy Teal & Karen Cranston](/our-community/index.html#executive-council-leadership--staff) diff --git a/_posts/2024-10-10-pyOpenSci-celebrates-Inessa-pawson.md b/_posts/2024-10-10-pyOpenSci-celebrates-Inessa-pawson.md deleted file mode 100644 index 34fccd34..00000000 --- a/_posts/2024-10-10-pyOpenSci-celebrates-Inessa-pawson.md +++ /dev/null @@ -1,71 +0,0 @@ ---- -layout: single -title: "pyOpenSci celebrates Inessa Pawson" -blog_topic: community -excerpt: "Inessa’s leadership and dedication have profoundly shaped the open source community, from NumPy to NumFOCUS. Learn more about her contributions and impact." -author: "Leah Wasser" -permalink: /blog/pyopensci-celebrates-inessa-pawson.html -header: - overlay_image: images/headers/pyopensci-floral.png -categories: - - blog-post - - community -classes: wide -toc: true -comments: true -last_modified: 2024-12-05 ---- - -# pyOpenSci Celebrates Inessa - -Today, I want to take a moment to celebrate a devoted pyOpenSci community member, colleague, and friend, [Inessa](https://github.com/InessaPawson/InessaPawson), -who was just awarded the NumFOCUS Community Leadership award. In my opinion, no one is more deserving of recognition for their contributions and dedication -to the open source community. Inessa's day job at [OpenTeams](https://otincubator.com/team.html) is devoted to open source, where she serves as the Open Source Program -Manager. But her open source work extends far beyond her professional role! - -
- -  Portrait of Inessa Pawson, recipient of the NumFOCUS Community
-    Leadership Award 2024. In the image, Inessa has light skin, blue eyes, and is wearing red lipstick. Her hair is styled up, and she is dressed in a black
-    blazer with a professional, confident expression. The background on the right side of the image is purple with the pyOpenSci logo at the top and a headline
-    that reads: pyOpenSci Celebrates Inessa. Below this, there is text that announces her award. The image of the award certificate is shown next to her name.
-    The text on the image highlights Inessa’s leadership in the scientific Python community, including her involvement with the SciPy US,PyCon US, NumPy, and NumFOCUS. - -
Inessa receives the 2024 NumFOCUS Community Leadership award. -
-
- -"Inessa Pawson receives the NumFOCUS Community Leadership Award 2024. Inessa, a pyOpenSci Advisory Council member, is celebrated for her impact in the scientific Python -community, including co-organizing the SciPy US and PyCon US Maintainers Summits, serving on the NumPy Steering Council, and leading a number of contributor -experience projects." - -I have tremendous admiration for Inessa's commitment to supporting the open source community, particularly in the scientific Python ecosystem. Her efforts reach -far beyond pyOpenSci, impacting the broader open science and Python landscapes. - -## Inessa's open source contributions - -To foster opportunities for cross-project collaboration in open source and open science, Inessa has co-organized the [SciPy US conference](https://www.scipy2024.scipy.org) -since 2020, establishing and serving on several committees (Maintainers Track, Teen Track, Tutorials, Social Activities, Mentorship Program, Hybrid Committee). She has -also organized the NumFOCUS Project Summit for the past two years (2023 and 2024) and serves on the Scientific Python Project SPEC Steering Committee. - -Inessa has championed the NumFOCUS Code of Conduct (CoC) reform and the creation of the NumFOCUS CoC Working Group. - -Inessa is also a core contributor to the scientific Python community. Amongst many commitments within the scientific Python ecosystem, she has been serving -on the **NumPy Steering Council** since 2021 and co-leading the **scikit-learn survey team** since early 2024. Inessa is a recipient of the 2019 NumPy -New Contributor Award. - -What truly sets Inessa apart is her relentless commitment to community building. She is a founder and organizer of the CHAOSS Scientific Research Working Group, -Tech Alliance of SWFL, PyLadies South Florida, and PySWFL (Python user group in SWFL). Inessa doesn’t just participate; she builds frameworks and fosters connections -to ensure the sustainability of the communities that fuel scientific progress. - -## Inessa cares about DEIA - deeply - -Inessa’s leadership and passion for fostering inclusive, supportive communities also extend to her role on the pyOpenSci Advisory Council. She has always been -there to talk through challenges as pyOpenSci has grown over the years. I see her as a key advisor behind pyOpenSci's success. - -## Inessa is also a devoted mom - -On top of all these accomplishments, Inessa is a dedicated mother to two wonderful, bright children who you may have run into at a PyCon US or SciPy US conference! -And, on a personal note, Inessa is a wonderful friend and an incredible human being. I can't think of anyone more deserving of recognition. - -Her wide-ranging contributions make her a true leader in the open source community. I’m so happy to be able to take a moment to celebrate her impact. diff --git a/_posts/2024-10-28-human-dimension-data-science.md b/_posts/2024-10-28-human-dimension-data-science.md deleted file mode 100644 index c7658b4c..00000000 --- a/_posts/2024-10-28-human-dimension-data-science.md +++ /dev/null @@ -1,555 +0,0 @@ ---- -layout: single -title: "The Human Dimension to Clean, Distributable, and Documented Data Science Code" -blog_topic: education -excerpt: "Discover how to create clean, accessible, and impactful data science code by focusing on the human side of coding practices. Here, Eric Ma shares insights from his talk at the 2024 pyOpenSci Fall Festival. Learn key open science principles, including readability, cognitive load, and the toolmaker’s mindset, and explore practical strategies to enhance your work" -author: "Eric J. Ma" -permalink: /blog/human-dimension-clean-documented-data-science-code.html -header: - overlay_image: /images/blog/headers/eric-ma-header.png -categories: - - blog-post - - community -classes: wide -toc: true -comments: true -last_modified: 2025-01-07 ---- - -This post was originally posted on [Eric's blog](https://ericmjl.github.io/blog/2024/10/25/the-human-dimension-to-clean-distributable-and-documented-data-science-code/) in support of the [2024 pyOpenSci Open Science Fall Festival](/events/pyopensci-2024-fall-festival.html). - -
- - - An illustration of a diverse group of people collaboratively analyzing and discussing code displayed on a large transparent screen, surrounded by books, plants, and abstract symbols, set against a vibrant gradient background. - -
- - -## Introduction - -Since 2016 (8 years now!), -I've been advocating for data scientists -to apply basic software development practices in their work. -This means making sure that one's work is well-documented -- and thus easily understandable, -working in a way that is portable across machines -- and thus easily accessible, -making sure that one's code is modular -- and thus easy to reuse, -and ensuring that one's code is well-tested -- and thus reliable. (Table 1) -In this post, -we'll dive deeper into why it is crucial for making your work impactful -to consider the psychology of people -who read, install, and use your work. -We'll explore the "why" behind the "what" -that will be covered in the upcoming pyOpenSci training course. -This training course will be teaching you a lot of valuable skills - the "what to do" - -and I'd like to help reinforce the "why" behind all of these. -By understanding the reasoning and motivation behind these practices, -you'll be better equipped to apply them effectively -and adapt them to your specific needs in data science projects. - -| Practice | Benefit | -|--------------------------|-----------------------| -| Well-documented | Easily understandable | -| Portable across machines | Easily accessible | -| Modular code | Easy to reuse | -| Well-tested code | Reliable | - -_Table 1: Key practices and their benefits in data science code development._ - -## Key concept 1: readability and cognitive load - -When we write code, -it's easy to forget that we're not just communicating with machines, -but with other humans as well - including our future selves. -This principle is deeply ingrained in Python's philosophy, -as expressed in "The Zen of Python" which states, -"Readability counts." -Python's emphasis on readability over performance -underscores the importance of human-friendly code. -Readability is crucial because it directly impacts the cognitive load -placed on anyone trying to understand or use your code. - -### The importance of readability - -Readable code offers several significant benefits. - -First and foremost, it enables faster comprehension. -When code is clean and well-structured, -readers can quickly grasp its purpose and functionality, -allowing them to understand the logic and flow more efficiently. - -This leads to the second advantage: easier maintenance. -Code that is easy to read is also easier to debug, update, and extend, -saving valuable time and resources in the long run. - -Furthermore, readable code greatly improves collaboration! -When team members can easily understand each other's code, -it fosters better teamwork and enables them to build upon each other's work more effectively. -This synergy can lead to more innovative solutions and faster project completion. - -Lastly, clear and readable code reduces the likelihood of errors. -When code is easy to interpret, -there's less chance of misunderstanding its intent or functionality. -This clarity minimizes the risk of introducing bugs during modifications -or when integrating with other parts of the system, -ultimately leading to more robust and reliable software. - -### A cautionary tale from my graduate school days - -During my graduate studies, -I developed a complex data analysis project -for identifying putative reassortant influenza viruses -from the Influenza Research Database (IRD). -While the algorithm was scientifically sound and potentially valuable, -its implementation left much to be desired -from a software engineering perspective. - -The project consisted of more than 10 separate Python scripts -that had to be executed in a specific order. -There was little to no documentation, -and the code itself was not written with readability or maintainability in mind. -You can find the original code repository [here](https://github.com/ericmjl/influenza-reassortment-detector) -to see an example of what **not** to do. - -The project had several significant drawbacks: - -- **Lack of modularity**: The scripts were tightly coupled, making it difficult to reuse or modify individual components. -- **Poor documentation**: Without clear documentation, understanding the purpose and workflow of each script was challenging. -- **Difficult deployment**: The complex execution order and tight coupling to a GridEngine HPC cluster made it nearly impossible for others to deploy or use the tool effectively. - -The consequences of this poor code quality -became apparent when the IRD team -expressed interest in incorporating my algorithm into their database. -I remember being engaged with them in discussion, -and was excited to see my work potentially being used -by researchers around the world. -However, the barrier to deployment was immense -due to the scrappy implementation and lack of documentation. -While I never knew what the real reasons were behind-the-scenes, -I can only imagine that the lack of a well-documented, -modular, and easily deployable solution -was a major barrier to the adoption of my work. - -This experience taught me a valuable lesson -about the importance of writing clean, well-documented, and easily deployable code, -even in academic settings. -Had I invested more time in proper software development practices, -my work could have had a much broader impact -and been more easily adopted by the scientific community. - -### The impact of readability - -By prioritizing readability, -we can significantly enhance the overall quality and impact of our data science projects. -Readable code: - -1. Reduces the cognitive load on users, making it easier for them to understand and use your code. -2. Encourages collaboration and knowledge sharing within the data science community. -3. Improves the maintainability and longevity of your codebase. -4. Enhances the overall credibility and professionalism of your work. - -Remember, the goal is to create tools and analyses -that others can easily access, understand, and build upon. -By embracing the human dimension of our code, -we not only improve our individual projects -but also contribute to the growth and accessibility -of the entire field of data science. - -## Key concept 2: User-friendly installation and setup - -The ease of installation and setup -can make or break the adoption of your data science package. -Even the most brilliant code is useless -if users can't get it running on their systems. -Let's explore the psychological barriers users face -and how to overcome them. - -### The psychology of installation friction - -When users encounter complex installation processes, -they often face several psychological barriers. - -**Barrier 1 - Complexity**: -First, the sheer complexity can be overwhelming, -causing users to abandon the installation before they even begin. -This initial hurdle can be particularly daunting -for those who are less technically inclined -or new to the field. - -**Barrier 2 - Frustration**: -Secondly, -users may experience frustration when installations fail -or when they encounter unclear error messages. -These setbacks can be discouraging, -potentially leading users to give up on further attempts to install and use the software. -I remember the early days of installing SciPy! -Nowadays, it's so easy: -`pip install scipy` or `conda install scipy` and you've got it installed. -But back then, I was wrangling C compilers and linker flags at the terminal... -as a 3 month old programmer! -Anyways, the negative emotions associated with these experiences -can create a lasting impression, -deterring users from trying again in the future. - -**Barrier 3 - Time Pressure**: -Many users, particularly those in professional settings, -have limited time to experiment with new tools. -If the installation process is time-consuming or requires multiple attempts, -your potential users may decide that the potential benefits of the tool -are not worth the time investment required to get it up and running. -Your impact, as a result, is diminished -- -especially because your users missed the opportunity -to leverage what you've built! - -**Barrier 4 - Imposter Syndrome**: -Difficulties in the setup process -can trigger feelings of imposter syndrome. -When users struggle to install or configure a tool -that they believe should be straightforward, -they may begin to question their own competence. -This self-doubt can be a powerful deterrent to adoption, -as users may avoid tools -that make them feel inadequate or out of their depth. - -### Two perspectives on setup: User vs. Developer - -When we talk about "getting set up" with a data science package or project, -it's important to recognize that this can mean two distinct things, -depending on the user's intentions: - -**Installing a tool for use**: -This is the perspective of an end-user who wants to leverage your package as a tool in their own work. -They're primarily concerned with getting the package installed and running quickly, -without needing to understand or modify the underlying code. - -**Installing to contribute**: -This is the perspective of a developer or contributor -who wants to explore, modify, or contribute to your project's code. -They need to set up a full development environment, -which often involves more steps and a deeper understanding of the project structure. - -Each of these scenarios presents its own set of challenges and psychological barriers: - -For tool users, the focus is on simplicity and speed. -They want a straightforward installation process, -ideally with a single command like `pip install your-package`. -Complex dependencies or platform-specific instructions can be major deterrents. - -For developers, the process is inherently more involved. -They need to clone the repository, -set up a virtual environment, -install development dependencies, -and possibly configure additional tools like linters or test runners. -While they may be more technically inclined, -a clear and well-documented setup process is still crucial to encourage contributions. - -By recognizing and addressing both of these perspectives in your documentation and setup process, -you can create a more inclusive and user-friendly experience for all potential users of your project, -whether they're simply using it as a tool or diving deep into the code. - -### The impact of user-friendly setup - -By prioritizing a smooth installation and setup process: - -1. You lower the barrier to entry for potential users. -2. You increase the likelihood of adoption and continued use. -3. You create a positive first impression, encouraging users to explore your package further. -4. You reduce the support burden, as fewer users encounter installation issues. - -Think back to the moment that you picked up someone else's Python package -and it _just worked_... wasn't that a magical feeling? -When you build a tool, you'll want to get users to the "aha!" moment -of using your package as quickly and painlessly as possible. -Every step you can eliminate or simplify in the setup process -is a win for user adoption and satisfaction. - -## Key concept 3: Documentation as a bridge to understanding - -Documentation is often treated as an afterthought in data science projects, -but it's a crucial bridge between your code and its users. -Good documentation can make the difference -between a project that gathers dust -and one that becomes widely adopted and built upon. -The Diataxis framework, -developed by Daniele Procida, -provides an excellent structure -for thinking about and organizing documentation. - -### The role of documentation - -The Diataxis framework identifies four distinct types of documentation, each serving a specific purpose: - -1. **Tutorials**: Learning-oriented guides that help new users get started quickly. -2. **How-To Guides**: Problem-oriented instructions for completing specific tasks. -3. **Explanation**: Understanding-oriented discussions that provide background and context. -4. **Reference**: Information-oriented technical descriptions of the code. - -These four types of documentation serve different roles in a user's journey: - -1. **Onboarding**: Tutorials help new users quickly understand and start using your code. -2. **Problem-Solving**: How-To Guides assist users in accomplishing specific tasks. -3. **Deep Understanding**: Explanation sections provide insight into the why and how of your project. -4. **Detailed Information**: Reference documentation offers a reliable source of information for all features and functions. - -### Types of documentation artifacts - -Within these four categories, you might create various documentation artifacts: - -1. **README**: The first point of contact for users, providing an overview and quick start guide. -2. **API Documentation**: Detailed explanations of functions, classes, and modules (part of the Reference quadrant). -3. **Tutorials**: Step-by-step guides for common use cases (part of the Tutorials quadrant). -4. **Examples**: Practical demonstrations of how to use the code in real-world scenarios (often part of How-To Guides). -5. **Inline Comments**: Explanations within the code itself for complex logic or algorithms (can feed into Reference documentation). - -By structuring your documentation according to the Diataxis framework, -you can ensure that you're meeting the diverse needs of your users, -from newcomers to experienced developers looking for specific information. - -### Best practices for effective documentation - -When it comes to creating effective documentation, -there are several key practices to keep in mind. - -**Contemporaneous documentation**: -First and foremost, -it's crucial to write documentation as you code, -rather than leaving it as an afterthought! -Leaving just-in-time comments -can help you preserve critical details that may be handy -for future readers of your code. -This approach ensures that your documentation remains accurate and complete, -reflecting the most up-to-date state of your project. - -**Clear language**: -Clear language is paramount in documentation. -Avoid using jargon or overly technical terms without explanation. -Instead, strive to explain complex concepts in simple, accessible language -that a wide range of users can understand. -This clarity helps reduce the learning curve for new users -and makes your project more approachable. -If you use tools like Quarto or MyST, -you can use pop-ups, sidebars, and callouts -to help ~~disambiguate~~ explain terms. - -**Examples**: -Examples are a powerful tool in documentation. -Whenever possible, -include code snippets and use cases -that illustrate how to use your package. -These practical demonstrations can help users -quickly grasp how to apply your code to real-world scenarios, -bridging the gap between theory and practice. - -**Regular updates**: -As your project evolves, -so should your documentation. -Regularly reviewing and updating your documentation -is essential to ensure it remains relevant and accurate. -This ongoing maintenance helps prevent confusion and frustration -that can arise from outdated or incorrect information. - -Personally, -I've been building GenAI tooling -to help me identify out-of-date documentation -as my codebase evolves, -as well as update the documentation directly. -I've been prototyping this in LlamaBot, -and if you're interested in talking about it, -I'm more than happy to chat! - -**Tooling**: -Finally, consider leveraging documentation tools -to enhance the presentation and organization of your content. -Documentation systems, such as Quarto and MkDocs -can help you create professional-looking documentation -that's easy to navigate and visually appealing. -These tools can significantly improve the user experience, -making your documentation more engaging and effective. -Additionally, use LLMs to help you draft documentation! -It'll help you get over writer's block. - -### The impact of good documentation - -Investing in quality documentation yields far-reaching benefits for your data science project: - -1. It increases user adoption and satisfaction -2. It reduces support burden on maintainers -3. It fosters a vibrant community by encouraging contributions -4. It enhances project credibility and professionalism -5. It maximizes code's potential and impact - -Remember, even the most brilliant code -is only as valuable as users' ability to understand and implement it. -High-quality documentation is the key that unlocks your code's full potential, -amplifying its reach and influence in the field. -By creating and maintaining excellent documentation, -you're not just explaining your code – -you're expanding its impact within the data science community and beyond. - -## How we operationalize these ideas at work - -At Moderna, where I work, -I've been fortunate to be an early member of -the Data Science and Artificial Intelligence teams. -This gave me the opportunity to influence the ways of working -of a team that was still figuring out how to operationalize these ideas in practice. -We've implemented several concrete practices -that bring these concepts to life in our daily work. - -### Code reviews - -One of our key practices is the consistent use of code reviews. -Except for rapid prototyping that needs to be completed within an afternoon, -we always conduct code reviews. -While this process may slow us down in the short term, -typically adding a day or two of waiting for colleagues to review, -it pays off significantly in the long run. -(Moreover, we use AI-assisted coding to speed up our code writing anyway, -so not much is lost!) -We move faster overall because of the growing shared knowledge across the team. -Code reviews are particularly crucial -for ensuring both the correctness and maintainability of our code. -They also serve as an excellent way for team members -to learn coding patterns from each other and maintain high code quality. -It's worth noting that a hallmark of an experienced programmer -is their ability to deploy effective patterns in their codebase. - -### Docathons and AI-assisted documentation - -We also emphasize the habit of writing documentation through regular "docathons." -Every quarter, we set aside two days specifically for working on documentation. -During these events, we focus on updating documentation for our various projects. -These docathons serve a dual purpose: -they are not only productive work sessions but also great team-building exercises. -We find that newcomers to the team are particularly helpful during these events, -as they assist in keeping our onboarding documentation up to date, -providing fresh perspectives on what new team members need to know. - -In addition, we use AI to help us draft documentation. -Modeling after [documentation tooling that I built for LlamaBot](https://github.com/ericmjl/llamabot/blob/main/docs/cli/docs.md), -we use the same pattern at work to help us draft documentation, -especially for routine, templated, and tedious-to-write documentation. -Humans are still responsible for checking the drafted documentation, -but at least we use AI tools to help us get started. - -### Standard project templates and automation - -To streamline our development process, -we have implemented standard project templates and automation tools. -Our approach is similar to Cookiecutter Data Science-templated projects, -but with additional automation and customization specific to Moderna's needs. -We make extensive use of pre-commit hooks and GitHub Actions -to automatically check our code with every commit. -This automation significantly alleviates -the mental burden associated with manual checking. -By leveraging these tools, -we have effectively removed much of the human burden -typically associated with nitpicking another person's code. -Instead, we let the automated systems handle these checks, -and we simply respond to what the "robots" tell us needs attention. - -### The impact of these practices - -Through these practices, -we have created an environment where clean, distributable, -and well-documented code is not just an ideal, -but a daily reality. -This approach has significantly enhanced our team's efficiency, -collaboration, and the overall quality of our data science projects at Moderna. -We engage in long-term research partnerships with our colleagues -and can reliably take code from prototype to production with minimal overhead. -We easily onboard new people onto projects -thanks to the high-quality documentation provided. - -## Conclusion - -Throughout this keynote blog post, -we've explored the critical human dimension of data science code, -focusing on three key concepts: -readability, user-friendly installation, and documentation. -These elements, -often overlooked in favor of algorithmic efficiency or cutting-edge techniques, -are fundamental to the success and impact of your data science projects. - -**Readability** reduces cognitive load and fosters collaboration, -inviting others to build upon our work. -This openness drives scientific progress in data science. - -**User-friendly installation** lowers barriers to entry, -encouraging wider adoption and increasing impact. -A brilliant but unusable algorithm is effectively useless. - -**Documentation** bridges code and users, -serving as a form of communication and teaching. -Good documentation can transform users into contributors, -fostering a community around your project. - -These practices are fundamental to open science and reproducible research. -By making our code accessible and understandable, -we enable verification, replication, and advancement of scientific work. -This approach addresses the reproducibility crisis in many fields. -Ultimately, we contribute to a more transparent and robust scientific ecosystem, -not just improving individual projects. - -As data scientists, our goals are to create tools and analyses that make a real-world impact. -By considering the human dimension to how our code gets used, -we dramatically increase the chances of our work being used, -understood, and built upon by others. -As you go forward, be other-centered in your work, -and treat your customers as you would want them to treat you! - -### Call to action - -As you embark on your next data science project, challenge yourself to: - -1. Prioritize readability from the start. -2. Create a smooth installation process. -3. Document as you go, not as an afterthought. -4. Seek early user feedback. -5. Contribute to open-source projects, focusing on documentation and user experience. - -Impactful data science isn't just about clever algorithms or big data. -By adopting a toolmaker's mindset, -we create knowledge that others can easily access, understand, and build upon. -This approach enhances our ability to solve real-world problems creatively. - -Let's commit to writing code that's both computationally efficient and human-friendly. -In doing so, we'll foster a more collaborative, innovative, and impactful data science community. -Thank you for reading! - -## Additional Resources - -| Resource | Description | -|--------------------------------------------------------------------|----------------------------------------------------------------------------------------------| -| [The Hitchhiker's Guide to Python](https://docs.python-guide.org/) | An excellent resource for Python best practices, including code style and project structure. | -| [Python Packaging User Guide](https://packaging.python.org/) | Comprehensive guide on distributing Python packages. | -| [Write the Docs](https://www.writethedocs.org/) | A global community of people who care about documentation, with many resources and guides. | -| [Diataxis Framework](https://diataxis.fr/) | Detailed explanation of the documentation framework mentioned in this post. | - - -## FAQ - -**Should I be using AI tools to help me code?** -Yes! And make sure you are ready to defend every line of code that is produced by an AI tool. -My personal conviction is that humans who write with AI assistance -will be the ones who are able to turbocharge their effectiveness. -At the same time, humans are the ones who are responsible for the end product. -So, let AI assist you, but don't relinquish your responsibility! - -**Should I be using AI tools to help me write my documentation?** -Yes! And just like with code, -make sure you are ready to defend every line of documentation that is produced by an AI tool. -In fact, I wrote this blog post with the help of Claude 3.5 Sonnet, -in which I drafted out my ideas in bullet point format -and then asked Claude to help me flesh out each section one at a time -into a cohesive written piece. -And I'm ready to defend every line that's in this blog post -or else be willingly corrected! - -**What if I don't have time to write documentation?** -Use AI tools to help! -But if you're in an airgapped environment with no access to AI tools over an API, -see if you're able to procure hardware to run local LLMs on your own machine. -It turns out that we have supercomputers (by 1980s standards) in our pockets, -on our laps, and on our desks, -and the total amount of compute capacity that just runs idle is staggering. diff --git a/_posts/2024-12-13-pyOpenSci-job-communications.md b/_posts/2024-12-13-pyOpenSci-job-communications.md deleted file mode 100644 index bf99f4f0..00000000 --- a/_posts/2024-12-13-pyOpenSci-job-communications.md +++ /dev/null @@ -1,94 +0,0 @@ ---- -layout: single -title: "CLOSED pyOpenSci is hiring a Communications Lead" -blog_topic: updates -excerpt: "pyOpenSci is hiring a part-time Writer and Social Media Specialist to enhance communication and engagement within the scientific Python community. This flexible, remote role involves creating social media content, newsletters, and blogs and fostering community interaction on social media to support open source and open science. Join us to make a meaningful impact on the open source ecosystem!" -author: "Leah Wasser" -permalink: /blog/pyopensci-job-communications.html -header: - overlay_image: images/headers/pyopensci-floral.png -categories: - - blog-post - - community -classes: wide -toc: true -comments: true -last_modified: 2025-05-14 ---- - -**We are no longer accepting applications for this position. Thank you for your interest.** - -## CLOSED Job: writer & social media specialist - -*Last Updated: {{ page.last_modified}}* - -### About the role - -pyOpenSci seeks a talented Writer and Social Media Specialist to enhance our communications and engagement with the scientific Python community. This position involves crafting engaging content highlighting our [online learning content](https://www.pyopensci.org/lessons/), [tutorials](https://www.pyopensci.org/python-package-guide/tutorials/intro.html), [events](https://www.pyopensci.org/events.html), and [package ecosystem](https://www.pyopensci.org/python-packages.html) for our social media channels, newsletters, and blog to keep our community informed and inspired. It also involved reviewing and editing educational content. This is an excellent opportunity for someone passionate about open science and skilled in clear, impactful communication. The position requires 10-15 hours per week at a rate of 25-40$/hour based on experience, with the flexibility of remote, part-time work. There is some flexibility in this position week-to-week based on pyOpenSci's deadlines. This is a non-regular, part-time, remote, non-exempt position. This position will report -to the pyOpenSci Executive Director. Applicants must be eligible for employment in the United States. - -### About pyOpenSci - -[pyOpenSci](https://www.pyopensci.org/) is a vibrant and diverse scientific open source community that develops processes and guidelines for creating and maintaining scientific Python packages and develops educational content around creating Python packages, sharing code, and engaging in open science practices. Our mission is to help make science more open and collaborative while also increasing the participation of groups that have been traditionally underrepresented in the open source community. - -pyOpenSci runs an [open peer review process for scientific Python software.](https://www.pyopensci.org/about-peer-review/index.html) Through this program, we reinforce community-defined packaging guidelines while improving usability, documentation, and package quality. Further, we are curating a database of vetted scientific Python tools that are actively maintained. - -A core part of our mission is to increase the participation of historically underrepresented groups in open science and open source. We achieve this by providing mentorship, translating resources, offering training scholarships, and partnering with global organizations. - -pyOpenSci is a fiscally sponsored project of Community Initiatives. - -## Key responsibilities - -* **Social Media Management:** Create and schedule posts on platforms including Mastodon, LinkedIn, BlueSky, and YouTube. Ensure our content is engaging, consistent, and aligns with pyOpenSci’s mission. - * LinkedIn is currently our most active platform. As such, you will spend time writing our[ LinkedIn newsletters](https://www.pyopensci.org/handbook/community/social.html#social-accounts) and developing graphic and text content for posts that keep the community up-to-date and engaged with our efforts. - * pyOpenSci currently uses [Buffer](https://www.buffer.com) to manage scheduled posts and associated analytics. -* **Content Creation:** Write and edit newsletters, [blog posts,](https://www.pyopensci.org/blog/) and other materials to update our community on pyOpenSci’s programs, new resources, and events. - * Create supporting graphics to enhance engagement. - * Contribute to and review online lessons being developed by pyOpenSci -* **Community Engagement:** Interact with our online community to foster engagement, respond to questions, and keep conversations aligned with our goals. -* **Content Planning:** Work with the pyOpenSci team to develop a content calendar that supports key events, initiatives, and outreach goals. - -pyOpenSci also has plans to launch a YouTube channel. Video editing skills are not required for this position but are a bonus. -{: .notice } - -### What we’re looking for - -* **Communication Skills:** Excellent written communication skills with experience creating strategic content for social media, blogs, and newsletters. -* **Community-Oriented:** Passion for engaging with the scientific community and supporting our open science mission. -* **Attention to Detail:** Strong proofreading skills and a knack for creating polished, professional, engaging content. -* **Organized and Reliable:** Ability to work independently, manage time effectively, and meet deadlines in a remote environment. -* **Visual Content Creation Skills:** Proficiency in creating visually appealing social media graphics with an eye for design and aesthetics, ensuring content is engaging and on-brand. - -## About You - -* Broadly familiar with open science and open source principles and excited about developing content that promotes these principles. -* Experienced in science communication, writing, or a related field. -* Fluent in written and spoken English. - -### Preferred but not required qualifications - -* Experience using Python and Git/GitHub. -* Familiarity with video content creation or editing. - -### What you can expect - -In this role, you’ll work closely with our executive director and engage with the pyOpenSci community to amplify our message and increase our visibility. You’ll be able to contribute creatively to our communications and make a tangible impact on the open science movement. - -## Equal employment opportunity - -Community Initiatives is an equal opportunity employer and considers applicants for employment regardless of age, race, color, religion, creed, sex, sexual orientation, gender identity or expression, national origin, marital status, disability, or protected veteran status. - -## How to apply - -We encourage applications from candidates of all backgrounds, especially those traditionally underrepresented in open science and open source communities. -To apply, please email admin@pyopensci.org with the following: - -* A current resume. -* A cover letter that discusses your experience developing social content. In that cover letter, please include at least one link, image, or example of each of the following: - * Social posts that you have created (ideally with graphics you have created), - * A blog post that you have written that you are proud of - * Optional: If you have created video content before, please include an example. - -Please submit example materials as links or attachments as you see fit. - -Applications will be reviewed on a rolling basis. diff --git a/_posts/2025-01-17-quadratik.md b/_posts/2025-01-17-quadratik.md deleted file mode 100644 index fb1e0a9a..00000000 --- a/_posts/2025-01-17-quadratik.md +++ /dev/null @@ -1,389 +0,0 @@ ---- -layout: single -title: "QuadratiK: Collection of Methods Constructed using Kernel-Based Quadratic Distances" -blog_topic: software -excerpt: "QuadratiK provides a set of goodness-of-fit tests, a clustering technique using kernel-based quadratic distances, and algorithms for generating random samples from Poisson kernel-based distributions (PKBD). QuadratiK has recently been accepted into the pyOpenSci ecosystem." -author: "Raktim Mukhopadhyay" -permalink: /blog/quadratik.html -header: - overlay_image: images/headers/pyopensci-quadratik.png -categories: - - blog-post - - quadratic distances - - clustering - - one-sample test - - two-sample test - - k-sample test - - PKBD - - user interface - - pyos-accepted -classes: wide -toc: true -comments: true -last_modified: 2025-02-06 ---- - -## Introduction - -`QuadratiK` provides a suite of methods based on kernel-based quadratic distances, and hence the name! - -`QuadratiK` contains several goodness of Fit (GoF) tests such as normality tests and two and k-sample tests. It also includes tests for uniformity on the d-dimensional sphere, a clustering algorithm using Poisson kernel densities, and algorithms for generating random samples from PKBD. `QuadratiK` offers graphical functions that enhance user experience by facilitating the validation, visualization, and interpretation of clustering results. Furthermore, it provides methods for meaningful analyses and reproducible inference across diverse fields. A dashboard application with a user-friendly interface is also a part of `QuadratiK` to enhance accessibility for practitioners beyond the domain of statistical sciences. - -**This package is joint work with Dr. Marianthi Markatou, SUNY Distinguished Professor, University at Buffalo and Dr. Giovanni Saraceno, Assistant Professor, University of Padova.** - - -## Goodness-of-Fit (GoF) Tests - -Goodness-of-Fit (GoF) tests are classical tools for assessing the compatibility of data with a given probability model. GoF tests typically compute a distance-like metric between the null distribution and observations, rejecting the null hypothesis if the distance exceeds a critical value. - -The methods for normality, two-sample, and k-sample test use a bandwidth parameter `h`. We have also provided an algorithm for determining the optimal value of `h` based on the mid-power analysis (please see Markatou and Saraceno (2024)). You can find more details on algorithm in our [manual](https://quadratik.readthedocs.io/en/latest/user_guide/hselect.html). - -In this section, the various GoF tests are shown with corresponding examples. - -### Normality Test - -Normality tests are used to determine if the sample data is well-modeled by a normal distribution. In our case, this is done by measuring the distance between the sample data and the target distribution, where the target distribution in this case is the d-dimensional normal distribution. - -In our case, this is done by measuring the distance between the empirical cumulative distribution function of the sample data and the target distribution, where the target distribution in our case is the d-dimensional normal distribution. - -```python -iimport numpy as np - -np.random.seed(0) -from QuadratiK.kernel_test import KernelTest - -# data generation -data_norm = np.random.multivariate_normal(mean=np.zeros(4), cov=np.eye(4), size=500) - -# performing the normality test -normality_test = KernelTest( - h=0.4, num_iter=150, method="subsampling", random_state=42 -).test(data_norm) - -# printing the summary for normality test -print(normality_test.summary(print_fmt="grid")) -``` - -The results of this test is shown below. -
- - - Results for the Normality Test. - -
- -The test rightly fails to reject the null hypothesis, as the samples have been generated from a standard normal distribution. - -### Two-Sample Test -The two-sample GoF test is used to determine whether two separate samples are likely drawn from the same population distribution. - -To illustrate the two sample test, we generate n = 200 random samples from a multivariate standard normal distribution and a skewed normal distribution with value of skewness parameter lambda = 0.1. - -```python -import numpy as np - -np.random.seed(0) -from scipy.stats import skewnorm - -from QuadratiK.kernel_test import KernelTest - -# data generation -X_2 = np.random.multivariate_normal(mean=np.zeros(4), cov=np.eye(4), size=200) -Y_2 = skewnorm.rvs( - size=(200, 4), - loc=np.zeros(4), - scale=np.ones(4), - a=np.repeat(0.1, 4), - random_state=20, -) -# performing the two sample test -two_sample_test = KernelTest(h=2, num_iter=150, random_state=42).test(X_2, Y_2) - -# printing the summary for the two sample test -print(two_sample_test.summary(print_fmt = "grid")) -``` - -The results of the test is shown below. -
- - - Results for the Two Sample Test. - -
- -The test rejects the null hypothesis, as the samples have been generated from two different distributions. - -### K-Sample Test - -Similar to the two-sample test, the k-sample test examines whether k groups of samples are obtained from the same distribution. - -For illustrating the k-sample test, we use the glass identification dataset from the [UCI ML repository](https://archive.ics.uci.edu/dataset/42/glass+identification). We use the first three classes of glass types to illustrate the working of the k-sample test. - -```python -# Importing required libraries -from ucimlrepo import fetch_ucirepo -import numpy as np -from QuadratiK.kernel_test import KernelTest - -# Fetching the dataset -glass_identification = fetch_ucirepo(id=42) - -# Selecting the data for specified types of glass -filtered_data = glass_identification.data.original.query("Type_of_glass in [1, 2, 3]") -X = filtered_data.drop(columns=['Type_of_glass']) -y = filtered_data['Type_of_glass'].to_numpy() - -# Setting random seed -np.random.seed(0) - -# Performing the Kernel Two-Sample Test -k_sample_test = KernelTest(h=2, num_iter=150, random_state=42).test(X, y) - -# Printing the test summary -print(k_sample_test.summary(print_fmt="grid")) -``` -The results of the test is shown below. -
- - - Results for the K-Sample Test. - -
- -The null hypothesis is rejected for the k-sample test indicates that there is **significant evidence to conclude that at least one of the distributions among the three glass types is different**. In other words, the samples from the three classes of glass do not all come from the same underlying population distribution. This suggests that there are meaningful differences in the characteristics or features of the glass types being compared. - -### Uniformity Test on the Sphere - -In this we test the null hypothesis of uniformity on the sphere. We illustrate this test using an example. - -The data for this example is generated from a multivariate standard normal distribution, and is further divided by the L2 norm of generated vectors. This processed data is uniformly distributed on the surface of the unit sphere. - -```python -import numpy as np - -np.random.seed(0) -from QuadratiK.poisson_kernel_test import PoissonKernelTest - -# data generation -z = np.random.normal(size=(500, 3)) -data_unif = z / np.sqrt(np.sum(z**2, axis=1, keepdims=True)) - -# performing the uniformity test -unif_test = PoissonKernelTest(rho=0.5, random_state=42).test(data_unif) - -# printing the summary for uniformity test -print(unif_test.summary(print_fmt = "grid")) -``` -The results of the test is shown below. -
- - - Results for the Uniformity Test. - -
- -## Clustering - -`QuadratiK` implements the Poisson kernel-based clustering algorithm on the sphere proposed by Golzy and Markatou (2020). We will demonstrate the spherical clustering capabilities of `QuadratiK` through an image segmentation task. - -The image we will be using is shown below, and the task is to identify the various regions (eg. entity or other features on interest in an image). - -
- - - Dog Cat Image. - -
- -Particularly, in this image a potential image segmentation task is to identify the various entities i.e. the cat and the dog in the image. Let's apply the clustering algorithm and see what does it return to us. - -```python -import matplotlib.pyplot as plt -from PIL import Image -import numpy as np - -# Load and resize the image -image = Image.open("dog-cat.png") -new_size = (50, 50) # Width, Height -image = image.resize(new_size) - -# Convert the image to a NumPy array -image_array_resized = np.array(image, dtype=float).reshape((-1, 3)) -image_array_resized = image_array_resized + 10**-6 # Avoid division by zero - -# Apply Poisson Kernel Based Clustering -from QuadratiK.spherical_clustering import PKBC - -k_values = range(2, 11) -pkbc = PKBC(num_clust=k_values, random_state=0).fit(image_array_resized) - -segmented_images = [] - -plt.figure(figsize=(16, 8)) - -num_k_values = len(k_values) -num_cols = 6 -num_rows = (num_k_values + num_cols - 1) // num_cols - -for i, k in enumerate(k_values, start=1): - labels = pkbc.labels_[k] - labels_reshaped = labels.reshape(new_size[1], new_size[0]) - - np.random.seed(42) - unique_labels = np.unique(labels) - colors = np.random.randint(0, 255, size=(len(unique_labels), 3)) - segmented_image = np.zeros((new_size[1], new_size[0], 3), dtype=np.uint8) - - for label in unique_labels: - segmented_image[labels_reshaped == label] = colors[label] - - segmented_images.append(segmented_image) - - # Plot segmented image - plt.subplot(num_rows, num_cols, i) - plt.imshow(segmented_image) - plt.axis('off') - plt.title(f"Segmented (k={k})") - -plt.tight_layout() -plt.show() -``` - -The image is segmented into k clusters with k ranging from 2 to 8. Below, we display the regions identified for each value of k. - -
- - - Dog Cat Image Segmented - -
- -Starting from k = 5, the segmented images reveal only minor changes in the identified segments upon closer examination. Let us see if we can validate our observation using the elbow plots. - -```python -validation_metrics, elbow_plots = pkbc.validation() -elbow_plots -``` -
- - - Dog Cat Image Validation Plots - -
- -The elbow plots show a clear elbow at k = 5, which aligns with our observation that all regions of the image are effectively identified at this value of k. - -The clustering algorithm proposed in Golzy and Markatou has been used in other works such as Golzy et al. (2023), Strelnikoff at al. (2020), and Strelnikoff et al. (2024). - -## Sampling from PKBD - -In this example, we generate observations from the Poisson kernel-based distribution on the sphere. `QuadratiK` in Python implements two algorithms to generate random samples, the acceptance-rejection algorithm using the Mises-Fisher and angular central Gaussian distributions. In the example, we consider mean direction $\mu = (1,1,1)$ and dimension d = 3 with concentration parameter $\rho = 0.9$. We sample n = 500 observations for the available methods. - -```python -# Import the PKBD class from the spherical_clustering module in the QuadratiK package -from QuadratiK.spherical_clustering import PKBD -# Instantiate the PKBD class -pkbd = PKBD() -# Generate 500 samples from PKBD using rejvmf -samples_rejvmf = pkbd.rpkb( - n=500, mu=[1, 1, 1], rho=0.9, method="rejvmf", random_state=42 -) -# Generate 500 samples PKBD using rejacg -samples_rejacg = pkbd.rpkb( - n=500, mu=[1, 1, 1], rho=0.9, method="rejacg", random_state=42) -``` - -The generated samples can also be visualized on the unit sphere. - -```python -import matplotlib.pyplot as plt -import numpy as np - -# Plot samples on unit sphere -phi, theta = np.mgrid[0 : np.pi : 100j, 0 : 2 * np.pi : 100j] -x = np.sin(phi) * np.cos(theta) -y = np.sin(phi) * np.sin(theta) -z = np.cos(phi) -fig = plt.figure(figsize=(5, 5)) -ax = fig.add_subplot(111, projection="3d") -ax.view_init(azim=50, elev=30) -ax.plot_surface(x, y, z, color="white", alpha=0.8, linewidth=0) -ax.scatter( - samples_rejvmf[:, 0], - samples_rejvmf[:, 1], - samples_rejvmf[:, 2], - color="b", - s=25, - marker="*", - label="rejvmf", -) -ax.scatter( - samples_rejacg[:, 0], - samples_rejacg[:, 1], - samples_rejacg[:, 2], - color="red", - s=25, - marker="o", - label="rejacg", -) -ax.set_xlim([-1, 1]) -ax.set_ylim([-1, 1]) -ax.set_zlim([-1, 1]) -ax.set_aspect("equal") -ax.tick_params(axis="both", labelsize=8) -plt.legend(loc="upper right", fontsize=14) -plt.tight_layout() -``` - -
- - - PKBD Samples on Unit Sphere. - -
- -
- -More details on Poisson Kernel-Based Distributions can be found in the package documentation [here](https://quadratik.readthedocs.io/en/latest/user_guide/pkbd.html). - -## Dashboard - -`QuadratiK` also provides a graphical user interface (GUI) that enables users to interact with its methods in a non-programmatic and user-friendly manner. - -```python -from QuadratiK.ui import UI -UI().run() -``` - -
- - - Dashboard. - -
- -## Concluding Remarks - -`QuadratiK` provides methods to researchers and practitioners to delve deeper into their data, draw robust inference, and conduct potentially impactful analyses and inference across a wide array of disciplines. The `QuadratiK` package is also available in `R` and is hosted on [CRAN](https://cran.r-project.org/web/packages/QuadratiK/index.html). You can learn more about `QuadratiK` in our [arXiv preprint](https://arxiv.org/abs/2402.02290). Additional theoretical papers of interest are listed in the reference section. - -Please feel free to reach me at raktimmu at buffalo.edu. - -Thank you! Happy coding to you — may your bugs be few, and your data ever insightful! 🚀😊 - -## References - -- Saraceno G., Markatou M., Mukhopadhyay R., Golzy M. (2024). Goodness-of-Fit and Clustering of Spherical Data: the QuadratiK package in R and Python. arXiv preprint arXiv:2402.02290. - -- Ding Y., Markatou M., Saraceno G. (2023). “Poisson Kernel-Based Tests for Uniformity on the d-Dimensional Sphere.” Statistica Sinica. DOI: 10.5705/ss.202022.0347. - -- Golzy M. & Markatou M. (2020) Poisson Kernel-Based Clustering on the Sphere: Convergence Properties, Identifiability, and a Method of Sampling, Journal of Computational and Graphical Statistics, 29:4, 758-770, DOI: 10.1080/10618600.2020.1740713. - -- Sablica, L., Hornik, K., & Leydold, J. (2023). Efficient sampling from the PKBD distribution. Electronic Journal of Statistics, 17(2), 2180-2209. - -- Markatou, M., & Saraceno, G. (2024). A unified framework for multivariate two-sample and k-sample kernel-based quadratic distance goodness-of-fit tests. DOI: 10.48550/arXiv.2407.16374v1 - -- Golzy, M., Rosen, G. H., Kruse, R. L., Hooshmand, K., Mehr, D. R., & Murray, K. S. (2023). Holistic assessment of quality of life predicts survival in older patients with bladder cancer. Urology, 174, 141-149. - -- Strelnikoff, S., Jammalamadaka, A., & Warmsley, D. (2020, December). Causal maps for multi-document summarization. In 2020 IEEE International Conference on Big Data (Big Data) (pp. 4437-4445). IEEE. - -- Strelnikoff, S., Jammalamadaka, A., & Warmsley, D. M. (2024). U.S. Patent No. 11,907,307. Washington, DC: U.S. Patent and Trademark Office. diff --git a/_posts/2025-02-07-pyopensci-2024-a-year-in-review.md b/_posts/2025-02-07-pyopensci-2024-a-year-in-review.md deleted file mode 100644 index 6417b867..00000000 --- a/_posts/2025-02-07-pyopensci-2024-a-year-in-review.md +++ /dev/null @@ -1,239 +0,0 @@ ---- -layout: single -title: "2024: A Transformative Year for pyOpenSci" -blog_topic: updates -excerpt: "2024 was a transformative year for pyOpenSci. Through training, mentorship, and peer review, we expanded our community, created free educational resources, and empowered scientists worldwide. Here’s what we built together—and why it matters now more than ever." -author: "Leah Wasser" -permalink: /blog/2024-pyopensci-retrospective.html -header: - overlay_image: images/headers/pyopensci-floral.png -categories: - - blog-post - - community -toc: true -classes: wide -comments: true -last_modified: 2025-02-07 ---- - -## Introduction - -In 2024, pyOpenSci’s vibrant community led efforts to make open source science more accessible, inclusive, and equitable for all. We empowered the broader community to create, contribute to, and discover better software through beginner-friendly [training events](https://www.pyopensci.org/events.html), [collaborative tutorials](https://www.pyopensci.org/learn.html#start-learning-beginner-friendly-python-packaging-tutorials), and [software peer review](https://www.pyopensci.org/about-peer-review/index.html). - -
- - - Infographic summarizing pyOpenSci’s achievements in 2024 with the title ‘pyOpenSci 2024: A year of community & growth.’ The ‘Training & Sprints’ section shows 7 events and 180 attendees. The ‘Peer Review’ section highlights 39 accepted packages and approximately 100 editors and reviewers. The ‘Community’ section emphasizes 1,436 issues and pull requests submitted, 278 contributors, and 23 new co-developed lessons. - -
- -Looking back, I’m inspired and humbled by what we’ve achieved together: - -- We co-created a [Python package template](https://github.com/pyOpenSci/pyos-package-template) aligned with our [beginner-friendly Python packaging tutorials](https://www.pyopensci.org/python-package-guide/tutorials/intro.html) that we co-developed. -- We launched a new **Training Initiative** to empower open source newcomers and [explore what sustainability means](https://www.pyopensci.org/blog/pyopensci-funding-sustainability.html) for pyOpenSci. -- We expanded our [**Software Peer Review Program**](https://www.pyopensci.org/about-peer-review/index.html), increasing its global impact. -- And our contributor community grew - - -As I reflect on an incredible year, I want to take a moment to celebrate these milestones and set the stage for an ambitious 2025. Here’s a look back at what we accomplished together in 2024 and where we're heading next. - - - -## Co-creation of beginner-friendly content: Python packaging made easy(ier) - -A defining strength of the pyOpenSci community is its commitment to co-creating accessible technical lessons for Pythonistas at all skill levels. These lessons, in turn, support our beginner-friendly tutorials and training events (more below). In 2024, this commitment shone through in creating our tutorial, [**"How to Create a Python Package"**](https://www.pyopensci.org/python-package-guide/tutorials/intro.html). This tutorial provides an opinionated way to create a Python package--a key step in making Python packaging more approachable and accessible for all. - -Our packaging tutorial is the product of a vibrant collaboration between developers, scientists, and beginners. Packaging tool maintainers and packaging experts worked alongside those newer to packaging to co-develop, review, and refine content that is accurate, thorough, and welcoming to newcomers. - - -
- - - Diagram showing the lessons in our packaging tutorial. There are 6 total - what is a Python package, make code pip installable, publish your package to PyPI, add a README and LICENSE file, add metadata for PyPI and finally publish to conda forge. - -
- -### Collaborative learning in action: building together, learning together - -Our co-development process brought contributors of all experience levels together to create a resource that: - -- **Blend expertise:** Developers and scientists shared insights, while beginners shaped the content with fresh perspectives. -- **Demystifies packaging:** Clear, precise explanations, reviewed by experts and tested by newcomers, break down complex steps for new users. -- **Describes core concepts visually:** Custom graphics and step-by-step guides make technical concepts easier for visual learners to grasp. - -We're building both knowledge and community by publishing these lessons as free, open-access resources. This collaborative effort exemplifies the unique power of pyOpenSci to bridge expertise, foster learning, and strengthen the open source ecosystem. - -### Simplifying Python packaging: Our easy-to-use Python package template - -To further simplify the process of creating a new Python package, the community also came together to develop an easy-to-use [Python packaging template](https://github.com/pyOpenSci/pyos-package-template). The template allows you to create a skeleton Python package that follows our beginner-friendly Python package tutorial with just a few commands. - -Many learners used this template successfully during our Fall Festival (more below!) and we look forward to refining it further in the upcoming months. - -Give it a test-drive and let us know what you think! - - -## pyOpenSci's training new training initiative: empowering our global community - -In 2024, we launched the pyOpenSci [training initiative](https://www.pyopensci.org/blog/pyos-education-announcement.html) to lower barriers and make scientific software education more accessible to all. - -To further our commitment to equity, we awarded 25+ scholarships to support participation from a diverse group of students, researchers, and contributors. - -### Event Highlights: Fall Festival - -Our first-ever [Fall Festival](#) featured inspiring keynote speakers like Rowan Cockett, who introduced participants to [MyST Markdown](https://mystmd.org/), Melissa Mendoça who discussed her personal pathway into open source from academia, and Eric Ma, who overviewed the importance of [reproducibility in science](/blog/human-dimension-clean-documented-data-science-code.html). - -On the event's last day, George Stagg, developer of Quarto Live, and James Balamuta kicked off the day with an overview of how Quarto Live makes interactive publishing of dynamic scientific outputs easier, connecting scientific workflows with shared outputs. - -
- - - Graphic showing the keynote speakers for the pyOpenSci Fall Festival. The title reads: pyOpenSci Fall Festival Keynote Speakers. The featured speakers are Melissa Mendoça, James Balamuta, Eric Ma, George Stagg, and Rowan Cockett, each displayed in a circular frame against a purple background with connecting nodes as a design element. - -
- -The dynamic spatial chat platform fostered real-time collaboration and made learning interactive, personalized, and fun! - -{% include pyos-blockquote.html quote="I love the engagement…how people were attended to individually despite being in a group setting. I enjoyed that learning could happen in a personal and group setting." author="Workshop Participant" event="Fall Festival 2024" class="highlight purple" %} - -#### Volunteer contributors who made it possible - -The Fall Festival wouldn’t have been possible without the dedication of our incredible volunteers, who handled everything from workshop support to tech troubleshooting. Their efforts ensured a welcoming, smooth experience for participants. - -
- - - Graphic showing a ‘Thank You’ message to pyOpenSci Fall Festival community supporters. The text reads ‘Thank You, pyOpenSci Community Supporters!’ with the Fall Festival logo. Circular photos of community members include Rich Iannone, Steven Sylvester, Jonny Saunders, Tracy Teal, Carol Willing, Felipe Moreno, Tetsuo Koyama, Isabel Zimmerman, and Jeremiah Paige, displayed against a purple background with connecting nodes as a design element. The pyOpenSci URL (pyopensci.org) is displayed in the lower left. - -
- - -#### Open education lessons from the Fall Festival -Participants didn’t just learn—they contributed! Thanks to the collaborative energy, the event produced several lessons that are now freely available to the community: - -- [Writing Clean Code](https://www.pyopensci.org/lessons/write-better-code/clean-modular-code/) -- [Optimizing Code for Better Workflows](https://www.pyopensci.org/lessons/write-better-code/optimize-code/) -- [Running Code Efficiently](https://www.pyopensci.org/lessons/package-share-code/run-code/) -- [Publishing and Sharing Code](https://www.pyopensci.org/lessons/package-share-code/publish-share-code/) - -These lessons reinforce key technical skills and showcase the power of community-driven learning and co-creation. - -### Event highlight: Intro to Python packaging workshops - -As a part of our training initiative, we also ran two beginner-friendly packaging workshops where dozens of participants successfully created their first Python packages by following our [beginner-friendly packaging tutorials](https://www.pyopensci.org/python-package-guide/tutorials/intro.html). - -What did you enjoy most about the workshop? - -{% include pyos-blockquote.html quote="The content and the crew! The team was so kind, patient, and approachable. I appreciate the amount of support and reassurance given during this tutorial. The content of the tutorial was also spot on. Everything we covered felt relevant and useful, and gave me the confidence to feel capable of creating my own packages." author="Workshop Participant" event="SciPy 2024 Create your first Python package training" class="highlight purple" %} - -The success of our training materials underscores the demand for inclusive, high-quality open science resources; they also demonstrate the power of community collaboration. - -## Expanding our software peer review program in 2024 - -The pyOpenSci software peer review program empowers scientists to build and improve the tools they rely on to process and analyze data. In 2024, we expanded our ecosystem of reviewed scientific Python packages to **39**, thanks to the dedication of our editorial and review teams. - -We also tried out a new editor in chief rotation system to avoid too much time burden on any specific editor. Below are our fearless EiC's for 2024. - -
- - - Graphic titled ‘Editor in Chief 2024 Software Peer Review,’ highlighting the four Editors-in-Chief for 2024 with photos and descriptions.
-	•	Szymon Molinski (Fall 2024): Image of Szymon holding a dog by the ocean. Passion: Open source development, breakdancing, and hiking with his pug.
-	•	Chiara Marmo (Summer 2024): Illustrated portrait of Chiara. Fun fact: She enjoys reading theater pieces, playing all roles herself, and takes pride in accomplishing unexpected things.
-	•	Alex Batisse (Spring 2024): Black-and-white photo of Alex. Passion: Understanding and visualizing information, from stats to dataviz, and making it intelligible for others.
-	•	Isabel Zimmerman (Winter 2024): Photo of Isabel smiling outdoors in a wide-brimmed hat. Passion: Machine learning operations and improving the processes that support creating, sharing, and maintaining models. - -
- -Our editorial team grew to **18 members**, with a rotating Editor-in-Chief position, and was supported by **81 volunteer reviewers** who contributed their time to ensure that [every reviewed package in our ecosystem](https://www.pyopensci.org/python-packages.html) meets the highest standards for quality and usability. - -In 2024, we received over **28 new submissions**. - -### Peer review success in 2024 - -- **Publishing impact through partnerships:** - **21 packages** of our accepted packages were published in [JOSS through our peer review partnership](https://www.pyopensci.org/software-peer-review/partners/joss.html). - -- **Strengthening ecosystems through domain-specific affiliation: Astropy:** - Our domain-specific community partnership program accepted three packages for [Astropy affiliation](https://www.pyopensci.org/software-peer-review/partners/astropy.html). Our partnership with Astropy and JOSS demonstrates how communities with some overlapping goals can truly work together effectively. - -
- - - Graphic illustrating the pyOpenSci partnerships and review flow:
-	1.	pyOpenSci Accepted: The process starts with a package being accepted by pyOpenSci, represented by the pyOpenSci logo and a checkmark.
-	2.	JOSS Published: Accepted packages can be published in the Journal of Open Source Software (JOSS), indicated by the JOSS logo and a checkmark.
-	3.	Community Affiliated: Packages can also become affiliated with community-specific organizations, represented by a box labeled ‘Community Affiliated’ with a checkmark.
-
-The flowchart shows arrows connecting these stages, visually demonstrating the possible outcomes after pyOpenSci acceptance. - -
- -### Navigating ethical challenges in generative AI - -In 2024, we began to address the emerging challenge of reviewing packages that rely on proprietary generative AI models. We discussed important ethical questions about transparency vs. innovation in scientific software. Should we review packages that depend upon proprietary (closed box) models that are rapidly evolving? - -More work is needed, and we are committed to handling these complexities as we always do--collaboratively and with great care. - -## A thriving, diverse contributor community: how contributions to pyOpenSci have skyrocketed - -In 2024, pyOpenSci welcomed 278 contributors from diverse backgrounds, many making their first-ever open source contributions. By prioritizing mentorship, accessibility, and community-led learning, we helped first-time contributors gain the confidence and skills to shape the future of open source science. - -This was also the first year where volunteer contributions outnumbered staff contributions—a testament to the power of community. - -Wow! - -
- - - Bar chart titled ‘Staff vs. Volunteer Contributor by Quarter,’ showing the count of contributions by quarter from Q4 2018 to Q1 2025. The chart uses stacked bars, with green representing volunteer contributions and purple representing staff contributions. The chart shows a significant growth in contributions starting around 2023, with volunteer contributions surpassing staff contributions in recent quarters. - -
Plot that shows an increase in contributions to pyOpenSci open education content.
- -
- -### Empowering first-time contributors - -A key driver of contributor and community growth was our [**beginner-friendly sprints**](https://www.pyopensci.org/blog/pyopensci-pyconus-2024-sprints.html). Sprint events exemplified the power of community support combined with mentorship & just a bit of training (mostly around git and GitHub). These sprints welcomed over **50 participants** and resulted in **86 issues and pull requests**—many from first-time contributors. - -- **PyCon US, SciPy, and PyCascades Sprints:** Hosted across three major conferences, these events focused on hands-on mentorship, guiding participants through impactful contributions. - -{% include pyos-blockquote.html quote="Amazing! Leah was so helpful as it was my first time doing anything like that. I had used GitHub for personal projects but never with other people so she was so good at teaching." author="Workshop Participant" event="pyOpenSci sprint 2024" class="highlight purple" %} - -Our sprints strengthened our contributor community and sparked new initiatives, like translating our packaging guide into Spanish and Japanese. - -## Funding and sustainability - -In 2024, we reached an important milestone in pyOpenSci’s journey. Our initial funding from the Sloan Foundation, which gave us our start as a fledgling project, ended in December. The Sloan Open Source Program’s generous support took us a long way—it helped me grow pyOpenSci from a part-time passion project into a thriving community dedicated to lowering barriers to open source scientific software. - -We are deeply grateful for Sloan’s belief in our vision and commitment to open science. Last fall, we also received support from the Chan Zuckerberg Initiative (CZI), which will empower us as we move forward. This combined funding has enabled us to launch our training initiative, expand our peer review program, and co-develop beginner-friendly lessons and tutorials. - -## Building the future: new initiatives and funding efforts - -As we look to the future, our current focus includes: -- **Exploring funding models for pyOpenSci** -- **Growing our peer review partners and program** -- **Connecting with University OSPOS (Open Source Program Offices)** -- **A focus on empowering the community to contribute to open source** -- **Educational Videos that support our online content** - -## Strong together: Why inclusion in open science matters now more than ever - -At a time when access, equity, and sustainability in open science face challenges, we believe in building a space where everyone—regardless of background—can contribute, learn, and thrive. Diversity strengthens open source, and we are committed to ensuring that our community remains welcoming, supportive, and accessible. - -## Sustaining our impact - -As we head into 2025, sustainability and our commitment to diversity, equity, inclusion and accessibility remains a top priority. We’re committed to growing pyOpenSci in ways that support our contributors, empower learners, and strengthen open source science. We’re actively seeking new partnerships, welcoming new contributors and funding opportunities to ensure our work continues to thrive. - -To our funders, contributors, and community members—thank you. Your support makes our work possible and helps us build a future where scientific software is open, accessible, and collaborative. - -As we reflect on where we’ve been and where we’re going, it’s important to acknowledge the teams that continue to guide our vision and growth. -Our leadership teams—including the [Executive Council](https://www.pyopensci.org/our-community/index.html#executive-council-leadership--staff), [Advisory Council](https://www.pyopensci.org/our-community/index.html#pyopensci-advisory-council) and -[Peer Review Editorial Board](https://www.pyopensci.org/about-peer-review/index.html#meet-our-editorial-board) have been instrumental in carving the path forward for pyOpenSci. - -We look forward to building the future of open science, one contribution at a time—and we invite you to join us on this journey. diff --git a/_posts/2025-03-05-pyOpenSci-first-open-science-festival.md b/_posts/2025-03-05-pyOpenSci-first-open-science-festival.md deleted file mode 100644 index 0c9b8be7..00000000 --- a/_posts/2025-03-05-pyOpenSci-first-open-science-festival.md +++ /dev/null @@ -1,219 +0,0 @@ ---- -layout: single -title: "Building Momentum for the Future: Reflections on Our First Open Science Festival Week" -blog_topic: community -excerpt: "pyOpenSci ran 6 workshops in 2024, 4 of which happened during our Fall Festival! Learn more about how the workshops went and watch the keynote talks that introduced Quarto Live, MystMarkdown and stories about reproducibility, open science and open source in this blog." -author: "Leah Wasser" -permalink: /blog/pyopensci-reflections-fall-festival-2024.html -header: - overlay_image: images/headers/pyopensci-floral.png -categories: - - blog-post - - community -classes: wide -toc: true -comments: true -last_modified: 2025-03-05 ---- - -## Our inaugural fall festival was a great success! - -Three years ago, I envisioned an online event where our community could come together to celebrate open source and open science, share knowledge, and learn new skills. Last month, that vision became reality with pyOpenSci’s first-ever [Fall Festival, held from October 28 to November 1](/events/pyopensci-2024-fall-festival.html). The event brought together **64 participants** from over **15 countries**—a global mix of researchers, developers, educators, and Python enthusiasts. - - -
- - - -
- - -The week was packed with inspiring keynotes, hands-on workshops, and informal office hours, where participants connected and reflected on their learning. - -****** - -What did you enjoy most about the fall festival: -> Getting to know about pyOpenSci as an organization and resource. Also, [being provided with] practical, professional tools that I can use right away. -{: .highlight-quote .purple } - -***** - -## Acknowledging our amazing pyOpenSci team - -Events like the Fall Festival don’t happen without an incredible team working behind the scenes to support every step of the process. -I want to extend our heartfelt gratitude to all the instructors, helpers, and keynote speakers who contributed their time and expertise to make this event truly special. Your dedication, energy, and enthusiasm ensured an engaging and impactful experience for everyone involved! - -
- - - Thank You graphic for pyOpenSci Fall Festival, featuring photos of community supporters. The circular portraits include Rich Iannone, Steven Sylvester, Jonny Saunders, Tracy Teal, Carol Willing, Felipe Moreno, Tetsuo Koyama, Isabel Zimmerman, and Jeremiah Paige, all placed against a purple background with the text: Thank You - Fall Festival - pyOpenSci Community Supporters - -
- - -A special shout-out to Carol Willing, Jeremiah Paige, and Jonny Saunders, who supported multiple workshops and co-developed and reviewed many of the lessons now published online. We’re so lucky to have such an incredible community. 🫶 - -## What made this event special - -The vibe made this event special. The energy of participants who wanted to learn together was supported by the vibrant and knowledgeable pyOpenSci community. While many mentioned me directly, the vibe was more than just me. Our community came together to help scientists learn hard technical skills--together and without judgment. - -> I love the engagement…how people were attended to individually despite being in a group setting. I enjoyed that learning could happen in a personal and group setting. -{: .highlight-quote } -> - -We met learners where they were at! - -> The enthusiasm of Leah and her friends, Spatial chat and all new Python things I didn't used before like Packaging, Great Tables, Quarto, Clean Code and Programming in [GitHub] CodeSpaces -{: .highlight-quote } - -Using the interactive platform SpatialChat rather than a traditional online platform like Zoom helped to create that vibe. More on that below. - -## Keynote talks - -We kicked the event off on Monday, October 28, with a morning of KeyNote talks headlined by **Eric Ma, Melissa Mendonça, and Rowan Cockett**. The Monday talks aligned perfectly with the training events held Tuesday through Friday. The talks set the stage for a truly engaging week of learning together. - -
- - - - -
- - -### Eric Ma: The human side of clean code - -{% include video id="JfpetG7nVgc" provider="youtube" %} - -[Eric's talk](/blog/human-dimension-clean-documented-data-science-code.html) highlighted how simple practices like clear documentation, readable code, and user-friendly installation can amplify the impact of data science projects. - -A standout moment? The "Roast Your Repo" exercise! Eric invited attendees to critique a repository from his thesis, showcasing the power of small improvements—like adding a fleshed-out README or modularizing code—to make research reusable and collaborative. It was a fun, hands-on way to explore how the human touch transforms code quality. And let's be honest, we all likely have one of those code bases or repos from our early degrees! I sure do (and it's not even on GitHub!). - -### Melissa Mendonça: From academia to open source - -{% include video id="n9IZGT4DxDs" provider="youtube" %} - -Melissa shared her journey from academia to open source software development, reflecting on the courage it takes to step into the unknown. She celebrated the scientific Python ecosystem, emphasizing how libraries like NumPy and SciPy enable countless domain-specific projects. - -Melissa also highlighted the challenges of volunteer-driven communities, stressing the need for clear governance and transparent user engagement. Her focus on open science principles—transparency, reproducibility, and accessibility—was inspiring and reinforced the importance of collaborative, inclusive practices. - -### Rowan Cockett: Rethinking scientific publishing - -{% include video id="zrwt0PX5mdM" provider="youtube" %} - - -Rowan invited us to imagine a future where scientific publications are as dynamic as the research they describe. He introduced [Myst Markdown](https://mystmd.org/) tools that blend code, data, and narrative and discussed his company, [CurveNote](https://curvenote.com/), which aims to revolutionize scientific publishing. - -His vision of collaborative, interactive, and automated publishing draws from open source principles and challenges the static nature of traditional papers. By rethinking how we share research, Rowan reminded us that we already have the tools to make science more immediately accessible and impactful. - -### Workshops and Quarto Live - -We also invited George Stagg (Posit), developer of Quarto Live and James Balamuta to talk about how Quarto Live is empowering education. Quarto Live makes it easy for you to create interactive data science environments for learning in the browser. - -* Yes, it runs on GitHub pages - no servers needed. -* Yes, students can write Python on their phones. - -Both talks are below. - -#### George Stagg - -{% include video id="W_a80br7t2A" provider="youtube" %} - -#### James Balamuta - -{% include video id="7q1Z84ssxg0" provider="youtube" %} - - - -## Let the (open science & open source) learning begin! - -
- - An illustration of a diverse group of people sitting around a round table in a collaborative meeting setting. Some are using laptops, others are holding notebooks or phones, with modern lighting and decorative plants in the background, creating a warm and productive atmosphere. - -
- -The main "course" of the event was 4 days of active learning. Here, participants engaged in interactive workshops designed to build skills and confidence in writing, sharing, and publishing scientific code. Using SpatialChat, we created a dynamic and collaborative environment that fostered real-time learning and group discussions—something you just can’t replicate in traditional video call platforms like Zoom. - -Our curriculum followed a clear narrative: - -**Write better code → Package it → Share it → Tell an interactive data story.** - -> [I enjoyed....] Leah's teaching style, the platform, and the tutorials. I felt very ease learning together with the pyOpenSci community. -{: .highlight-quote .magenta } - -Each day introduced new tools and practices to help participants transform their workflows and make their science more accessible, reusable, and impactful. Here’s how we approached it: - -### Day 1: Write Better, Cleaner Scientific Code - -We started by diving into [writing better, cleaner code](https://www.pyopensci.org/lessons/write-better-code/clean-modular-code/). Participants explored how to make their code: -- [DRY (Don’t Repeat Yourself)](https://www.pyopensci.org/lessons/write-better-code/clean-modular-code/python-dry-modular-code.html) and modular, -- [Expressive](https://www.pyopensci.org/lessons/write-better-code/clean-modular-code/python-write-expressive-code.html) and easy to understand, -- [Consistent with Python style guidelines](https://www.pyopensci.org/lessons/write-better-code/clean-modular-code/python-pep-8.html) like PEP 8. - -We introduced strategies to improve robustness, such as creating [functions](https://www.pyopensci.org/lessons/write-better-code/optimize-code/) and using [tests and checks](https://www.pyopensci.org/lessons/write-better-code/optimize-code/python-function-checks.html) to validate outputs. - -### Day 2: Create Your First Python Package - -On Day 2, participants learned how to turn their code into reusable, installable Python packages. I've taught Python packaging with Hatch several times over the past year, and everyone consistently loves having an all-in-one tool! They also pick it up quickly, making the tool accessible and usable--a win-win! This was one person's response to their favorite day: - -> ...I thought Hatch was really useful and I liked the fact that it can do a couple of things as one tool. -{: .highlight-quote } - - -The packaging day covered: -- [An overview of Python packaging for beginners](https://www.pyopensci.org/python-package-guide/tutorials/intro.html#what-is-a-python-package), -- Making code [installable in Python environments](https://www.pyopensci.org/python-package-guide/tutorials/create-python-package.html) with tools like [Hatch](https://www.pyopensci.org/python-package-guide/tutorials/get-to-know-hatch.html), -- Adding package metadata using a [pyproject.toml file](https://www.pyopensci.org/python-package-guide/tutorials/pyproject-toml.html), -- Including a [license](https://www.pyopensci.org/python-package-guide/tutorials/add-license-coc.html) to define how others can use and share your Python package. - -### Day 4: share your Python code (with everyone!) - -On Day 4, we empowered participants to share their work more broadly. Many learners enjoyed this day which was cool for me because it was entirely new content I had never taught before but thought was so important to any scientist building software and writing code. Below, we asked them what day their favorite was. The theme of this response was common in the feedback: - - -> [I enjoyed] making package installable via PyPI by using hatch (one often writes code, but never gets to this stage) -{: .highlight-quote .magenta } - - Key share your code topics included: -- [Why sharing code matters](https://www.pyopensci.org/lessons/package-share-code/publish-share-code/share-code.html), -- [Adding a DOI to code using Zenodo](https://www.pyopensci.org/lessons/package-share-code/publish-share-code/cite-code.html), -- Publishing through [JOSS](https://joss.theoj.org/) or the [pyOpenSci peer review process](https://www.pyopensci.org/about-peer-review/index.html), -- Publishing code on [PyPI](https://www.pyopensci.org/lessons/package-share-code/publish-share-code/publish-code.html#pypi) using Hatch. -- NOTE: we also have a [publish to conda-forge lesson](https://www.pyopensci.org/python-package-guide/package-structure-code/publish-python-package-pypi-conda.html). - -
- - An illustration of a diverse team gathered around a large, round table in a collaborative work meeting. Several people are using laptops, while others are holding notebooks or tablets. A small plant decorates the center of the table, and modern pendant lights hang overhead, contributing to a comfortable and productive workspace. - -
- -### Day 5: Interactive data storytelling with Quarto & GreatTables - -We wrapped up with an introduction to [Quarto](https://quarto.org/), a powerful tool for creating dynamic, interactive scientific narratives. Participants explored how to integrate code, data, and findings into a cohesive story—transforming static publications into living, engaging documents. - -We also showcased [Quarto Live](https://quarto.org/docs/guide/), which lets users dynamically interact with code in the browser. For educators, this opens exciting opportunities to create lessons where students can learn directly in a live coding environment. How cool is that? - -Fun fact: GreatTables has been [accepted by pyOpenSci](https://github.com/pyOpenSci/software-submission/issues/202) with the plan to be fast-tracked through the Journal of Open Source Software (JOSS) after the pyOpenSci review through [our JOSS partnership](https://www.pyopensci.org/software-peer-review/partners/joss.html). - - -## Reflections on the 2024 pyOpenSci Fall Festival - -The 2024 Fall Festival was an incredible learning experience for pyOpenSci! One of the standout successes was our last-minute switch to using Spatial Chat as our virtual platform. Participants loved how easy and intuitive it was to use, and it quickly became a seamless way to foster interaction and collaboration. - -> I liked the use of software like spatialchat, where you could break out into groups to work on things. This makes everything a lot more casual and fosters networking. -{: .highlight-quote } - -We were also thrilled to offer 16 scholarships for this event, making the festival accessible to diverse attendees. -We’re excited to continue improving our events to maximize engagement and accessibility for all community members. We look forward to building on this success for future festivals! - -## What's next for pyOpenSci - -Most of the resources used to teach are published on our [pyOpenSci lessons website](https://www.pyopensci.org/lessons). We’re actively working on updates to incorporate additional content still housed in Google Docs, ensuring it’s accessible to all. - -Looking ahead, we’re excited to run more events like this to support pyOpenSci’s broader mission of supporting the open source software scientists need to make their work open. We are planning to: - -* Develop a new set of lessons that support using GitHub collaboratively and empower contributors to make their first open source contributions. -* Run online beginner-friendly sprints that further engage the community in making contributors to open source, no matter how big or small, we want to ease pain points! -* Attend and run events at PyCon 2025 and SciPy 2025, holding events and connecting with you! - -Check our blog and events page for upcoming events and opportunities to engage with the vibrant pyOpenSci community. diff --git a/_posts/2025-03-13-python-packaging-security-pypi.md b/_posts/2025-03-13-python-packaging-security-pypi.md deleted file mode 100644 index 5b594648..00000000 --- a/_posts/2025-03-13-python-packaging-security-pypi.md +++ /dev/null @@ -1,325 +0,0 @@ ---- -layout: single -title: "How to Secure Your Python Packages When Publishing to PyPI" -blog_topic: education -excerpt: "Learn how to secure your Python package PyPI publishing workflows and protect your package from attacks. This post covers actionable steps, using PyPI Trusted Publisher, and sanitizing workflows, to ensure your projects stay safe." -author: "Leah Wasser" -permalink: /blog/python-packaging-security-publish-pypi.html -header: - overlay_image: images/headers/pyopensci-floral.png -categories: - - python-packaging - - blog-post - - community -classes: wide -toc: true -comments: true -last_modified: 2025-03-13 ---- - -## Is your PyPI publication workflow secure? - -We can learn a lot from the Python package breach [involving Ultralytics](https://blog.pypi.org/posts/2024-12-11-ultralytics-attack-analysis/). This breach highlighted the importance of making our PyPI publishing workflows for Python packages more secure. - -In this breach, hackers exploited a GitHub Actions workflow to inject malicious code into a Python package. This package was then published to PyPI. The outcome: users who downloaded the package unknowingly allowed their machines to be hijacked for Bitcoin mining. - -{% include pyos-blockquote.html quote="Hackers tricked a Python package into running bad code, using other people’s computers to mine Bitcoin without permission. Yikes!" class="highlight" %} - -While unsettling, there’s a silver lining: the PyPI security team had already addressed most of the issues that caused this breach. - -{% include pyos-blockquote.html quote="Because the Ultralytics project was using Trusted Publishing and the PyPA’s publishing GitHub Action: PyPI staff, volunteers, and security researchers were able to dig into how maliciously injected software was able to make its way into the package." author="Seth Larson, PSF Security Expert" class="highlight magenta" %} - -This means that the important thing for us, as maintainers, is that we all should know how to lock down our publishing workflows. -Here, I'll cover the lessons learned that you can apply TODAY to your Python packaging workflows! - -*Special thanks to [Seth Larson](https://github.com/sethmlarson), [Hugo van Kemenade](https://github.com/hugovk), [Sviatoslav Sydorenko](https://github.com/webknjaz), [William Woodruff](https://github.com/woodruffw) and [Carol Willing](https://github.com/willingc) for reviewing and significantly improving blog post!!* - -
-## TL;DR Takeaways - -The fall 2024 Ultralytics breach was a wake-up call for all maintainers: secure your workflows to protect your users and the Python ecosystem. The most important steps that you can take are actually the simplest: - -Below are **3 things that you can do right now** to secure your PyPI Python packaging workflow: - -### Secure GitHub--Human and GitHub--PyPI connections - -1. 🔒 If you have a GitHub Action that publishes to PyPI, make sure that the **publish section of your action uses a controlled GitHub environment**. Name that environment `pypi` and set environment permissions in GitHub that allow specific trusted maintainers to authorize the environment to run. I'll show you how to do this below. -1. 🤝 Create a **Trusted Publisher link between your package's (GitHub/GitLab) repository and PyPI**. You can call this trusted connection within the locked-down GitHub environment (named `pypi`) that you created above. -1. 🍒 Add [`zizmor`](https://woodruffw.github.io/zizmor/) to your build to check GitHub Actions for vulnerabilities. You can run zizmor on your workflow files locally, or you can set it up as a pre-commit hook which is probably a better bet. - -Together, these three steps protect both sides of your PyPI publication process--the trigger on GitHub and the connection between GitHub and PyPI. 🚀🚀🚀 - -Don’t wait--start securing your Python publishing workflows today. 🔒 - -
- - -## A call to (GitHub) actions ... - -The Ultralytics breach highlights the need for us all to follow and understand secure PyPI publishing practices and carefully monitor workflows. Below are actionable steps you can take to enhance security when publishing Python packages to PyPI using GitHub Actions. - - [PyPA provides a great overview of using actions to publish your Python package.](https://packaging.python.org/en/latest/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/) -{: .notice } - -## 1. Create a dedicated GitHub environment for publishing actions - -First, make sure that your PyPI publish GitHub Action uses an isolated GitHub environment. Isolated environments ensure your publishing process remains secure even if other parts of your CI pipeline are compromised. This is because you can lock an environment down by ensuring that only specific users can authorize this environment to run. - - -A GitHub Action is a CI/CD (Continuous Integration/Continuous Deployment) tool that allows you to automate tests. [Click here to read more about what CI/CI is.](https://www.pyopensci.org/python-package-guide/maintain-automate/ci.html) -{: .notice .notice--success } - -If you look at the workflow example below, notice that we have an [environment called `pypi`](https://github.com/pyOpenSci/pyosMeta/blob/2a09fba/.github/workflows/publish-pypi.yml#L57) that is used for trusted publishing. The `pypi` environment creates a direct link between this action and PyPI Trusted Published (discussed below). - -```yaml - publish: - name: >- - Publish Python 🐍 distribution 📦 to PyPI - if: github.repository_owner == 'pyopensci' - needs: - - build - runs-on: ubuntu-latest - environment: - name: pypi - url: https://pypi.org/p/pyosmeta -``` -***** - -To lock down a GitHub environment: - -* First, go to the Settings in your repository where the workflow is run -* Within settings, select **environments** from the left-hand sidebar -* Add a new environment. Use pypi as your environment name; this is what PyPA (the Python Packaging Authority) recommends. -* Ensure Required reviewers is enabled. This setting allows you to designate specific individuals who can approve and manually run the workflow on GitHub. Any reviewers you add must have the appropriate permissions to authorize the workflow by clicking a button. This adds a human verification step to the process. -* Once the Required reviewers button is checked, add maintainers who you want to be able to enable the action to run. - -*Optionally, you can click prevent self-review, preventing someone from triggering a release or a build and then running it!* - -
- Animated gif file that shows the GitHub interface where you can click on settings and go to the environment setting to create or edit a GitHub environment -
- To create a new environment to use in a GitHub Action, 1) go to your repo's settings; 2) click environment; 3) add a new environment. In this screenshot, we already have a pypi environment created. Note that you can name your environment whatever you want, however, PyPI suggests that you use the name pypi for a Trusted Publisher workflow. -
-
- - -
- - - Screenshot of the GitHub settings interface showing the ‘Environments’ section with configuration options for ‘pypi.’ The ‘Deployment protection rules’ section is visible, with ‘Required reviewers’ enabled and two reviewers listed: ‘lwasser’ and ‘willingc.’ Other options such as ‘Prevent self-review’ and ‘Wait timer’ are present but not enabled. - -
- GitHub environment settings for “pypi,” displaying deployment protection rules with required reviewers configured for workflow approvals. -
-
- - -## 2. Use Trusted Publisher for PyPI - -Now that you have a GitHub environment setup, you can set up Trusted Publisher in your PyPI account. - -A Trusted Publisher setup creates a secure link between PyPI and your repository. -- PyPI is allowed to authenticate your [package distribution files (sdist and wheel archives)](https://www.pyopensci.org/python-package-guide/package-structure-code/python-package-distribution-files-sdist-wheel.html#how-to-create-the-distribution-format-that-pypi-and-pip-expects) uploads directly, so no additional configuration is required. -- Trusted Publisher restricts publishing to a specific GitHub Actions workflows and environments defined in your repository. - -Using a Trusted Publisher combined with a locked-down environment eliminates the need to store sensitive tokens as GitHub secrets. It also removes the need to refresh and update tokens periodically to avoid token leaks or theft issues. - -
- - - A workflow diagram showing GitHub Actions building distribution files (sdist and wheel), publishing them securely to PyPI, represented as a warehouse. The diagram includes a lock icon emphasizing security, with the pyOpenSci logo in the top-left corner. - -
- Example of the PyPI Trusted Publisher form, used to securely link a GitHub repository with PyPI for publishing Python packages. Trusted Publisher reduces the risk of token theft and improves overall security. -
-
- -If you only [publish locally to PyPI using the command line](https://www.pyopensci.org/python-package-guide/tutorials/publish-pypi.html), you must use a PyPI token. However, if you’re using GitHub Actions to automate your publishing process, setting up **Trusted Publisher** is a secure and easier-to-manage option. -{: .notice } - -### How to get started - -[PyPI provides a great guide to getting started with Trusted Publisher](https://docs.pypi.org/trusted-publishers/adding-a-publisher/). - - -The steps for setting up Trusted Publisher are: -1. Login to your PyPI account -2. Click on your profile to take you to **Your projects**. -3. Click on **publishing** on the left-hand side of the site. (it's below account settings). -4. At the top of the page is a Manage Publishers section. At the bottom, you will see **Add a new pending publisher** -5. Fill out a form that looks like the one below in the add a new pending publisher section. Notice that you can select GitHub, GitLab, Google and ActiveState as platforms. -6. Notice that the form asks for your project name, owner, repo name, workflow's file name, and environment (**STRONGLY recommended**). - - -
- - - PyPI Trusted Publisher form example showing settings for linking a GitHub repository with PyPI for secure publishing. - -
- Example of the PyPI Trusted Publisher form, used to securely link a GitHub repository with PyPI for publishing Python packages. Trusted Publisher reduces the risk of token theft and improves overall security. -
-
- -For an example of a GitHub workflow that uses Trusted Publishing, check out our active pyOpenSci [PyPI publishing GitHub workflow](https://github.com/pyOpenSci/pyosMeta/blob/main/.github/workflows/publish-pypi.yml), which follows the Trusted Publisher approach. - - -
- - - PyPI Trusted Publisher manage settings showing what the Trusted Publisher setup looks like after you've created it in PyPI. It shows all of the items that you filled out in the form and has a remove button if you want to remove it from PyPI. - -
- Example of the PyPI Trusted Publisher setup in PyPI once you've created the Trusted PuUblisher link by filling the form out above. -
-
- - -**Note:** Read more here about [support for publishing to GitLab](https://docs.pypi.org/trusted-publishers/adding-a-publisher/#gitlab-cicd) using trusted publishing. -{: .notice } - -## 3. Add `zizmor` to your CI workflows - -Finally, consider adding [Zizmor](https://woodruffw.github.io/zizmor/) to your [Continuous Integration (CI)](https://www.pyopensci.org/python-package-guide/maintain-automate/ci.html#what-is-continuous-integration) and [pre-commit checks](https://www.pyopensci.org/python-package-guide/package-structure-code/code-style-linting-format.html#use-pre-commit-hooks-to-run-code-formatters-and-linters-on-commits). - -Zizmor is a static analysis tool designed to help identify GitHub Action security issues. Zizmor scans your workflows and highlights common vulnerabilities, ensuring your continuous integration / continuous deployment pipelines remain secure and efficient. - -Named as a playful nod to Dr. Zizmor’s famous “clear skin” ads, zizmor aims to give you “beautiful clean workflows.” - -Learn more about zizmor on the [official blog post by William Woodruff](https://blog.yossarian.net/2024/10/27/Now-you-can-have-beautiful-clean-workflows). -{: .notice .notice--success } - -### How it works - -To use zizmor locally to check your workflows, first install it using `pip` or `pipx`: - -`pip install zizmor` - -Then, ask it to check a specific workflow file (or set of files). - -Below, I ran it on our pyosMeta PyPI build. Among other things, it found a template injection risk in our build that we can easily fix by adding a sanitization step discussed below! - -PyPI really is on top of things! - -```console -$ zizmor .github/workflows/publish-pypi.yml - -error[template-injection]: code injection via template expansion - --> path/here/pyosMeta/.github/workflows/publish-pypi.yml:97:7 -github.ref_name may expand into attacker-controllable code -``` - -You can also set up `zizmor` as a pre-commit hook. pyOpenSci plans to do this in the future, but here is an example of it [set up for core Python](https://github.com/python/cpython/pull/127749/files#diff-63a9c44a44acf85fea213a857769990937107cf072831e1a26808cfde9d096b9R64). - -Pre-commit hooks run checks every time you commit a file to Git history. [Learn more about using them here.](https://www.pyopensci.org/python-package-guide/package-structure-code/code-style-linting-format.html#use-pre-commit-hooks-to-run-code-formatters-and-linters-on-commits) - -## Other security measures you can consider - -There are other things that we can learn too from the recent breach. Many of these will be identified if you set up zizmor. These are discussed below. - - -### Sanitize branch names in your workflow - -One of the critical issues in the Ultralytics breach involved a -[**malicious branch name containing a shell script**](https://github.com/ultralytics/ultralytics/pull/18020) -that was executed because `github.ref` was used without sanitization. This type -of attack, known as **template injection**, allows malicious content in branch -names to be treated as shell commands. - -{% include pyos-blockquote.html quote="...is a classic GitHub Actions template injection: the expansion of `github.head_ref || github.ref` is injected directly into the shell’s context, with no quoting or interpolation.." author="https://blog.yossarian.net/2024/12/06/zizmor-ultralytics-injection" class="highlight magenta" %} - -Because the branch name wasn’t sanitized, it was treated as a shell command and executed with full permissions. Yikes! - -In the example below, an unsanitized branch name could execute harmful commands: - -```yaml -jobs: - example-job: - runs-on: ubuntu-latest - steps: - - name: Run a script - run: | - echo "Running script for branch: $GITHUB_REF" -``` - -To prevent this, [sanitize or clean](https://docs.github.com/en/get-started/using-git/dealing-with-special-characters-in-branch-and-tag-names) branch names before using them. A small Bash step can -remove unsafe characters: - - -```yaml -jobs: - example-job: - runs-on: ubuntu-latest - steps: - - name: Sanitize branch name - run: | - SAFE_BRANCH=$(echo $GITHUB_REF | sed 's/[^a-zA-Z0-9_\-\/]//g') - echo "Sanitized branch name: $SAFE_BRANCH" - echo "Running script for branch: $SAFE_BRANCH" -``` - - -## Lock down GitHub permissions & delete old PyPI tokens and GitHub secrets - -In addition to securing your workflows, lock down your accounts and repositories. 2FA (2-factor authentication) is thankfully now required as a security measure for both GitHub and PyPI. However, be sure to store your recovery codes somewhere safe (like in a password manager!). - -Also consider: - -- **Revoking old tokens**: If you've previously created PyPI API tokens and/or associated GitHub secrets, delete any unused or outdated ones. -- **Restrict repository access**: Limit write GitHub repository access to a trusted subset of maintainers. Most contributors don’t need direct write access, which reduces security risks. - - -### 🚫 Avoid `pull_request_target` and consider release-based workflows - -A trigger event in a GitHub Action is an event that sets off an action to run. For instance, you might have a trigger that runs a linter like Black or Ruff when a new pull request is opened. - -The [`pull_request_target`](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target) trigger event in GitHub Actions that Ultralytics used allows workflows to run with elevated permissions on the base branch, even when triggered by changes from a fork. Thus, your workflow becomes vulnerable when used as a trigger to push a release to PyPI. - -Instead of a pull_request_target or a pull_request, consider adopting a **release-based publishing workflow**. This approach: - -- Triggers publication workflows only on new versioned releases. You can lock down which maintainers are allowed to create releases using GitHub permissions -- Ensure workflows related to publishing are explicitly scoped to `release` events. - -In the example GitHub Action `.yaml` file below, you see a `release` trigger defined. This tells the action to only trigger the workflow when you publish a release. - - -```yaml -name: Publish to PyPI -on: - # By using release as a trigger, only GitHub users and actions with write access to make releases to our repo can trigger the push to PyPI - release: - types: [published] -``` - -Using a release-based workflow ensures that your publishing step is tightly controlled. A pull request will never accidentally trigger a publish build. This reduces your risk! - - - -## Don’t cache package dependencies in your publish step - -Caching dependencies involves storing dependencies to be reused in future workflow runs. This approach saves time, as GitHub doesn’t need to redownload all dependencies each time the workflow runs. - -However, caching dependencies can allow attackers to manipulate cached artifacts. If this happens, the workflow may pull in compromised versions from the cache during the next run. - - -## Learn More - -pyOpenSci follows best practices for PyPI publishing using our custom GitHub Actions workflow. Check out our tutorial on Python packaging here: -👉 [pyOpenSci Packaging Tutorial](https://www.pyopensci.org/python-package-guide/package-structure-code/python-package-structure.html) -👉 Join our discourse here - -
-## Get involved with pyOpenSci - -* Check out our [volunteer page](https://www.pyopensci.org/volunteer.html) if you are interested in getting involved. -* Keep an eye on our [events page](/events.html) for upcoming training events. - -Follow us on social platforms: - -* [ Mastodon](https://fosstodon.org/@pyopensci) -* [ Bluesky](https://bsky.app/profile/pyopensci.org) -* [ LinkedIn](https://www.linkedin.com/company/pyopensci) -* [ GitHub](https://github.com/pyOpenSci) - -If you are on LinkedIn, you should [subscribe to our newsletter, too](https://www.linkedin.com/newsletters/7179551305344933888/?displayConfirmation=true). -
diff --git a/_posts/2025-03-18-contribute-to-open-source-github-lessons.md b/_posts/2025-03-18-contribute-to-open-source-github-lessons.md deleted file mode 100644 index 5d2f2d3a..00000000 --- a/_posts/2025-03-18-contribute-to-open-source-github-lessons.md +++ /dev/null @@ -1,100 +0,0 @@ ---- -layout: single -title: "Contribute to Open Source Software: It's More Than just Code" -blog_topic: education -excerpt: "Contributing to open source isn’t just about code—it’s also about navigating social norms. Discover pyOpenSci’s new Contribute to Open Source lessons, which cover both the technical and social aspects, and are free for anyone to use." -author: "Leah Wasser" -permalink: /blog/contribute-to-open-source-lessons.html -header: - overlay_image: images/headers/pyopensci-floral.png -categories: - - blog-post - - community - - contribute-to-open-source -classes: wide -toc: true -comments: true -last_modified: 2025-03-11 ---- - - -## Beyond code: the social side of open source - -When you think about contributing to open source, you might assume the biggest hurdle for newcomers is technical--learning Git, using GitHub, and/or writing code. Most contribute to open source guides focus on technical skills. But for many new contributors, the challenge isn’t only technical—it’s social too. - -Receiving open feedback on your contributions in the form of code review, whether code or documentation, [creates anxiety for many](https://osf.io/preprints/psyarxiv/8k5a4_v1). - -- **Joining an unfamiliar community:** Understanding who to reach out to and how the project operates can feel intimidating. -- **Understanding unspoken norms:** Every project has its own culture, from how discussions happen to how decisions are made. And these norms are not always well-documented. -- **Building confidence in a new space:** Many contributors worry about making mistakes, be it while using GitHub or submitting a Pull Request or even in the content updates themselves. -- **Navigating constructive feedback** – Open source is built on open peer review; however, receiving public feedback on a contribution can be intimidating. - - -In my 10+ years of building and maintaining software, contributing to projects, and running beginner-friendly sprints, I’ve seen firsthand that communication, collaboration, and project culture are just as—if not more—important than technical skills. I've also experienced the imposter syndrome first-hand. I was nervous about my first contributions and wasn't sure how to start contributing in a meaningful way. - -**Technical and social skills go hand in hand.** Open source communities are most productive when contributors and maintainers recognize this balance between the technical and social skills associated with contributing. In most cases, all of the people involved in the project are volunteers with varying priorities, skillsets, and motivations to participate. -{: .notice .notice--info} - -### Developing our contribute to open source lessons - -This past year, with support from the [Better Software for Science (BSSw) Fellowship](https://bssw.io/fellows/leah-wasser), I developed lessons shaped by insights gained from the [pyOpenSci contributor community](https://www.pyopensci.org/our-community/index.html). Our lessons are now freely available as open educational resources designed to help contributors and maintainers foster a more welcoming, collaborative and productive open source community. - -Check out our lessons now. - -> BSSw is a unique partnership between the National Science Foundation (NSF) and the Department of Energy (DOE) that provides small grants to advocates in the scientific open source space. -{: .notice } - -### A community-driven approach to open source - -Over the past two years, **pyOpenSci has welcomed over 300 contributors**, with more than **120 issues and pull requests** submitted during our [beginner-friendly sprints](/blog/pyopensci-pyconus-2024-sprints.html). These experiences reinforced what we already knew—technical skills are just one part of people successfully contributing to open source. - -Through our sprints, we saw firsthand how **clear guidance, supportive communities, and transparent contribution processes** helps newcomers gain confidence. Often, they stick around our community after the events. These insights shaped our *Contribute to Open Source* lessons. - -## Beyond code: The social skills of open source - -Our lessons address both the technical and social aspects of contributing to open source. - -### For contributors: how to navigate your first contribution - -For contributors, we focus on: -1. Understanding the technical steps surrounding making your first contribution -2. Signals that tell you that a project welcomes newcomers and, -3. What to expect as they navigate their first contributions. - -A few highlights include: - -- **[Get to know a repository](https://www.pyopensci.org/lessons/contribute-open-source/get-to-know-repo.html):** Learn how to look for documentation files like `CONTRIBUTING` and `DEVELOPMENT` guides that will become your guiding light in making your first contributions. These files should help you understand whether new contributions are welcome, what the contribution process is, and what types of contributions are welcome. -- **[Find and understand issues](https://www.pyopensci.org/lessons/contribute-open-source/identify-issue.html):** Explore how to search and create GitHub issues as you begin the process of finding a task to work on. Learn how to communicate effectively with maintainers who you may have never interacted with before. -- **[Create effective pull requests](https://www.pyopensci.org/lessons/contribute-open-source/pull-request.html):** Learn how to open a pull request, write clear pull request messages titles and descriptions. Also, learn how to make the pull request process more efficient by reviewing your own work and linking your PR to an issue to streamline maintainer review. - -### For Maintainers: Creating a contributor-friendly repository - -While these lessons are focused on contributing, we also provide some maintainer tips to help set clear expectations for contributors: - -- **Communicate your availability:** Let contributors know if you have time to support them. It’s okay if you're busy—just set clear expectations! -- **Provide clear guidelines:** Add `CODE_OF_CONDUCT`, `CONTRIBUTING`, and `DEVELOPMENT` files to define what contributions you welcome and how contributors should engage with your project. -- **Use automation to support contributors:** Tools like [pre-commit.ci](https://pre-commit.ci/) help automate formatting and linting, reducing friction for new contributors. - -## Co-Creating open source contribution lessons - -Our *Contribute to Open Source* lessons, like all pyOpenSci resources, are co-developed collaboratively. By bringing together contributors with different backgrounds and skill levels, we ensure our materials are clear, accurate, and welcoming. - -A diverse contributor base makes our lessons stronger: -* **Experts** provide technical accuracy. -* **Beginners** ensure content is approachable. -* **Everyone in between** help refine and improve the content. - -This same community-driven approach shaped our [packaging guide](https://www.pyopensci.org/python-package-guide/), which covers [packaging tools](https://www.pyopensci.org/python-package-guide/package-structure-code/python-package-build-tools.html) and [tutorials](https://www.pyopensci.org/python-package-guide/tutorials/intro.html). Like our lessons, it was co-developed and openly reviewed to ensure accessibility and clarity. - -
- - - A diagram illustrating the co-development of open lessons by pyOpenSci. The diagram consists of a circular section divided into five segments labeled ‘Maintainers,’ ‘Beginners,’ ‘Experts,’ ‘Researchers,’ and ‘Contributors,’ surrounding a central section labeled ‘Moderation.’ An arrow extends from the circle to the right, labeled with key moderation activities such as ‘Listen,’ ‘Moderate,’ ‘Facilitate,’ ‘Structured review,’ and ‘Code of Conduct,’ leading to ‘Accessible Lessons.’ The background features a subtle floral pattern. - -
- -By openly co-developing and refining these resources together, we’re making scientific open source more accessible for everyone. - -## What's next? Broadening participation in open source - -With [PyCon US](https://us.pycon.org/2025/) and SciPy on the horizon, we’re excited to put these lessons into action. At our upcoming sprints, we’ll work together to **refine these resources—improving the lessons and packaging guide to make participating open source even more accessible.** If you plan to be at either meeting, keep an eye out for pyOpenSci events! diff --git a/_posts/2025-03-21-pyopensci-commitment-inclusion.md b/_posts/2025-03-21-pyopensci-commitment-inclusion.md deleted file mode 100644 index 55a0ec76..00000000 --- a/_posts/2025-03-21-pyopensci-commitment-inclusion.md +++ /dev/null @@ -1,72 +0,0 @@ ---- -layout: single -title: "Reaffirming pyOpenSci's Commitment to Inclusion" -blog_topic: community -excerpt: "pyOpenSci is growing a global, vibrant and inclusive open source community where everyone from all backgrounds and identities can contribute to better, more open science. Join us in breaking down barriers and building a future where all voices shape scientific discovery." -author: "Executive Council" -authors: "Leah Wasser, Karen Cranston, Tracy Teal" -h2: "pyOpenSci’s mission has always been founded on inclusion" -permalink: /blog/pyopensci-commitment-inclusion.html -header: - overlay_image: images/headers/pyopensci-floral.png -categories: - - blog-post - - community - - inclusion -classes: wide -toc: true -comments: true -last_modified: 2025-03-21 ---- - -During challenging times, it’s critical to pause and reflect on who we are, what we care about, and why our work matters. Since its inception, pyOpenSci, a global community fiscally sponsored in the U.S., has been committed to actively building an inclusive, welcoming, open source community of practice that supports better, more open science. Our Code of Conduct reinforces our [values,](https://www.pyopensci.org/handbook/CODE_OF_CONDUCT.html) as does the thought that we put into the events that we run, the accessible resources that we develop, and the work that we do to make our [peer review program inclusive](https://www.pyopensci.org/#broadening-participation-in-scientific-open-source) and [creating Python software easier](https://www.pyopensci.org/python-package-guide/tutorials/intro.html). - -Today, we celebrate the core values that drive pyOpensci. Today, we reaffirm our long-standing commitment to building an inclusive, open source community and highlight our mission. - -> pyOpenSci broadens participation in scientific open source by breaking down social and technical barriers. - -And this is how we achieve our mission: - -> We are a community of novice to expert Pythonistas; together, we make creating, finding, sharing, and contributing to reusable code more accessible to everyone everywhere, supporting open science and advancing discovery. - -## Carving out space for everyone - -Creating an inclusive and welcoming space is core to broadening participation in open source and open science. - -* When people feel supported, they contribute -* When knowledge is shared, communities grow and thrive. - -Through mentorship, beginner-friendly events, and accessible resources, we actively invite those who might not otherwise see a place for themselves in open source. Our review process includes both developers and those who use the software; here, inclusion ensures that our reviews consider usability and accessibility in addition to the technical nuances of packaging. - -
- - - ... - -
- -Through mentorship and support, we carve out space for new reviewers, which includes those from historically underrepresented backgrounds in open source to contribute to peer review. Similarly, our beginner-friendly lessons and training events are co-developed with beginner-to-expert contributors to ensure the technical concepts are accessible to more people. - -**Inclusion invites everyone to the table**; it creates space for new contributors and supports existing community members. Inclusion creates opportunities for everyone to work together. Together, we share knowledge, shape best practices, and ensure our work serves the broader community. - -
- - - ... - -
- -## A blossoming ecosystem of contributors - -By breaking down barriers, we create open science on-ramps that help everyone learn, contribute, and grow together. In many ways, open source communities are like thriving gardens—diverse, vibrant, and sustained by many contributors. - -The most vibrant and resilient gardens are full of diverse color, texture, and life, attracting pollinators that sustain the whole system. Cultivating diversity builds resilience. Similarly, contributors in our pyOpenSci community come from different backgrounds, identities, and experience levels. - -When we make space for all contributors—ensuring they have what they need to grow—our community becomes more resilient as the ecosystem evolves. Scientific discovery happens organically. The more perspectives and experiences we include, the more impactful our work becomes. - -
- - - ... - -
diff --git a/_posts/2025-05-06-pyos-pyconus-2025.md b/_posts/2025-05-06-pyos-pyconus-2025.md deleted file mode 100644 index 1cadecb1..00000000 --- a/_posts/2025-05-06-pyos-pyconus-2025.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -layout: single -title: "pyOpenSci at PyCon US 2025 - Python, Packaging and Community" -blog_topic: community -excerpt: "pyOpenSci is excited to be at PyCon US for the third year in a row! Find us at the Maintainers summit on May 16th and then join us for a beginner-friendly sprint after the meeting." -author: "Leah Wasser" -permalink: /blog/pyopensci-at-pyconus-2025.html -header: - overlay_image: images/blog/headers/pycon-us-2024-header.png -categories: - - blog-post - - community -classes: wide -toc: true -comments: true -event: - start_date: "2025-05-16" - location: Online -last_modified: 2025-05-06 ---- - -
-## TL;DR - -* We’re excited to get back to PyCon US this year! -* Join us for the Maintainers Summit, community events, and the Packaging Summit. At the end of each meeting, we will hold beginner-friendly sprints. -* Join us in our beginner-friendly sprints on Monday / Tuesday following the conference. -* Last year’s recap: [Read about pyOpenSci at PyCon US 2024](/blog/recap-pyos-pyconus-2024.html) - -
- -## PyCon US Maintainers Summit: Co-hosted by pyOpenSci - -I'm thrilled to be a second-time organizer of the PyCon US [Maintainers Summit](https://us.pycon.org/2025/events/maintainers-summit/) alongside long-time organizer [Inessa Pawson](https://github.com/InessaPawson) and Python Steering Council alum and past PyCon chair [Mariatta Wijaya](https://github.com/mariatta). This annual event brings maintainers, contributors, and community leaders together to share challenges and strategies in maintaining and sustaining open source software. - - -
- - - Group of attendees collaborating at round tables during the PyCon US Maintainers Summit 2025, with text that reads ‘Join Us! PyCon US Maintainers Summit 2025 – Friday 16 May 2025’. Two organizers pose and smile in the foreground wearing conference badges and masks. - -
- -All are welcome to attend, listen to talks, and connect with fellow Pythonistas who care about maintaining open source software! - -## Python packaging summit - -We’re also excited to attend the **Packaging Summit** this year. There's been a lot of work to improve the Python packaging ecosystem in the past year. This event is an excellent opportunity to learn more about current efforts. - -## Community lead activities - -Our advisory council member Chase Million will be hosting a space-focused open space (that's a mouthful!!), so if you are in the space science arena, keep an eye out on the Open Space board in the Hallway track at PyCon US for that! - -Also, check out [Zack Weinberg's talk on writing Extensions in Python that can be interrupted if you need to pause the task](https://us.pycon.org/2025/schedule/presentation/39/). diff --git a/_posts/2025-06-30-pyos-scipy-2025.md b/_posts/2025-06-30-pyos-scipy-2025.md deleted file mode 100644 index c3894aea..00000000 --- a/_posts/2025-06-30-pyos-scipy-2025.md +++ /dev/null @@ -1,187 +0,0 @@ ---- -layout: single -title: "pyOpenSci at SciPy 2025 - science, Python and Community" -blog_topic: community -excerpt: "pyOpenSci is excited to be at SciPy 2025 this year! Find us at our second packaging tutorial, a birds of a feather session and hanging out in sessions where our community members are leading events and talks. Don't forget to join us for our beginner-friendly sprint after the meeting!" -author: "pyopensci" -permalink: /blog/pyopensci-at-scipy-2025.html -header: - overlay_image: images/headers/pyopensci-scipy-2025.png -categories: - - blog-post - - community -classes: wide -toc: true -comments: true -last_modified: 2025-05-06 ---- - -## pyOpenSci at SciPy 2025: Community, Contribution, and Packaging Know-How - -We're heading to SciPy 2025 in Tacoma, Washington, and we couldn’t be more excited to connect with the incredible scientific Python community in person! This year, we’re showing up with tutorials, conversations, and community-driven events designed to support open science and lower the barrier to contributing to open source. - -Here's how you can connect with us throughout the conference: - - -### Learn Packaging with pyOpenSci (Tutorial) - -pyOpenSc is back this year, teaching a hands-on tutorial on **Python packaging for scientific software**. Whether you’re just getting started or looking to strengthen your skills, this session is for you. You'll leave with a better understanding of how to create Python packages that are shareable, reusable, and aligned with scientific best practices. - -**When:** Monday, July 7, 2025, at 1:00 pm PDT \ -**Where:** Room 613/614, Greater Tacoma Convention Center \ -**Learn more / Register:** [SciPy 2025 Tutorial: Learn Packaging with pyOpenSci](https://cfp.scipy.org/scipy2025/talk/Z3VBWR/) \ -**Who’s teaching:** - -* **[Leah Wasser](https://github.com/lwasser)**: Executive Director and founder of pyOpenSci and open science community builder -* **[Carol Willing](https://github.com/willingc)**: Long-time Python core developer and Jupyter contributor -* **[Jeremiah Paige](https://github.com/ucodery)**: Pythonista and packaging expert -* **[Tetsuo Koyama](https://github.com/tkoyama010)**: Software engineer and scientific open source contributor -* **[Inessa Pawson](https://github.com/InessaPawson)**: Open Source program manager at Open Teams, and advocate for inclusive open source - -**Note:** Tutorial registration is separate from your main conference ticket, so grab your spot early if you want in! - -### BoF: Let’s Talk About Python Packaging Pain Points - -**When:** Thursday, July 10, 2025, 1:15–2:10 pm PDT \ -**Where:** Room 315, Greater Tacoma Convention Center - -We’re thrilled to be hosting a **Birds of a Feather (BoF)** session this year—a full-circle moment, since pyOpenSci first kicked off with a BoF at SciPy in 2019. This time, we're gathering folks to talk candidly about the **pain points and friction** in Python packaging for scientific software. Join us for an interactive session. Share your pain points to help direct the work that pyOpenSci does over the upcoming year, and learn how to get involved. - -#### About the BoF - -This Birds of a Feather session invites attendees to join a community conversation around the challenges and solutions in packaging scientific software using Python. From figuring out where to start with packaging to troubleshooting wheels, reproducibility, and publishing to PyPI or conda-forge, we know many of these steps aren’t well-documented or can feel overwhelming. - -This session is a space for maintainers, contributors, and users to share: - -* What’s worked and what hasn’t in their packaging journey -* Resources, tools, or templates that have helped -* Ideas for how the community can better support each other - - -### [Open Code, Open Science — What’s Getting in Your Way?](https://cfp.scipy.org/scipy2025/talk/3ZHYMH/) - -Collaborating on code and software is essential to open science—but it’s not always easy. Join this BoF for an interactive discussion on the real-world challenges of open source collaboration. We’ll explore common hurdles like Python packaging, contributing to existing codebases, and emerging issues around LLM-assisted development and AI-generated software contributions. - -We’ll kick off with a brief overview of pyOpenSci—an inclusive community of Pythonistas, from novices to experts—working to make it easier to create, find, share, and contribute to reusable code. We’ll then facilitate small-group discussions and use an interactive Mentimeter survey to help you share your experiences and ideas. - -Your feedback will directly shape pyOpenSci’s priorities for the coming year, as we build new programs and resources to support your work in the Python scientific ecosystem. Whether you’re just starting out or a seasoned developer, you’ll leave with clear ways to get involved and make an impact on the broader Python ecosystem in service of advancing scientific discovery. - -**Session Conveners:** - -* **[Leah Wasser](https://github.com/lwasser)** – Executive Director and Founder of pyOpenSci -* **[Inessa Pawson](https://github.com/InessaPawson)** – Open Teams Open Source Program Manager, NumPy Steering Council Member -* **[Tetsuo Koyama](https://github.com/tkoyama010)** – Scientific computing and visualization developer, PyVista team -* **[Jeremiah Paige](https://github.com/ucodery)** – Python packaging expert -* **[Avik Basu](https://github.com/ab93)** – Scientific open source contributor - -### **[Sprint With Us!](https://www.scipy2025.scipy.org/sprints)** - -**When:** July 12-13, 2025 \ -**Details:** Breakfast at 8:00 a.m., Kickoff at 9:00 a.m. - -Join us during the SciPy sprint weekend to work on real-world problems in open science. - -You can help us: - -* Improve and write documentation -* Review tutorials -* Translate our Python packaging guide -* Contribute to our infrastructure - -Our sprints are: - -* Beginner-friendly -* Focused on Python packaging, documentation, and translation -* Collaborative and welcoming - -No prior open source experience? No problem. We’ve got guided lessons and mentors ready to help you take your first (or next!) step as a contributor. - -Check out our [help-wanted project board](https://github.com/orgs/pyOpenSci/projects/3/views/2) to get a sense of some of the ways you can contribute or help pyOpenSci during the event. - -### You won’t want to miss these pyOpenSci community member talks - -Make sure to support members of the pyOpenSci community who are speaking or presenting at SciPy 2025! - -Let’s cheer each other on, learn together, and continue making open science more accessible! - -### pyOpenSci Community Member Tutorials - -#### [Shiny for Python – Building Production-Ready Dashboards](https://cfp.scipy.org/scipy2025/talk/HEHW8W/) - -When: **Tuesday, July 8th, 2025, 1:30–5:30 pm PDT** -Where: **Ballroom D, Greater Tacoma Convention Center** - -If you’re ready to level up your Python dashboards, don’t miss **[Daniel Chen’s](https://github.com/chendaniely)** hands-on workshop at SciPy 2025: *Shiny for Python – Building Production-Ready Dashboards*. Held on July 8, this half-day session will walk you through creating scalable web applications using Shiny’s new Python framework. You’ll learn to refactor apps with modules, write tests with Playwright, and deploy dashboards for free. - -Daniel brings deep teaching experience from his roles at Posit and UBC, making this ideal for Python users looking to expand into interactive web development—even if you’ve never touched Shiny before. - -Check out the dashboard templates in advance:[ Shiny for Python Templates](https://shiny.posit.co/py/templates/) - - -#### [Reproducible ML Workflows with pixi](https://cfp.scipy.org/scipy2025/talk/GDN8PN/) - -**When: **Monday, July 7th, 2025, 1:30–5:30 pm PDT** -**Where: **Room 315, Greater Tacoma Convention Center** - -Don’t miss *Reproducible Machine Learning Workflows for Scientists with pixi*—a practical, hands-on tutorial at SciPy 2025 led by data physicist **[Matthew Freikert](https://cfp.scipy.org/scipy2025/speaker/H8ZFYG/)**. This half-day workshop tackles one of the most frustrating pain points in scientific computing: building reproducible, CUDA-enabled environments across diverse machines. - -Matthew will walk participants through using Pixi, an fast, and efficient environment manager that supports conda, to create robust Python workflows with PyTorch and JAX. You’ll learn to install hardware-accelerated environments, solve for multi-platform lock files, and deploy them via container images—no prior CUDA or ML expertise required. - -Come prepared to code and leave with a repeatable setup for your AI/ML experiments. - -Learn more about pixi:[ https://pixi.sh](https://pixi.sh) - -#### [3D Visualization with PyVista](https://cfp.scipy.org/scipy2025/talk/MHNTAD/) - -**When: **Monday, July 7th, 2025, 1:30–5:30 pm PDT** -Where: **Ballroom D, Greater Tacoma Convention Center** - -Get ready to bring your data to life in *3D Visualization with PyVista*, a hands-on SciPy 2025 tutorial led by PyVista co-creator **[Bane Sullivan](https://cfp.scipy.org/scipy2025/speaker/NEC33M/)** and core developer **[Tetsuo Koyama](https://github.com/tkoyama010)**. Taking place July 7, this session will introduce you to PyVista—a Pythonic interface to VTK that makes 3D plotting as intuitive as using Matplotlib. - -Used in over 2,000 open-source projects, PyVista is the go-to library for interactive 3D visualization across fields like engineering, geophysics, and scientific computing. You’ll explore real-world datasets, master PyVista’s data structures and filters, and learn how to integrate with libraries like meshio and trimesh. Plus, you’ll get hands-on with Jupyter plugins for server- and client-side rendering. - -Whether you’re a Python beginner or a power user looking to deepen your visualization toolkit, this tutorial is for you. - -Get set up before the workshop:[ https://tutorial.pyvista.org/getting-started.html#installation](https://tutorial.pyvista.org/getting-started.html#installation) - - -### pyOpenSci Community Member Talks - - -#### [Packaging a Scientific Python Project](https://cfp.scipy.org/scipy2025/talk/RECJVV/) - -**When: **Wednesday, July 9th, 2025, 1:55–2:25 pm PDT** -Where: **Room 317, Greater Tacoma Convention Center** - -If you’ve ever felt overwhelmed by the rapidly evolving Python packaging ecosystem, **[Henry Schreiner’s](https://github.com/henryiii)** talk at SciPy 2025 is a must-attend. In *Packaging a Scientific Python Project*, Henry walks attendees through best practices for preparing scientific software for distribution using the **Scientific Python Development Guide**—an evolving resource he helped author. - -This session covers everything from backend selection and GitHub Actions to packaging compiled components with tools like scikit-build-core, pybind11, and cibuildwheel. Henry will also demo the repo-review tool, which checks your project against current best practices—right in the browser using WebAssembly. - -Whether you’re starting a new package or upgrading an old one, you’ll walk away with modern workflows and tooling that make robust, reproducible packaging less painful—and more powerful. - - -
-## Connect with us - -There are lots of ways to get involved if you are interested! - -* If you read through our lessons and want to suggest changes, open an issue in our [lessons repository here](https://github.com/pyOpenSci/lessons). -* [Volunteer to be a reviewer for pyOpenSci’s software review process](https://docs.google.com/forms/u/6/d/e/1FAIpQLSeVf-L_1-jYeO84OvEE8UemEoCmIiD5ddP_aO8S90vb7srADQ/viewform?usp=send_form). -* [Submit a scientific Python package to pyOpenSci for peer review.](https://www.pyopensci.org/software-peer-review/how-to/author-guide.html#submit-your-package-for-peer-review) -* [Donate to pyOpenSci](https://give.communityin.org/pyopensci_2024?ref=ab_0sHhtifYvgR0sHhtifYvgR) to support scholarships for future training events and the development of new learning content. -* Check out our [volunteer page](https://www.pyopensci.org/volunteer.html) for other ways to get involved. - -You can also: - -* Keep an eye on our [events page](https://www.pyopensci.org/events.html) for upcoming training events. - -Follow us on socials: - -* [Mastodon](https://fosstodon.org/@pyopensci) -* [Bluesky](https://bsky.app/profile/pyopensci.org) -* [LinkedIn](https://www.linkedin.com/company/pyopensci/) -* [GitHub](https://github.com/pyOpenSci) - -If you are on LinkedIn, check out and [subscribe to our newsletter](https://www.linkedin.com/newsletters/7179551305344933888/?displayConfirmation=true), too. - -
diff --git a/_posts/2025-07-24-scipy-2025-recap.md b/_posts/2025-07-24-scipy-2025-recap.md deleted file mode 100644 index e2212272..00000000 --- a/_posts/2025-07-24-scipy-2025-recap.md +++ /dev/null @@ -1,147 +0,0 @@ ---- -layout: single -title: "Highlights from SciPy 2025: Building Community, Code, and Culture" -blog_topic: community -excerpt: "Explore pyOpenSci’s highlights from SciPy 2025—from Python packaging workshops and talks to first-time contributions, collaboration, and open science in action." -author: "pyopensci" -permalink: /blog/scipy-2025-recap.html -header: - overlay_image: images/headers/pyopensci-scipy-2025.png -categories: - - blog-post - - community -classes: wide -toc: true -comments: true -last_modified: 2025-05-06 ---- - - -
- - - A room full of attendees at the pyOpenSci packaging workshop during SciPy 2025, all seated and coding on laptops. - -
- -[SciPy 2025](https://www.scipy2025.scipy.org/) was one for the books, and for [pyOpenSci](https://www.pyopensci.org/), it was a powerful reminder of how far our community has come and how much energy we bring when we show up together. - -This year, more than 15 members of the pyOpenSci community participated in the conference. We hosted a workshop, facilitated a BoF session with over 60 participants, gave talks, and filled the hallway track with laughter, advice, and real conversations about scientific Python pain points and the future of research software. We also led a community sprint where contributors opened over 30 issues and pull requests—an incredible showing of collaboration across experience levels. - -To help new contributors feel supported, we shared our ['Contribute to Open Source' lessons](https://www.pyopensci.org/lessons/contribute-open-source/), which walk you through the process of making your first contribution in a welcoming, beginner-friendly way. - -Here are a few additional highlights from the week. - -## ⚙️ Workshops: Packaging tools, templates, and fast-track setups - -We kicked off SciPy with our pyOpenSci packaging workshop, where we introduced participants to our user-friendly packaging template and taught modern packaging practices using [Hatch](https://hatch.pypa.io). This was the second time we used the template in a live setting, and it was just as smooth as we hoped. Answer a few questions, and boom\! You’ve got a working, standards-compliant Python package. - -We love Hatch because it includes a task runner out of the box (see [the Scientific Python Development Guide](https://learn.scientific-python.org/development/) for why that matters), and it integrates seamlessly with [UV](https://astral.sh/uv). Hatch has supported UV under the hood for a while, and with a single config line in your pyproject.toml, you can enable UV’s speed in your workflow. (We’ve already [baked that into our template](https://github.com/pyOpenSci/pyos-package-template).) - -Shoutout to [Carol Willing](https://github.com/willingc), [Inessa Pawson](https://github.com/InessaPawson), [Jeremiah Paige](https://github.com/ucodery), and [Tetsuo Koyama](https://github.com/tkoyama010) for making this workshop such a success. - -We also attended the Pixi workshop, which showcased how [Pixi](https://pixi.sh) utilizes UV and supports mixed [PyPI](https://pypi.org/)/[Conda](https://docs.conda.io/projects/conda/en/latest/user-guide/getting-started.html) environments. The instructors walked us through spinning up cloud-based GPU compute using [NVIDIA Brev,](https://developer.nvidia.com/brev) which was quick, powerful, and surprisingly easy to configure. Pixi is a great option for you if you want the speed of UV but the Conda support! - -
- - - Small group discussion at pyOpenSci’s Packaging Pain Points BoF session at SciPy 2025, with participants seated in a circle sharing insights. - -
- -## BoF: Breaking Down Pain Points, Together - -Our “Packaging Pain Points” BoF (Birds of a Feather) session drew *over 60 participants* and an incredible buzz of energy! We split into small groups to talk about the social and technical challenges researchers face when sharing Python code and workflows. The conversation was honest, collaborative, and energizing. - -We’ll share a dedicated post soon about the insights we gathered, but one thing is already clear: when we come together to name what’s hard, we create the conditions for real, inclusive solutions. - -Huge thanks to [Leah Wasser](https://github.com/lwasser), [Jonny Saunders](https://github.com/sneakers-the-rat), [Avik Basu](https://github.com/ab93), [Jeremiah Paige](https://github.com/ucodery), [Inessa Pawson](https://github.com/InessaPawson), [Tetsuo Koyama](https://github.com/tkoyama010), and everyone who helped make the session run smoothly. - -## Sprinting Toward Sustainable Software - -Our final-day sprint was one of the most vibrant and heartening moments of the week. The pyOpenSci community showed up in full force—so much so that we had to push two sets of tables together to fit everyone. The room buzzed with activity as over 30 issues and pull requests were opened in just one day, many from first-time contributors. - -What made it especially powerful? Experienced maintainers paired up organically with newcomers, walking them through the setup process, answering questions, and celebrating small wins. It was a clear embodiment of the pyOpenSci ethos: mentorship, openness, and shared learning. - -To support folks new to open source, we shared our [Contribute to Open Source lessons](https://www.pyopensci.org/lessons/), which walk through how to make your first contribution in a supportive and beginner-friendly way. These lessons helped ease people into the sprinting process and gave them the confidence to get started. - -We’re grateful to everyone who joined, and especially to those who made their very first open source contributions with us. If you were there, you helped make something truly special. - -
- - - A woman Leah Wasser, smiling at the podium while preparing to present at a pyOpenSci workshop at SciPy 2025, with attendees gathered behind her. - -
- -## Talks, Lightning Talks, and the Hallway Track - -We’ll be honest, there were too many great talks at SciPy to cover them all. But we want to highlight a few of the more memorable lightning talks from our community this year: - -* [Jonny Saunders](https://github.com/sneakers-the-rat) gave an epic lightning talk that highlighted some of the challenges LLMs pose to the open-source and scientific communities. [This license,](https://github.com/sneakers-the-rat/gpu-free-ai/blob/main/LICENSE) for those who missed it, gives you a flavor of what happened during the talk\! -* [Yuvi Panda](https://github.com/yuvipanda) delivered a deeply relatable talk on therapy, open source, and why maybe—just maybe—you should choose the former before the latter. -* [Leah Wasser](https://github.com/lwasser) discussed our pyOpenSci mission to address social and technical pain points in scientific open source and reflected on the role of software peer review in supporting reproducible, high-quality research. -* [Tetsuo Koyama](https://github.com/tkoyama010) gave a lively demo of [PyVista](https://docs.pyvista.org), a powerful 3D visualization library built on VTK and used by over 2,000 open source projects. Designed as the 3D equivalent of [Matplotlib](https://matplotlib.org/), PyVista makes it easy to create beautiful, interactive 3D visualizations right from Python with no prior VTK knowledge needed. - -And of course, we couldn’t wrap up the week without a shoutout to the SciPy Song—a tradition that keeps getting better every year\! This year’s version, crafted by [Naty Clementi](https://github.com/ncclementi), [Juanita Gomez](https://github.com/juanis2112), and [Paige Martin](https://github.com/paigem), even featured a nod to pyOpenSci\! - -
- - - Add alt - -
- -If you missed the conference, talks will be available on YouTube in the coming months—but the *hallway track*, where collaborations begin and friendships form? That’s the part you really have to experience in person. We hope to see you there next year. - -## What’s Next? - -We’ll be following up on more posts soon, especially on: - -* The community feedback we gathered during the BoF -* Lessons learned from teaching with UV, Hatch, and Pixi -* Updates to our [packaging template](http://github.com/pyopensci/pyos-package-template) and documentation - -### We’re also thrilled to announce a new collaboration: - -pyOpenSci is partnering with the [Stanford Open Source Program Office (OSPO)](https://opensource.stanford.edu/) for our first joint event, happening August 7\! This interactive session will explore how software peer review helps researchers build better tools, improve their coding practices, and gain academic recognition. - -📅 Learn more: [pyOpenSci x Stanford OSPO Peer Review Event](https://www.pyopensci.org/events/pyopensci-stanford-ospo-peer-review.html) -{: .btn .btn--primary } - -We’ll also be co-hosting a packaging workshop at Stanford later this year, so stay tuned for more details on that! - -If you joined us during SciPy this year, thank you. Whether you were sprinting, chatting, teaching, or just soaking it all in, you helped make it special. - -If you missed it but want to get involved, check out our [volunteer page](https://www.pyopensci.org/volunteer.html). - -Open science moves forward when we build it together, and SciPy 2025 reminded us just how much we can do when we do. - ---- - -
- -## Connect with us! - -There are lots of ways to get involved if you are interested\! - -* If you read through our lessons and want to suggest changes, open an issue in our [lessons repository here](https://github.com/pyOpenSci/lessons). -* [Volunteer to be a reviewer for pyOpenSci’s software review process](https://docs.google.com/forms/u/6/d/e/1FAIpQLSeVf-L_1-jYeO84OvEE8UemEoCmIiD5ddP_aO8S90vb7srADQ/viewform?usp=send_form). -* [Submit a scientific Python package to pyOpenSci for peer review.](https://www.pyopensci.org/software-peer-review/how-to/author-guide.html#submit-your-package-for-peer-review) -* [Donate to pyOpenSci](https://give.communityin.org/pyopensci_2024?ref=ab_0sHhtifYvgR0sHhtifYvgR) to support scholarships for future training events and the development of new learning content. -* Check out our [volunteer page](https://www.pyopensci.org/volunteer.html) for other ways to get involved. - -You can also: - -* Keep an eye on our [events page](https://www.pyopensci.org/events.html) for upcoming training events. - -Follow us on social platforms: - -* [Mastodon](https://fosstodon.org/@pyopensci) -* [Bluesky](https://bsky.app/profile/pyopensci.org) -* [LinkedIn](https://www.linkedin.com/company/pyopensci/) -* [GitHub](https://github.com/pyOpenSci) - -If you are on LinkedIn, check out and [subscribe to our newsletter](https://www.linkedin.com/newsletters/7179551305344933888/?displayConfirmation=true), too. - -
diff --git a/_posts/2025-08-05-scipy-bof-lessons-learned.md b/_posts/2025-08-05-scipy-bof-lessons-learned.md deleted file mode 100644 index 5485ec33..00000000 --- a/_posts/2025-08-05-scipy-bof-lessons-learned.md +++ /dev/null @@ -1,237 +0,0 @@ ---- -layout: single -title: "Listening, learning, and building together: what we heard at our SciPy 2025 BoF" -blog_topic: community -excerpt: "We held an incredibly informative community session this year at the SciPy meeting in Tacoma Washington. We asked the community what their open source Python pain points were. Learn more about what we learned in this interactive session." -author: "Mandy Moore" -permalink: /blog/pyopensci-bof-community-scipy-2025.html -header: - overlay_image: images/headers/pyopensci-scipy-2025.png -categories: - - blog-post - - community -classes: wide -toc: true -comments: true -last_modified: 2025-08-05 ---- - - -
- - - Attendees from different scientific backgrounds actively participate in a BoF discussion, sharing experiences from across the research software ecosystem. - -
- -At [SciPy 2025](https://www.scipy2025.scipy.org/) in July, [pyOpenSci hosted a Birds of a Feather session](https://www.pyopensci.org/events/pyopensci-scipy25-bof-packaging-challenges.html) focused on packaging challenges in research software. - -Rather than giving another talk or demo, we created a space to listen. Building on themes from [our earlier blog about the social side of packaging](https://www.pyopensci.org/blog/python-packaging-friends-dont-let-friends-package-alone.html), we invited folks into a Birds of a Feather (BoF) session centered on one big question: - -**What are your biggest pain points when it comes to sharing and maintaining research software—and what should pyOpenSci focus on next?** - -
-💬 What’s a BoF? - -A “Birds of a Feather” (BoF) session is an informal gathering where people with shared interests come together to discuss a topic—no slides, no lectures, just real conversation. At SciPy, our BoF served as a chance to connect, surface ideas, and shape the future of open science together. -
- -The goal wasn’t to walk away with a perfect roadmap. It was to get honest input from people across the scientific open source ecosystem, like maintainers, researchers, tool builders, educators, and curious newcomers. We wanted to surface the real-world friction points that often get lost in documentation or conference talks. These aren’t just isolated issues—they’re patterns that affect how research software gets built, shared, cited, and sustained. - -And wow, did people show up! - -We kicked things off with a few warm-up questions, then broke into small groups for deeper discussion. Each group used [Mentimeter](https://www.mentimeter.com/) to log their thoughts in real time, so we could capture the full range of ideas. Some responses were serious. Others were playful (one response to “What’s hardest about packaging your code?” was simply: “cats” 😹). But across it all, real patterns emerged\! - -Here’s what we heard and how it’s shaping where we go from here. - ---- - -
- - - SciPy 2025 attendees sit in a discussion circle during the pyOpenSci BoF session, engaged in conversation about research software challenges. - -
- -## “Packaging is still painful”: core frustrations - -Despite the progress in tooling over the last few years, many attendees made one thing clear: **Python** **packaging is still a struggle**. - -Participants used phrases like “dependency hell,” “non-Python pain,” “configuration chaos,” and “too many choices” to describe their current reality. One common theme? There are *lots* of tools available, but little clarity on how to navigate them. - -People asked: - -* When should I use [Pixi](https://pixi.sh/) vs [pip](https://pypi.org/project/pip/) vs [uv](https://docs.astral.sh/uv/)? -* What’s the right way to support non-Python dependencies? -* Why does it still feel like setting up my environment is half the battle? - -Even seasoned developers shared that they still wrestle with [build systems](https://www.pyopensci.org/python-package-guide/package-structure-code/python-package-build-tools.html#build-front-end-vs-build-back-end-tools) and [publication pipelines](https://www.pyopensci.org/python-package-guide/tutorials/publish-pypi.html). And for scientists new to software development, the learning curve can feel nearly vertical. - ---- - - -
- - - Mentimeter board filled with participant feedback on how institutions could better support sustainable research software, including ideas like checklists and incentives. - -
- -## Docs are the front door—but it’s often locked - -[Documentation](https://www.pyopensci.org/python-package-guide/documentation/index.html) came up repeatedly, not just as a technical need but as a key accessibility issue. - -People weren’t just asking for more documentation. They were asking for: - -* Docs that actually work (e.g., install instructions that don’t break), -* Docs that explain why as well as how, -* And docs with effective examples, not just API references. - -We also heard about the challenge of writing good documentation when you’re strapped for time, unsure what your audience needs, or dealing with tools that change frequently. There were several calls for best practice guides—what *has* to be documented, how to make [gallery](https://www.pyopensci.org/python-package-guide/documentation/write-user-documentation/create-package-tutorials.html)\-style examples, and how to make sure docs stay up-to-date across versions. - ---- - -
- - - Mentimeter slide summarizing common barriers to making research code more open, including time constraints, lack of institutional support, and unclear best practices. - -
- -## Matchmaking, mentorship, and human help - -One of the clearest messages from the BoF was that technical infrastructure only goes so far. What people also need is human infrastructure. - -That includes: - -* Mentoring programs to help new contributors feel supported and seen -* Ways to match scientists with programmers (and vice versa), especially when each party has complementary knowledge but no bridge between them -* Contributor paths that don’t assume deep technical knowledge, but instead offer orientation and validation - -Several folks mentioned imposter syndrome, burnout, or the sense of shouting into the void as solo maintainers. It was a good reminder that building sustainable software also means building sustainable communities. - ---- - -
- - - Mentimeter slide displaying audience reactions to generative AI in research software, with mixed responses including excitement, skepticism, and caution. - -
- -## AI is here (whether we like it or not) - -Of course, we also asked about LLMs and generative AI and got a spectrum of reactions. - -Some folks are excited about the potential to scaffold code, summarize docs, or speed up tedious workflows. Others shared more cautionary takes, raising issues around: - -* Trust and reproducibility (especially when LLMs hallucinate) -* Licensing concerns (who owns what?) -* The risk that “AI-hype” could distract from foundational work and people - -In short, people are experimenting, but they’re also wary. There’s interest in using these tools responsibly—**not to replace developers, but to augment and support them**. - ---- - -## Academic culture still makes it hard - -One of the deeper undercurrents in the BoF conversation was how academic norms actively discourage openness and sustainability. This is especially true when it comes to research software, which often underpins entire publications but rarely receives credit, maintenance funding, or institutional support. - -We heard: - -* Researchers don’t get credit for making code reproducible or open. -* There’s little incentive to write good docs or maintain packages after publication. -* People are exhausted just trying to meet the minimum requirements for publication (never mind building for reuse). - -And yet, people had ideas: - -* Could pyOpenSci or a partner journal offer non-monetary awards for well-packaged, reproducible code? -* Could we provide press release templates to help institutions spotlight their open science champions? -* What if we offered more checklists or templates to help researchers make their code more open, with less guesswork? We’re already taking small steps in this direction through resources like our [packaging guide](https://www.pyopensci.org/python-package-guide/), the [pyos-package-template](https://github.com/pyOpenSci/pyos-package-template), and our [list of peer-reviewed packages](https://www.pyopensci.org/python-packages.html), which offers a bit of well-earned recognition for open research software. - -These aren’t technical problems. They’re cultural ones. And they’re just as important to address. - ---- - -## Ideas we loved - -This community doesn’t just surface problems. It shows up with solutions. Across the BoF discussions, people weren’t just venting—they were imagining what better could look like. - -Here are some of the ideas we’re excited to explore next, aligned with the major themes we heard: - -### Packaging & tooling - -* Promote beginner-friendly packaging tools—like our [Python packaging guide](https://www.pyopensci.org/python-package-guide/) and [packaging template](https://github.com/pyOpenSci/pyos-package-template), which we also use in our [lessons](https://www.pyopensci.org/lessons/). - -* Help researchers understand the evolving [Python packaging landscape](https://www.pyopensci.org/python-package-guide/package-structure-code/python-package-build-tools.html)—tools like uv, Pixi, pip, and Hatch—with comparison tables, use-case guides, or decision trees. - -* Offer clearer guidance on when to publish a new package vs. contribute to an existing one, and how to manage non-Python dependencies in cross-platform environments. - - ### Documentation & onboarding - -* Publish best practices for writing docs that actually work—including install instructions, example-rich gallery pages, and tips for keeping docs up to date. - -* Make contributing easier through better labeling of issues (e.g., “good first issue,” “review needed”) and improved contributor guides. - -* Create lightweight templates or checklists for maintainers who want to lower the barrier for contributions but aren’t sure where to start. - - ### Community & contributor support - -* Provide talking points for researchers who want to advocate for their contributions to research software in tenure reviews, grant applications, or institutional conversations. - -* Offer some form of visible recognition—like our [listed packages](https://www.pyopensci.org/software-contributing-guide/peer-reviewed-packages.html)—for maintainers who go above and beyond. - -* Pilot mentorship, pairing, or office-hour models to connect researchers with developers, especially in early-stage projects. - -### AI, academic culture & broader systems -* Build resources that help the community responsibly evaluate the use of LLMs and generative AI in open source—without overpromising or oversimplifying. -* Share reproducibility and openness checklists tailored to academic realities, helping researchers take incremental steps toward sustainable software. -* Explore non-monetary awards or spotlights that recognize peer-reviewed, well-documented, and reusable research software—even if it’s not “published” in the traditional sense. - ---- - -
- - - A small breakout group at SciPy 2025 BoF, with participants gathered around a table talking and taking notes on packaging and documentation pain points. - -
- -## So, what’s next? - -This BoF reminded us that the work pyOpenSci does matters. But it also reminded us that we can’t (and shouldn’t) do it alone. - -We are thrilled to be [working with Stanford University's Open Source Program office](https://www.pyopensci.org/events/pyopensci-stanford-ospo-peer-review.html) and are excited about a peer review event that we are hosting! - -We’ll also continue to develop our [packaging guide](https://www.pyopensci.org/python-package-guide/), [peer review process](https://www.pyopensci.org/software-peer-review/), and [community programs](https://www.pyopensci.org/contributing/) more beginner-friendly and human-centered. But we also want to invite your voices into that work. - -What do you want to see next? Where can we partner, collaborate, or amplify? - ---- - -
-## Connect with us! - -Whether you’re new to open science or have been building tools for decades, there’s a place for you here. - -* Check out our [packaging guide](https://www.pyopensci.org/python-package-guide/) for tutorials, tooling comparisons, and overviews of the Python packaging ecosystem. If you spot missing content, typos, or areas for improvement, [open an issue](https://www.pyopensci.org/python-package-guide/#report-issues-or-contribute) to help us make it better. -* [Volunteer to be a reviewer for pyOpenSci’s software review process](https://docs.google.com/forms/u/6/d/e/1FAIpQLSeVf-L_1-jYeO84OvEE8UemEoCmIiD5ddP_aO8S90vb7srADQ/viewform?usp=send_form). -* [Submit a research software package to pyOpenSci for peer review.](https://www.pyopensci.org/software-peer-review/how-to/author-guide.html#submit-your-package-for-peer-review) -* [Donate to pyOpenSci](https://give.communityin.org/pyopensci_2024?ref=ab_0sHhtifYvgR0sHhtifYvgR) to support scholarships for future training events and the development of new learning content. -* Check out our [volunteer page](https://www.pyopensci.org/volunteer.html) for other ways to get involved. - -You can also: - -* Keep an eye on our [events page](https://www.pyopensci.org/events.html) for upcoming training events. - -Follow us on social platforms: - -* [Mastodon](https://fosstodon.org/@pyopensci) -* [Bluesky](https://bsky.app/profile/pyopensci.org) -* [LinkedIn](https://www.linkedin.com/company/pyopensci/) -* [GitHub](https://github.com/pyOpenSci) - -If you are on LinkedIn, check out and [subscribe to our newsletter](https://www.linkedin.com/newsletters/7179551305344933888/?displayConfirmation=true), too. - -Let’s keep building a better, more inclusive, and more sustainable open science ecosystem, together. -
diff --git a/_posts/2025-08-13-meet-mandy.md b/_posts/2025-08-13-meet-mandy.md deleted file mode 100644 index 5167b6b0..00000000 --- a/_posts/2025-08-13-meet-mandy.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -layout: single -title: "Meet Mandy Moore, pyOpenSci’s new Communications and Community Lead!" -blog_topic: community -excerpt: "Meet Mandy Moore, our new Communications and Community Lead! Mandy is bringing 15+ years of experience in marketing, content strategy, and community engagement to help make open science more accessible and welcoming. Learn more about her work and story in this post." -author: "Mandy Moore" -permalink: /blog/mandy-moore-communications-lead.html -header: - overlay_image: images/headers/pyopensci-floral.png -categories: - - blog-post - - community -classes: wide -toc: true -comments: true -last_modified: 2025-08-12 ---- - -
- - - Photo of Mandy Moore wearing a wide-brimmed black hat, sitting indoors with colorful lights in the background. Beside her is the pyOpenSci logo, featuring a stylized flower with circuit-like petals and a snake in the center, on a purple patterned background. - -
- -## Meet Mandy Moore, pyOpenSci’s new Communications and Community Lead! - -We’re thrilled to introduce **Mandy Moore** as our new **Communications and Community Lead** at [pyOpenSci](https://www.pyopensci.org/)\! - -Mandy joins us in a part-time role focused on growing and supporting our community through clear, engaging, and inclusive communication. She’s already helping us shape how we show up across platforms, whether that’s through newsletters, social posts, blogs, or behind-the-scenes strategy. Her work is helping us highlight not just what we do at pyOpenSci, but who we’re doing it for. - -Mandy brings more than 15 years of experience in marketing, content strategy, podcast production, and community engagement—primarily in the broader tech world. While she’s newer to both Python and open science, she’s no stranger to translating technical work into accessible, human-centered content. Over the years, she’s worked behind the scenes on developer-focused podcasts, led marketing for startups, and helped open source projects share their impact and grow inclusive, welcoming communities. - -At pyOpenSci, Mandy is leading our blog, social media, newsletter, and contributor communications. If you’ve noticed a friendlier voice on LinkedIn or seen our blog posts getting more structured and community-centered, that’s Mandy at work. - -In addition to her work with pyOpenSci, Mandy runs a freelance business supporting mission-driven startups, open source projects, and solo founders. Her services span everything from content strategy and social media to podcast production and branding. She’s worked with projects like [Leapter](https://www.leapter.com/), [WPConcierge](https://wpconcierge.com/), and the [Dead Code Podcast](https://deadcode.website), and she’s always up for a good brainstorm about making tech more human and accessible. - -Her role supports one of the most essential parts of pyOpenSci’s mission: **making scientific open source more accessible, more visible, and more sustainable**. Through thoughtful content and community engagement, Mandy is helping us break down both technical and social pain points, one story, post, or conversation at a time. - -> “I’m not a scientist or a developer,” Mandy says. “But I *am* someone who believes deeply in open science, shared knowledge, and the power of good communication to bring people together. I’m honored to support this community and can’t wait to learn from all of you.” - -Mandy lives in **York, Pennsylvania,** with her partner **Keith**, their three kids, and two very good dogs: **Gallagher**, a gentle golden retriever, and **Luna**, a tiny but mighty miniature Yorkshire Terrier. Outside of work, she’s a lifelong writer, recovering perfectionist, and astrology nerd who still gets excited about the magic of a well-written sentence. She’s currently in the middle of planning her November wedding, which means her calendar is full of both GitHub issues *and* flower mockups. - -Please join us in giving Mandy a warm welcome! You can connect on LinkedIn to say hi. You can also find her writing at exhotmess.net and learn more about her tech work at mandymoore.tech. diff --git a/_posts/2025-08-14-pyopensci-executive-council-rototation.md b/_posts/2025-08-14-pyopensci-executive-council-rototation.md deleted file mode 100644 index 2672654d..00000000 --- a/_posts/2025-08-14-pyopensci-executive-council-rototation.md +++ /dev/null @@ -1,96 +0,0 @@ ---- -layout: single -title: "Succession is Success: pyOpenSci's Executive Council Transition Marks Organizational Maturity" -blog_topic: community -excerpt: "The Executive Council drives pyOpenSci's mission and vision. Learn more about how the council works, and about the current rotation that is happening as our board chair steps down and another member joins us marking a milestone of incredible growth for pyOpenSci." -author: "pyopensci" -permalink: /blog/pyopensci-executive-council-transition.html -header: - overlay_image: images/headers/pyopensci-floral.png -categories: - - blog-post - - community -classes: wide -toc: true -comments: true -last_modified: 2025-08-05 ---- - -When an organization successfully navigates its first leadership transition, it's a milestone worth celebrating. It demonstrates that the structures, processes, and culture you've built are strong enough to endure beyond any single individual. We've seen these transitions happening in our [Editorial Board](https://www.pyopensci.org/handbook/governance/structure.html#peer-review-editorial-board) over the past 3 years. Today, we're proud to announce pyOpenSci's first [Executive Council](https://www.pyopensci.org/handbook/governance/executive-council.html#pyopensci-executive-council) transition—a sign of our organization's growth and maturity. - -
- - - A graphic that says --Tracy Teal has completed a 3 year term as Executive Council Chair. We are grateful for the expertise, time and effort Tracy has put into the strategic vision for pyOpenSci.We also are grateful to Karen Cranston for stepping into the Executive Council chair position. And we welcome April Johnson as our newest Executive Council member!A timeline showing the growth of pyOpenSci from 2019 to 2024. It also has images of 3 women, Tracy, Karen and April on it on the right. - -
- -As founding Executive Council Chair **Tracy Teal** transitions from her leadership role after three transformative years, we're excited to welcome **Karen Cranston** as our new Executive Council Chair and **April Johnson** as a new Executive Council member. This transition represents not just changing leadership, but the successful maturation of pyOpenSci as an organization. - -## From grassroots vision to established organization - -pyOpenSci began in 2018 as a passion project born from engagement in the Python, open source community. As a volunteer who deeply cared about scientific open source software, I saw numerous pain points in how the Python scientific community approached software quality and sustainability. We brought together a small group of [like-minded open source leaders](https://www.pyopensci.org/handbook/reference/index.html) to pilot what [peer review of scientific Python software](https://www.pyopensci.org/about-peer-review/index.html) could look like—creating something that didn't exist but was clearly needed. - -Five years later, our grassroots effort had evolved into something much larger. In 2022, with Sloan Foundation funding secured, we transitioned to fiscal sponsorship under the 501(c)(3) fiscal host, Community Initiatives, establishing pyOpenSci as an independent, fiscally-sponsored project. This transition marked our transformation from a purely volunteer-driven initiative to a structured organization with [formal governance](https://www.pyopensci.org/handbook/governance/structure.html), funding, and accountability structures. - -## Building strong governance: The Executive Council's role - -The [pyOpenSci Executive Council](https://www.pyopensci.org/handbook/governance/executive-council.html) was founded in 2022 as the cornerstone of our governance structure. The Council provides strategic oversight, organizational guidance, and accountability while serving as ambassadors for our mission and as a support system for our [Executive Director](https://www.pyopensci.org/handbook/governance/structure.html#executive-director). Working alongside our [Advisory Council](https://www.pyopensci.org/handbook/governance/structure.html#advisory-council), which offers domain-specific expertise, this governance structure has been instrumental in our steady growth. - -## Three years of shared accomplishments - -Under Tracy's leadership as our founding Executive Council (EC) Chair, the EC has achieved remarkable milestones over the past three years. Together, we: - -* **Established our foundational identity:** Crafted a [powerful mission](https://www.pyopensci.org/handbook/governance/mission-values.html#mission-vision-and-values) while directing the vision and values of our organization that guide our work together. -* **Built robust governance structures:** Created transparent decision-making processes and accountability mechanisms. -* **Fostered a healthier and inclusive community:** Implemented a comprehensive [code of conduct](https://www.pyopensci.org/handbook/CODE_OF_CONDUCT.html) with clear procedures that support inclusive community engagement. -* **Secured sustainable funding:** We have also secured [over $1,000,000 in funding](https://www.pyopensci.org/blog/czi-funds-pyOpenSci-2024.html) while building relationships for future support. - -These achievements reflect the successful synergy between Executive Council members who brought diverse expertise in nonprofit management, open source communities, and scientific software development to guide our strategic direction. - -## Celebrating transition: a sign of success - -Three years into our journey as an independent organization, this Executive Council transition marks a milestone we're celebrating as a symbol of organizational maturity and success. - -Tracy's non profit leadership experience, strategic vision and steady guidance have been foundational to everything we've accomplished—from establishing our governance structures to guiding us through the critical early years of organizational development. As she transitions from her EC Chair role, we're deeply grateful for the strong foundation she's helped build. - -**Karen Cranston** will continue to serve on the pyOpenSci leadership team, moving into the role of EC Chair. Karen brings extensive experience in open science, having served in leadership roles at organizations such as [The Carpentries](https://carpentries.org/) and the [Open Bioinformatics Foundation](https://www.open-bio.org/) that bridge community, research and data science. Karen’s deep understanding of both the scientific community and organizational development makes her ideally suited to guide pyOpenSci through our next phase of growth. - -We're also thrilled to welcome **April Johnson** to our Executive Council. April brings invaluable expertise as a technology executive and consultant to humanities and civil rights nonprofits, most recently as [People Lead at 2i2c](https://2i2c.org/) and as Board Chair for [He She Ze & We](https://heshezewe.org/). Her experience in organizational development and people-centered leadership will be crucial as we continue to scale our community, work on sustainability, and impact. - -## What this maturity enables - -This successful transition demonstrates that pyOpenSci has evolved into a growing, sustainable organization. Succession is success as it demonstrates that we've built something larger than any individual contributor—with proven governance structures, institutional knowledge, and a thriving community that transcends leadership changes. - -As we move forward under Karen and April's guidance, we're excited about our next chapter. This includes a strategic focus on sustainability—thoughtfully considering how we generate revenue as an organization, and ways we can scale that also support long-term sustainability. We're also exploring deeper collaborations with aligned communities like [rOpenSci](https://ropensci.org/), [PREreview](https://prereview.org/), [The Carpentries](https://carpentries.org/), and [Open Life Science](https://openlifesci.org/). - -This transition positions us to take on bigger challenges, expand our impact on the scientific Python ecosystem, and continue building the future of open scientific software. We can't wait to see what comes next! - - -
- -## Connect with us! - -There are lots of ways to get involved if you are interested\! - -* If you read through our lessons and want to suggest changes, open an issue in our [lessons repository here](https://github.com/pyOpenSci/lessons). -* [Volunteer to be a reviewer for pyOpenSci’s software review process](https://docs.google.com/forms/u/6/d/e/1FAIpQLSeVf-L_1-jYeO84OvEE8UemEoCmIiD5ddP_aO8S90vb7srADQ/viewform?usp=send_form). -* [Submit a scientific Python package to pyOpenSci for peer review.](https://www.pyopensci.org/software-peer-review/how-to/author-guide.html#submit-your-package-for-peer-review) -* [Donate to pyOpenSci](https://give.communityin.org/pyopensci_2024?ref=ab_0sHhtifYvgR0sHhtifYvgR) to support scholarships for future training events and the development of new learning content. -* Check out our [volunteer page](https://www.pyopensci.org/volunteer.html) for other ways to get involved. - -You can also: - -* Keep an eye on our [events page](https://www.pyopensci.org/events.html) for upcoming training events. - -Follow us on social platforms: - - -* [Mastodon](https://fosstodon.org/@pyopensci) -* [Bluesky](https://bsky.app/profile/pyopensci.org) -* [LinkedIn](https://www.linkedin.com/company/pyopensci/) -* [GitHub](https://github.com/pyOpenSci) - -If you are on LinkedIn, check out and [subscribe to our newsletter](https://www.linkedin.com/newsletters/7179551305344933888/?displayConfirmation=true), too. - -
diff --git a/_posts/2025-09-11-pyopensci-september-news-2025.md b/_posts/2025-09-11-pyopensci-september-news-2025.md deleted file mode 100644 index 482b1756..00000000 --- a/_posts/2025-09-11-pyopensci-september-news-2025.md +++ /dev/null @@ -1,161 +0,0 @@ ---- -layout: single -title: "What’s New in pyOpenSci: September Updates + Community Shoutouts" -blog_topic: updates -excerpt: "This summer brought incredible momentum to pyOpenSci—from 60+ attendees at our SciPy session to 13 packages actively under review and exciting new leadership transitions. Join our November Python packaging workshop or explore how you can get involved in peer review." -author: "pyopensci" -permalink: /blog/pyopensci-september-2025-updates.html -header: - overlay_image: images/headers/pyopensci-floral.png -categories: - - blog-post - - community -classes: wide -toc: true -comments: true -last_modified: 2025-09-11 ---- - -The summer was full of connection, collaboration, and momentum in the pyOpenSci community. Whether you joined us at SciPy, submitted a package for review, or just lurked, we’re so grateful you’re here! - -Here’s what’s been happening 👇 - ---- - -## **💬 Community Shoutouts** - -**Welcome, Philip Narteh!** - -We’re thrilled to welcome [**Philip Narteh**](https://github.com/Phinart98) as pyOpenSci’s very first **Open Source Infrastructure Intern**, supported by [Quansight](https://quansight.com/), [Melissa Mendonça](https://github.com/melissawm), and [Tania Allard](https://github.com/trallard). Philip will be helping us migrate our website to [Django](https://www.djangoproject.com/), bringing valuable experience in accessibility within the Django ecosystem. We’re so excited to have you here, Philip! - -**Contributor milestones** - -We’ve also had some meaningful leadership transitions happen. - -* [**Tracy Teal**](https://github.com/tracykteal) completed her term as our founding Executive Council Chair after three transformative years. The Executive Council is the leadership body that guides pyOpenSci’s strategy, governance, and long-term sustainability. You can think of it as the group that helps us make big-picture decisions and ensures our work stays aligned with our mission. [Learn more about the Executive Council.](https://www.pyopensci.org/handbook/governance/executive-council.html) -* [**Karen Cranston**](https://github.com/kcranston) has stepped in as our new Executive Council Chair, and **April Johnson** has joined as a new Council member. - -These transitions highlight pyOpenSci’s growing maturity and sustainability as an organization. - -We also want to celebrate our editorial team: - -* [**Eliot Robson**](https://github.com/eliotwrobson) transitioned into the Editor-in-Chief role for our peer review process. -* [**Chiara Marmo**](https://github.com/cmarmo) and [**Isabel Zimmerman**](https://github.com/isabelizimm) wrapped up their terms as Editors after years of thoughtful reviews and community care. Thank you both! - ---- - -## **🎤 SciPy 2025 Highlights** - -Over 60 people joined our Birds of a Feather (BoF) session to discuss challenges in research software and what community-driven peer review can do to help. It was one of our most energizing community sessions yet! - -We also: - -* Kicked off the conference with our packaging workshop, introducing folks to our user-friendly template and modern tools like Hatch and UV. -* Welcomed many first-time contributors at our community sprint, with **over 30 PRs opened in one day**! -* Shared lightning talks, saw Pixi in action, and even got a **SciPy Song shoutout**. - -Big thanks to everyone who contributed to our SciPy presence—especially Jonny Saunders, Avik Basu, Jeremiah Paige, Inessa Pawson, Carol Willing, Tetsuo Koyama, and many others who made our workshop and sprints welcoming and impactful. - -📖Read: [Highlights from SciPy 2025: Building Community, Code, and Culture](https://www.pyopensci.org/blog/pyopensci-at-scipy-2025.html) - ---- - -## **🔍 Peer Review Updates** - -We’re excited to see continued momentum in our software review process! Right now, **13 packages are actively under review,** and another **10 are in prereview** (the early stage before formal review begins). You can explore the [live review status board](https://www.pyopensci.org/metrics/peer-review/current-review-status.html) anytime to follow along. - -🎉 Recently accepted packages include: - -* [**PIVA**](https://github.com/pudeIko/piva) (August 4, 2025) — A visualization and analysis toolkit for experimental data from [Angle-Resolved Photoemission Spectroscopy (ARPES)](https://arpes.stanford.edu/research/tool-development/angle-resolved-photoemission-spectroscopy). -* [**CyNetDiff**](https://github.com/eliotwrobson/CyNetDiff) (August 22, 2025) — A performance-focused library for simulating network diffusion processes using [Cython](https://cython.org/). - -A big congratulations to the maintainers of these projects, and a thank you to the reviewers and editors who supported them through the process. - -📦 Interested in submitting your own Python package? - -👉 [Read our guide and submit here](https://www.pyopensci.org/python-package-guide/) - ---- - -## **🧠 Community Learning + Behind-the-Scenes** - -**Event Recap: [Improving Research Software through Peer Review](https://www.pyopensci.org/events/pyopensci-stanford-ospo-peer-review.html)** - -On August 7, we partnered with Stanford’s OSPO for a virtual session exploring how pyOpenSci’s peer review model strengthens research software and supports contributor recognition. - -**New blog updates** - -If our blog posts feel a little more structured, community-centered, and friendly lately—say hi to our new **Communications and Community Lead, Mandy Moore!** She’s been working behind the scenes on everything from SEO-friendly image tags to blog polish to social media storytelling. - -Bonus: She even learned a little HTML for it. 😉 - -📖Read: [Meet Mandy Moore, pyOpenSci’s new Communications and Community Lead!](https://www.pyopensci.org/blog/mandy-moore-communications-lead.html) - ---- - -## 📅 Upcoming Events - -**October 2, 2025 – [Create a Python Package: A Hands-On Workshop](https://www.pyopensci.org/events/pyopensci-stanford-create-python-package-workshop.html)** - -This workshop is being held for the Stanford Open Source Program office community through our new membership model. We are excited to host another live online workshop on Spatial Chat, where we’ll walk you through building your first Python package using Hatch, UV, and other modern tools—complete with a ready-to-use GitHub Codespace. (9 AM PT / 10 AM MT) - -**November 6, 2025 – [Create a Python Package: A Hands-On Workshop](https://www.pyopensci.org/events/python-packaging-workshop-november-2025.html)** - -It's not too late to grab a seat in our second Python packaging workshop. Python packaging at your fingertips! - -Here, we’ll walk you through building your first Python package using Hatch, UV, and other modern tools—complete with a ready-to-use GitHub Codespace using a custom pyOpenSci template. (9 AM PT / 10 AM MT) - -✨ More workshops and events are always in the works—stay tuned via our [events page](https://www.pyopensci.org/events.html) for the latest updates. - ---- - -## **🛠️ How You Can Get Involved** - -* Volunteer with pyOpenSci – from peer review to outreach, we welcome contributors of all kinds. -* Follow us – [LinkedIn](https://www.linkedin.com/company/pyopensci/), [Mastodon](https://fosstodon.org/@pyOpenSci), [Bluesky](https://bsky.app/profile/pyopensci.org). - ---- - -## **👀 On Our Radar** - -* We’re working on new policies around the role of generative AI in peer review and open source. It’s a challenging and timely topic, and we’ve opened a [GitHub discussion](https://github.com/pyOpenSci/software-peer-review/issues/331) to gather thoughts from the community. -* We’re also developing policies to support review of packages already reviewed by our partner journal, JOSS. -* Curious about next-gen Python packaging? Check out [uv](https://github.com/astral-sh/uv), [Pixi](https://pixi.sh/), and [Hatch](https://hatch.pypa.io/latest/). -* Keep an eye out for more blogs and contributor spotlights—we’ll be highlighting both technical insights and the people who make this community thrive. - ---- - -✨ Let’s keep building together. - -Until next time, - -The pyOpenSci Team 💛 - - -
- -## Connect with us! - -There are lots of ways to get involved if you are interested! - -* If you read through our lessons and want to suggest changes, open an issue in our [lessons repository here](https://github.com/pyOpenSci/lessons). -* [Volunteer to be a reviewer for pyOpenSci’s software review process](https://docs.google.com/forms/u/6/d/e/1FAIpQLSeVf-L_1-jYeO84OvEE8UemEoCmIiD5ddP_aO8S90vb7srADQ/viewform?usp=send_form). -* [Submit a scientific Python package to pyOpenSci for peer review.](https://www.pyopensci.org/software-peer-review/how-to/author-guide.html#submit-your-package-for-peer-review) -* [Donate to pyOpenSci](https://give.communityin.org/pyopensci_2024?ref=ab_0sHhtifYvgR0sHhtifYvgR) to support scholarships for future training events and the development of new learning content. -* Check out our [volunteer page](https://www.pyopensci.org/volunteer.html) for other ways to get involved. - -You can also: - -* Keep an eye on our [events page](https://www.pyopensci.org/events.html) for upcoming training events. - -Follow us on social platforms: - - -* [Mastodon](https://fosstodon.org/@pyopensci) -* [Bluesky](https://bsky.app/profile/pyopensci.org) -* [LinkedIn](https://www.linkedin.com/company/pyopensci/) -* [GitHub](https://github.com/pyOpenSci) - -If you are on LinkedIn, check out and [subscribe to our newsletter](https://www.linkedin.com/newsletters/7179551305344933888/?displayConfirmation=true), too. - -
diff --git a/_posts/2025-09-14-pyopensci-surviving-to-thriving.md b/_posts/2025-09-14-pyopensci-surviving-to-thriving.md deleted file mode 100644 index d71b6f1b..00000000 --- a/_posts/2025-09-14-pyopensci-surviving-to-thriving.md +++ /dev/null @@ -1,96 +0,0 @@ ---- -layout: single -title: "From Surviving to Thriving: A Convening to Reclaim and Sustain Open Science Communities" -blog_topic: community -excerpt: "Building resilience in open science starts with collaboration. Learn how pyOpenSci is partnering with the Carpentries, Prereview, Open Life Sciences, and rOpenSci to reinvision how open communities can work together to share the future of open science in uncertain times." -author: "pyopensci" -permalink: /blog/pyopensci-surviving-to-thriving.html -header: - overlay_image: images/headers/pyopensci-floral.png -categories: - - blog-post - - community -classes: wide -toc: true -comments: true -last_modified: 2025-09-09 ---- - -*Authors: Kari L. Jordan, Erin Becker, Daniela Saderi, Vanessa Fairhurst, Patricia Herterich, Noam Ross, Yanina Bellini Saibene, Leah Wasser, Yo Yehudi* - -
- - - A photograph of a waterfall cascading down rocks in a rich green forest setting - -
"Black Forest: waterfall" by *rboed* is licensed under CC BY 2.0. To view a copy of this license, visit https://creativecommons.org/licenses/by/2.0/?ref=openverse.
-
- - - -Open science has transformed how research is conducted, shared, and reused. Yet the organisations at the heart of this transformation are often left vulnerable, underfunded, and disconnected from one another. To move from simply surviving to truly thriving, five leading open science organisations \-- [The Carpentries](https://carpentries.org/), [OLS](https://we-are-ols.org/), [rOpenSci](https://ropensci.org/), [pyOpenSci](https://www.pyopensci.org/), and [PREreview](https://prereview.org/)) \-- are convening to chart a collective path forward. We are so grateful to The Navigation Fund for supporting this work, and invite our communities to review the [full proposal](https://commons.datacite.org/doi.org/10.71707/qttn-3j47) online. - -## Why This Convening Matters - -Our work has reshaped scientific culture by modeling inclusive, participatory, and transparent practices. But despite the global reach and impact of our communities, we face shared challenges: - -* Chronic underfunding that undermines our ability to plan and grow. -* Fragmentation across networks, which weakens collective visibility and coordination. -* Unsustainable engagement models that over-rely on volunteers without adequate support systems. - -Like a single thread, each of our organisations is fragile alone; together, we can weave a stronger, more resilient open science fabric. - -## Current Gaps - -Open science is built on collaboration and transparency, but too often the organisations building this infrastructure operate in silos. The result is uneven visibility, duplicated efforts, and missed opportunities to align strategies. Community-driven organisations like ours face distinct challenges compared with technical infrastructure projects: recruiting contributors is not enough if the structures and motivation to sustain their engagement are missing. - -This fragmentation is not just an internal problem; it’s a loss for the broader research ecosystem that depends on our work. - -## Our Approach - -We are planning a strategic, in-person convening of leadership from our five organisations. This gathering will focus on: - -* Shared financial sustainability: Developing collective, value-aligned models for funding and resource generation. -* Collaborative engagement strategies: Designing systems that strengthen and connect our communities across roles, regions, and lived experiences. -* Equity-centered design: Ensuring accessibility, usability, and meaningful participation for all involved. - -Facilitated by [Wildly Open](https://wildlyopen.com/), we are excited to explore and better understand what is (and isn’t) working, and then build durable models for collaboration and sustainability. - -## What We Aim to Deliver - -* A coordinated sustainability strategy that reflects the shared values of our organisations. -* A funder-facing pitch to mobilise resources in support of this strategy. -* Stronger ties among our organisations that extend beyond the convening, creating lasting impact. - -## The Bigger Picture - -This convening is not an isolated event. It’s the first step in scaling a movement that reimagines how values-driven open science organisations can grow together, share resources, and strengthen the ecosystem we collectively serve. By working together, we can ensure that the future of open science is not only innovative, but also equitable, sustainable, and resilient. - - -
- -## Connect with us! - -There are lots of ways to get involved if you are interested! - -* If you read through our lessons and want to suggest changes, open an issue in our [lessons repository here](https://github.com/pyOpenSci/lessons). -* [Volunteer to be a reviewer for pyOpenSci’s software review process](https://docs.google.com/forms/u/6/d/e/1FAIpQLSeVf-L_1-jYeO84OvEE8UemEoCmIiD5ddP_aO8S90vb7srADQ/viewform?usp=send_form). -* [Submit a scientific Python package to pyOpenSci for peer review.](https://www.pyopensci.org/software-peer-review/how-to/author-guide.html#submit-your-package-for-peer-review) -* [Donate to pyOpenSci](https://give.communityin.org/pyopensci_2024?ref=ab_0sHhtifYvgR0sHhtifYvgR) to support scholarships for future training events and the development of new learning content. -* Check out our [volunteer page](https://www.pyopensci.org/volunteer.html) for other ways to get involved. - -You can also: - -* Keep an eye on our [events page](https://www.pyopensci.org/events.html) for upcoming training events. - -Follow us on social platforms: - - -* [Mastodon](https://fosstodon.org/@pyopensci) -* [Bluesky](https://bsky.app/profile/pyopensci.org) -* [LinkedIn](https://www.linkedin.com/company/pyopensci/) -* [GitHub](https://github.com/pyOpenSci) - -If you are on LinkedIn, check out and [subscribe to our newsletter](https://www.linkedin.com/newsletters/7179551305344933888/?displayConfirmation=true), too. - -
diff --git a/_posts/2025-10-02-why-we-choose-what-we-choose.md b/_posts/2025-10-02-why-we-choose-what-we-choose.md deleted file mode 100644 index 05200808..00000000 --- a/_posts/2025-10-02-why-we-choose-what-we-choose.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -layout: single -title: "Why We Choose What We Choose" -blog_topic: community -excerpt: "In selecting one workflow to package a Python project, pyOpenSci chooses between a lot of code tools. These choices often come after months of exploration and debate. Find out what motivates us to make the decisions that we do." -author: "Jeremiah Paige" -permalink: /blog/how-we-choose-python-tools.html -header: - overlay_image: images/headers/pyopensci-floral.png -categories: - - blog-post - - community -classes: wide -toc: true -comments: true -last_modified: 2025-10-02 ---- - -A primary focus at pyOpenSci, one of our petals of support, is selecting packaging tools that work well for our users and work well together. We use our curated selection of tooling in our packaging guide, in our online tutorials, and in our trainings. We don’t require the use of any of our selected tools to submit a package to our peer review program but we will suggest them if the package authors ask for help trying to clean up or adopt new workflows. - -The members of pyOpenSci spend a great deal of time selecting these tools, debating tradeoffs, test-driving them in new situations, looking at new community trends, and listening to feedback from events we run. But up until now we haven't typically posted in the open either how or why we have come to the decisions we did. This post outlines, in not very strict terms, the rubric we use when selecting a project that we want to recommend. We focus on a beginner-leaning happy path for packaging workflows. In other words, what works really well for new projects that don't have a lot, if any, non-conventional requirements to share their code. - -Our rubric comes from five categories, approximately ordered as follows: - -## Tools that are free and open - -We love open software! It's even in our name. We are always looking to nurture and support open source software, even beyond packaging projects. It should come as no surprise, then, that we only choose tools in our packaging guide that are themselves open source. - -We don’t just appreciate open source, though, we also look for projects that are open contribution – that do most of their maintenance, stewardship, and designing in public. This means there is a public bug tracker where new issues are accepted from anyone, and fixes for those bugs are also accepted from non-maintainers. It may also mean that new feature ideas are accepted from the community, or even given a period of public comment. Contributions from everyone, including members and non-members, should be respected as the exchange of free ideas. Project authors should not seek to take exclusive ownership of the collaborative efforts of the community they are a part of. This means that contributors of any level and for any purpose of the project should be recognized publicly. It also means that the project is not asking for the exclusive rights to those contributors' ideas (through overly restrictive Contributor License Agreements). - -Our commitment to open software goes beyond just projects that choose to host their code and bugs in a public manner. We also value Free Software; both as in Beer and as in Freedom. Permissive open source software empowers its users to take control of their tools and fix, extend, secure, and adapt code for the purposes that will best fit their own needs. Choosing projects that do not require a financial exchange in order to be used ensures that we can recommend our choices to anyone no matter their situation or location. - -## Tools that are inclusive - -Inclusivity is very important to us; it is a critical component of tooling projects we select. Programming, including packaging of that software, is a skill that should be available to everyone. - -The tools we advocate for should not seek to limit their use through actions such as: adoption of a restrictive license; poor, missing, obfuscated, or expert-only documentation; charging for use of the tool or any process related to its successful usage; not supporting mainstream operating systems. The projects behind the tools should welcome constructive bug reports from users of all levels, and ideally also welcome contributions from all their users. - -There are some signals that we look for to tell if the project is inclusive. We want all of our recommended tools to have a code of conduct for their project. The project should also have a contributors guide that is easy to find. Labeling issues or running sprints aimed at fostering commits from new contributors is also a great indicator. - -Projects that manage to attract and maintain a broad base of contributors will be viewed more positively. We value projects that strive to do this not only in code, but also in documentation, engagement with the bug tracker, external write-ups and tutorials, and so forth. - -## Tools that implement open standards - -It is very important to us that the tools and processes we stand behind support the full set of community standards. - -For Python, this typically means conforming to [Python Enhancement Proposals](https://peps.python.org), but may also involve other standard bodies such as [Scientific Python Ecosystem Coordination](https://scientific-python.org/specs/). - -Supporting community standards demonstrates that the project respects the community it is working within and is serious about interoperability with other tools and processes. When done right, these tools empower their users to move this workflow, or any of its inputs or outputs, to another standards-compliant tool or process with little to no friction. It also makes it easier to teach since many of the concepts, as well as in some cases entire parts of project data, are tool-agnostic and can feel "familiar" even to those that have never used the tool before. There is also likely to be more documentation developed through forums, blogs, workshops, and other online platforms, because they apply to more than one tool. - -It can be a lot of work for tool maintainers to keep up-to-date with changes in standardization, especially in a large and eclectic community such as Python. While we understand that it can take time to implement new features imposed from outside of a project, we also know that a selectively-implemented standard is often worse than no standard. - -## Tools that are well supported - -We would like to only recommend projects that we can confidently say are healthy, correct, and here to stay. A well-maintained project is a somewhat subjective metric that is hard to pin down, but whenever possible we would apply our [same standard](https://www.pyopensci.org/software-peer-review/how-to/author-guide.html#does-your-package-meet-packaging-requirements) for Peer Reviews of Scientific Software. - -Authors and maintainers should respond to open issues and continue to make fixes to the project. We do not have any expectation or metric of response time, open bug count, time to close commit requests, security artifacts, or any other level of effort requirement; only that the project is alive and healthy to the degree that is appropriate for its function. We also strongly prefer projects that have a team of core maintainers as opposed to an individual maintainer. - -## Tools that reduce user choices - -Python packaging suffers, perhaps infamously, from [Too Many Options](https://www.pyopensci.org/blog/python-packaging-friends-dont-let-friends-package-alone.html#just-say-no-to-tmo). We would like to make as many pragmatic choices as we can on behalf of the learner. Better yet is to make choices that will eliminate further choices and complexity later on in the process; this can help learners from getting stuck in runaway analysis paralysis. - -This means that these tools should implement sensible defaults for any configurable value. Like with Python, they should make the simple easy and the difficult possible. - -It also means that we will generally select one tool when two or more could do the same job. So long as the one tool fits our other criteria it doesn't have to "win" at every single task it is capable of doing when compared against a plethora of other tools. diff --git a/_posts/2025-11-18-generative-ai-peer-review.md b/_posts/2025-11-18-generative-ai-peer-review.md deleted file mode 100644 index 9b596b3c..00000000 --- a/_posts/2025-11-18-generative-ai-peer-review.md +++ /dev/null @@ -1,288 +0,0 @@ ---- -layout: single -title: "Navigating LLMs in Open Source: pyOpenSci's New Peer Review Policy" -blog_topic: software -excerpt: "Generative AI products are reducing the effort and skill necessary to generate large amounts of code. In some cases, this strains volunteer peer review programs like ours. Learn about pyOpenSci's approach to developing a Generative AI policy for our software peer review program." -author: "pyopensci" -permalink: /blog/generative-ai-peer-review-policy.html -header: - overlay_image: images/headers/pyopensci-floral.png -categories: - - blog-post - - community -classes: wide -toc: true -comments: true -last_modified: 2025-12-16 ---- - -authors: Leah Wasser, Jed Brown, Carter Rhea, Ellie Abrahams, Carol Willing, Stefan van der Walt, Eliot Robson - -## Generative AI meets scientific open source - -Some developers believe that using Generative AI products increases -efficiency. However, in scientific open source, speed isn't -everything—transparency, quality, and community trust are just as -important as understanding the environmental impacts of using large -language models in our everyday work. Similarly, ethical questions -arise when tools may benefit some communities while harming others. - -## Why we need guidelines - -At pyOpenSci, [we’ve drafted a new policy](https://github.com/pyOpenSci/software-peer-review/pull/344) for our peer review process to set clear expectations for disclosing the use of LLMs in scientific open-source software. - -Our goal is transparency and fostering reproducible research. For scientific rigor, we want maintainers to **disclose when and how they’ve used LLMs** so editors and reviewers can fairly and efficiently evaluate submissions. Further, we want to avoid burdening our volunteer editorial and reviewer team with being the initial reviewers of generated code. - -This is the beginning of our work to ensure that Gen AI tools are not -creating undue burden on our volunteer software review team. Humans -cannot perform in depth reviews at the rate at which these tools can -create large volumes of code. - -## A complex topic: benefits and concerns - -LLMs are perceived as helping developers: - -* Explain complex codebases -* Generate unit tests and docstrings -* Simplify language barriers for participants in open source around - the world -* Speed up everyday workflows - -Some contributors perceive these products as making open source more -accessible. And for some, maybe they do. However, LLMs also present -unprecedented social and environmental challenges that we have to -critically evaluate. - -### Incorrectness of LLMs and misleading time benefits - -Although it is commonly stated that LLMs help improve the productivity -of high-level developers, recent scientific explorations of this -hypothesis [indicate the -contrary](https://metr.org/blog/2025-07-10-early-2025-ai-experienced-os-dev-study/). -What's more is that the responses of LLMs for complex coding tasks -[tend to be -incorrect](https://arxiv.org/html/2407.06153v1) and/or overly -verbose/inefficient. It is crucial that, if you use an LLM to help -produce code, you should independently evaluate code correctness and -efficiency. - -### Environmental impacts - -Training and running LLMs [requires massive energy -consumption](https://www.unep.org/news-and-stories/story/ai-has-environmental-problem-heres-what-world-can-do-about), -raising sustainability concerns that sit uncomfortably alongside much -of the global-scale scientific research that our community supports. - -### Impact on learning - -Heavy reliance on LLMs risks producing developers who can prompt, but -not debug, maintain, or secure production code. This risk undermines -long-term project sustainability and growth. In the long run, it will -make it [harder for young developers to learn how to code and -troubleshoot -independently](https://knowledge.wharton.upenn.edu/article/without-guardrails-generative-ai-can-harm-education/). - -> We’re really worried that if humans don’t learn, if they start using these tools as a crutch and rely on it, then they won’t actually build those fundamental skills to be able to use these tools effectively in the future. _Hamsa Bastani_ - -### Ethics and inclusion - -LLM outputs can reflect and amplify bias in training data. In -documentation and tutorials, that bias can harm the very communities -we want to support. - -## Our approach: transparency and disclosure - -We acknowledge that social and ethical norms, as well as concerns -about environmental and societal impacts, vary widely across the -community. We are not here to judge anyone who uses or doesn't use -LLMs. Our focus centers on supporting informed decision-making and -consent regarding LLM use in the pyOpenSci software submission, -review, and editorial process. - -Our community’s expectation for maintainers submitting a package is simple: **be open and disclose any Generative AI use in your package** when you submit it to our open software review process. - -* Disclose LLM use in your README and at the top of relevant modules. -* Describe how the Generative AI tools were used in your package's development. -* Be clear about what human review you performed on Generative AI - outputs before submitting the package to our open peer review - process. - -Transparency helps reviewers understand context, trace decisions, and -focus their time where it matters most. We do not want the burden of -reviewing code generated from a model, placed on a volunteer. That -effort belongs to the maintainer who ran the model that generated that -code. - -### Human oversight - -LLM-assisted code must be **reviewed, edited, and tested by humans** -before submission. - -* Run your tests and confirm the correctness of the code that you submitted. -* Check for security and quality issues. -* Ensure style, readability, and concise docstrings. Depending on the - AI tool, generated docstrings can sometimes be overly verbose without - adding meaningful understanding. -* Explain your review process in your software submission to pyOpenSci. - -Please **don't offload vetting of generative AI content to volunteer -reviewers**. Arrive with human-reviewed code that you understand, -have tested, and can maintain. As the submitter, you are accountable -for your submission: you take responsibility for the quality, -correctness, and provenance of all code in your package, regardless of -how it was generated. - -### Watch out for licensing issues. - -LLMs are trained on large amounts of open source code, and most of -that code has licenses that require attribution (including permissive -licenses like MIT and BSD-3). The problem? LLMs sometimes produce -near-exact copies of that training data, but without any attribution -or copyright notices. **LLM output does not comply with the license -requirements of the input code, even when the input is permissively -licensed**, because it fails to provide the required attribution. - -Not all code carries the same licensing risk. The risk varies -depending on what you're generating. - -Risk of license infringement is **lower for routine tasks** like -refactoring existing code, test suite improvements, creating -boilerplate code, simple utility functions, and docstring generation. -These tasks are more common, often use widely documented patterns, -and are not as likely to be substantially similar to copyrighted -training data. - -Tasks that are **higher risk** include: - -* Algorithm implementations -* Developing workflows for complex data structures -* Domain-specific logic that is potentially already published or - copyrighted - -For high-risk content (e.g., algorithm implementations), you need to -understand the algorithm to vet its correctness, ensure the approach -is not already published and copyrighted, vet its performance, and -evaluate edge cases. If you understand it well enough to review it -thoroughly, you can often implement it yourself. In these cases, use -LLMs as learning aids—ask questions, study approaches, then write -your own implementation. - -Why this matters: - -* LLM-generated code may be _substantially similar_ to copyrighted - training data; sometimes it is identical. Copyright law focuses on - how similar your content is compared to the original. -* You can't trace what content the LLM learned from (the black box - problem); this makes due diligence impossible on your part. You - might accidentally commit plagiarism or copyright infringement by - using LLM output in your code even if you modify it. -* License conflicts occur because of both items above. Read on... - -When licenses clash, it gets particularly messy. Even when licenses -are compatible (e.g., MIT-licensed training data and MIT-licensed -output), you still have a violation because attribution is missing. -With incompatible licenses (say, an LLM outputs GPL code and your -package uses MIT), you can't just add attribution to fix it—you'd -technically have to delete everything and rewrite it from scratch -using clean-room methods to comply with licensing requirements. - -The reality of all of this is that you can't eliminate this risk of -license infringement or plagiarism with current LLM technology. But -you can be more thoughtful about how you use the technology. - -### What you can do now - -Consider the following: - -* Assess the licensing risk based on what you're generating: routine - refactoring carries lower risk than implementing novel algorithms or - domain-specific logic. -* Be aware that when you directly use content from an LLM, there will - be inherent license conflicts and attribution issues. -* **Use LLMs as learning tools**: Ask questions, review outputs - critically, then write your own implementation based on - understanding. Often the outputs of LLMs are messy or inefficient. - Use them to learn, not to copy. This is especially important for - high-risk content like algorithms. -* Understand and transform code returned from an LLM: Don't paste LLM - outputs directly. Review, edit, and ensure you fully understand what - you're using. You can ask the LLM questions to better understand its - outputs. This approach also helps you learn, which addresses the - education concerns that we raised earlier. -* Consider [clean-room - techniques](https://en.wikipedia.org/wiki/Clean-room_design): Have - one person review LLM suggestions for approach; have another person - implement from that high-level description. -* **Document your process**: If you plan to submit a Python package - for pyOpenSci review, we will ask you about your use of LLMs in your - work. Document the use of LLMs in your project's README file and in - any modules where LLM outputs have been applied. Confirm that it has - been reviewed by a human prior to submitting it to us, or any other - volunteer-led peer review process. - -You can't control what's in training data, but you can be thoughtful -about how you use these tools. - -
-Examples of how these licensing issues are impacting and stressing our -legal systems: - -* [GitHub Copilot litigation](https://githubcopilotlitigation.com/case-updates.html) -* [Litigation around text from LLMs](https://arxiv.org/abs/2505.12546) -* [incompatible licenses](https://dwheeler.com/essays/floss-license-slide.html) -
- -### Review for bias - -Inclusion is part of quality. Treat AI-generated text with the same -care as code. -Given the known biases that can manifest in Generative AI-derived text: - -* Review AI-generated text for stereotypes or exclusionary language. -* Prefer plain, inclusive language. -* Invite feedback and review from diverse contributors. - -## Things to consider in your development workflows - -If you are a maintainer or a contributor, some of the above can apply -to your development and contribution process, too. Similar to how -peer review systems are being taxed, rapid, AI-assisted pull requests -and issues can also overwhelm maintainers too. To combat this: - -* If you are using generative AI tools in your daily workflows, keep each task small, focused, and well-defined. This is particularly important if you are using agent mode. This will produce smaller changes to your codebase that -will be easier to thoughtfully review and evaluate. -* Open an issue first before submitting a pull request to a repository that you don't own to ensure it's - welcome and needed -* Keep your pull requests small with clear scopes. -* If you use LLMs, test and edit all of the output before you submit a - pull request or issue. -* Flag AI-assisted sections of any contribution so maintainers know - where to look closely. -* Be responsive to feedback from maintainers, especially when - submitting code that is AI-generated. - -## Where we go from here - -A lot of thought and consideration has gone into the development of -[pyOpenSci's Generative AI -policies](https://www.pyopensci.org/software-peer-review/our-process/policies.html#policy-for-use-of-generative-ai-llms). - -We will continue to suggest best practices for embracing modern -technologies while critically evaluating their realities and the -impacts they have on our ecosystem. These guidelines help us maintain -the quality and integrity of packages in our peer review process while -protecting the volunteer community that makes open peer review -possible. As AI tools evolve, so will our approach—but transparency, -human oversight, and community trust will always remain at the center -of our work. - -## Join the conversation - -This policy is just the beginning. As AI continues to evolve, so will -our practices. We invite you to: - -👉 [Read the full draft policy and discussion](https://github.com/pyOpenSci/software-peer-review/pull/344) -👉 Share your feedback and help us shape how the scientific Python - community approaches Generative AI in open source. - -The conversation is only starting, and your voice matters. diff --git a/_posts/2026-01-12-cynetdiff.md b/_posts/2026-01-12-cynetdiff.md deleted file mode 100644 index 672eb196..00000000 --- a/_posts/2026-01-12-cynetdiff.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -layout: single -title: "CyNetDiff: A Python Library for Accelerated Implementation of Network Diffusion Models" -blog_topic: software -excerpt: "CyNetDiff is a Python package for accelerating network diffusion simulations, recently accepted into the pyOpenSci ecosystem." -author: Eliot W. Robson -permalink: /blog/cynetdiff.html -header: - overlay_image: images/headers/pyopensci-floral.png -categories: - - blog-post - - cynetdiff - - network-diffusion - - pyos-accepted -classes: wide -comments: true -last_modified: 2026-01-12 ---- - -Network diffusion models study how information and epidemics spread over social networks, and have garnered increasing interest in recent years. Two of the most widely used models are the **independent cascade (IC)** and **linear threshold (LT) models**. These models simulate spread through a network by starting with a set of seed nodes that are designated as "active", and all other nodes are "inactive". Then, in each iteration, some inactive nodes become active, and the iterations continue until no other nodes can be activated. - -Many tasks related to these models, particularly influence maximization, require running simulations on large graphs many times, which can be computationally expensive. There has been significant prior work on reducing the number of simulations needed for these tasks, demonstrating the importance of an efficient implementation. - -While researchers often prefer high-level languages like Python for their flexibility and quick development, these computationally intensive tasks are better suited for low-level, compiled languages. To bridge this gap, we've developed **CyNetDiff**, a Python library that uses components written in Cython to provide the performance of a compiled language with the flexibility of a high-level language. - ---- - -## Package Features - -The performance-critical parts of CyNetDiff are written in **Cython**, a language extension that compiles to C++ while remaining callable from Python. To take advantage of Cython's speed, the library uses the **compressed sparse row (CSR) format** in an array-based representation. This data structure has low memory overhead and allows for efficient, repeated traversals, making it ideal for diffusion process simulations. - -The implementation also uses a **BFS-based traversal algorithm** that only explores the out-neighbors of nodes activated in the previous iteration. This means the work done is proportional to the number of edges connected to activated nodes, which can be much smaller than the size of the entire graph, especially when there are few seed nodes. - -The package also includes utility functions to easily convert well-established NetworkX graphs into the CSR format, allowing for seamless integration into existing research pipelines. - ---- - -## Development - -The development and review process for CyNetDiff was lengthy, partly due to the complex packaging requirements for Cython. After trying many different tools, we eventually settled on PDM and Meson as the packaging tools for this project. Our greatest difficulty in converting from prior tools was understanding the configuration files necessary for building and releasing our package. - -### Use of Generative AI - -During development, we used Google Gemini to help interpret and understand Meson's configuration syntax for our project's build system. The AI-generated configuration suggestions were carefully reviewed, tested, and adapted to our specific project needs before implementation. All code was validated through our test suite and the pyOpenSci review process. - -pyOpenSci encourages transparency about AI tool use in package development. To learn more about our approach to generative AI in open source software, see our [generative AI peer review policy](/blog/generative-ai-peer-review-policy.html). - ---- - -## Citing - -This library is joint work with Dhemath Reddy and [Abhishek K. Umrawal](https://ece.illinois.edu/about/directory/faculty/aumrawal). -This post is adapted from our [VLDB paper](https://dl.acm.org/doi/abs/10.14778/3685800.3685887), which -should be used for citations. diff --git a/_posts/2026-01-22-building-resilience-2026.md b/_posts/2026-01-22-building-resilience-2026.md deleted file mode 100644 index d642d399..00000000 --- a/_posts/2026-01-22-building-resilience-2026.md +++ /dev/null @@ -1,203 +0,0 @@ ---- -layout: single -title: "Building Resilience: pyOpenSci in 2026" -blog_topic: community -excerpt: "pyOpenSci learned a lot about resilience in 2025. As we navigate generative AIs impact on scientific open source and shifting funding landscapes, pyOpenSci is building resilience through training, sponsorship, and community-centered leadership. Learn more about our plan." -author: "pyopensci" -permalink: /blog/2026-building-resilience-together.html -header: - overlay_image: images/headers/pyopensci-floral.png -categories: - - blog-post - - community -classes: wide -toc: true -comments: true -last_modified: 2026-01-22 ---- - -## The world has changed - -I'm not going to sugarcoat it—2025 was hard. - -Watching organizations like the [Python Software Foundation turn down million-dollar National Science Foundation (NSF) grants](https://pyfound.blogspot.com/2025/10/NSF-funding-statement.html) -because DEIA work was no longer supported made something painfully clear: the funding landscape that has grounded so many of us is no longer reliable. - -At the same time, Generative AI fundamentally changed how open source work happens. Maintainers found themselves reviewing more code than ever—much of it machine-generated—while navigating ethical questions about authorship and responsibility they didn't sign up to solve on their own. - -For many of us, the ground felt unstable—not just months out, but sometimes day to day. - -And yet, something important happened. - -Instead of pulling back, **the pyOpenSci community leaned in**. People showed up -for each other. They reviewed, mentored, taught, organized, and adapted—often -quietly, often while navigating uncertainty of their own. - -
- - - pyOpenSci community members working together - -
The pyOpenSci community showing up for each other. SciPy 2025 beginner-friendly sprint.
-
- -## The moment that changed everything - -One moment from 2025 keeps coming back to me: our sprint at SciPy. - -We had record attendance at that sprint compared to previous years! The large group self-organized. Tables moved. Whiteboards filled with ad-hoc lessons -on Git and GitHub. Experienced contributors mentored newcomers. - -I think about the colleague who sat down, saw the size of the sprint group, -and asked—"how can I help?". He spent the day working with other sprinters, -helping them make their first contributions to open source. - -This is one example of how baking structure and strong value systems into a community can yield incredible returns. The **community members show up for each other because that is just how pyOpenSci operates**. It's who we are, and it's magical. - -
- - - Tracey and James at the SciPy 2025 sprint - -
Tracey and James at the SciPy 2025 sprint, where community - members came together to support each other and make their first - contributions to open source.
-
- - ->Community members show up for each other because that is how pyOpenSci operates. It's who we are and it's magical. -{: .highlight-quote .magenta } - - -
- - - SciPy 2025 sprint participants working in small groups - -
SciPy 2025 community session participants discussing challenges in scientific Open Source.
-
- - -That day felt like the manifestation of a blueprint. It showed what happens when you create the -right environment and empower a community to help each other. That model— -shared ownership, peer learning, mutual support—changed how I think about -building sustainable infrastructure and community. - -Resilience is about building inclusive spaces for people to both engage, learn, and support each other. - -## What we're building in 2026 - -### Shifting how we think about sustainability - -One of the clearest lessons from 2025 is that a grant-dependent model alone -can't support the community-centered infrastructure pyOpenSci is -building. Grants will always matter, but they can't be the only pillar -holding us up. - -As we move into 2026, pyOpenSci is shifting toward a more -diversified sustainability plan--one that centers on training, sponsorship, and -community support. And as always, our focus will remain on human-centered growth and development, which is particularly critical in our current moment of Generative AI-driven rapid change. - -Value-alignment will continue to be critical as we make this shift. - -### Training that connects people and builds responsible practice - -In 2026, we're investing in a new type of training: programs that bring -researchers from around the world together to learn both software development -best practices *and* the responsible use of Generative AI in scientific and -open source workflows. The training will be held asynchronously to reduce barriers -to participation and increase accessibility. And it will feature incredible leaders in the open source community in an effort to connect learners to the **real heart of Open Source--people**. - - -We’re piloting this new training format through our [partnership with -Stanford's Open Source Program Office (OSPO)](https://www.pyopensci.org/events/pyopensci-stanford-create-python-package-workshop.html). Building on the momentum, we’ll expand this model to other university OSPOs, leveraging the strong connections within the CURIOSS network and building an organizational membership program that supports researchers at scale. While our courses will initially be designed for OSPO researchers they will be valuable to anyone looking to develop the open source and responsible us of Generative -AI skills that are in high demand in today's tech-driven job market. - -Generative AI can support open source workflows, but it can't fully replace -the thought, design, and vision that only a human can implement. It also can't replace -mentorship, care, and compassion—human parts of the tech world that both fuel the -open source ecosystem and are also straining the fragile social web that supports it. Our training will continue to emphasize foundational open source skills, critical thinking and shared open source norms and workflows. - -Our goal is to create learning spaces that reduce isolation, build -confidence, connect learners to the humans that drive open source, and strengthen the -communities behind scientific software. - -
- - - Teaching a pyOpenSci workshop at SciPy 2025 - -
Teaching a pyOpenSci workshop at SciPy 2025, focused on Python - packaging and open source best practices.
-
- -## The people who make this possible - -None of what we learned in 2025—or what we're building toward in 2026— -happened in isolation. - -I'm deeply grateful to our 2025 Executive Council, **[Karen Cranston](https://github.com/kcranston) -and [April Johnson](https://github.com/aprilmj)**, for their steady guidance -and support as we navigated a challenging year. They encouraged me to embrace -sustainable leadership and supported me as I developed new programs to ensure -our long-term sustainability. - -Our Editors-in-Chief—**[James Balamuta](https://github.com/coatless), -[Eliot Robson](https://github.com/eliotwrobson), and [Lauren Yee](https://github.com/yeelauren)**— -led the peer review process through a period of rapid change, navigating -AI-fueled submissions while keeping quality and care at the center. -**[Carter Rhea](https://github.com/crhea93)** stepped up as an editor -numerous times, supporting volunteers who were overburdened and leading one -of our largest reviews—Astropy—which was just accepted in early 2026. - -
- - - PyCon US 2025 Maintainers Summit - -
PyCon US 2025 Maintainers Summit, where we created space for - those who keep scientific Python running.
-
- -Members of our Advisory Council—**[Carol Willing](https://github.com/willingc), -[Inessa Pawson](https://github.com/InessaPawson), and [Chase Million](https://github.com/cmillion)**— -helped me think through sustainability, partnerships, and long-term direction -with wisdom and generosity. - -
- - - Carol Willing presenting on micromentoring - -
Carol Willing from our Advisory Council presenting on - micromentoring at the Maintainers Summit, co-organized by pyOpenSci at PYCON US 2025.
-
- -**And to the many community members who contributed through reviews, workshops, -sprints, writing, mentoring, and quiet acts of support—thank you. This -organization exists because you choose to show up.** - -## Community matters most - -When things are uncertain, community infrastructure matters most. -It's how people find support that institutions can't provide. It's how -knowledge gets shared when traditional collaboration pathways break down. -It's how we take care of each other. - -That SciPy sprint showed me what's possible. When you create spaces where -people can show up, learn together, and support each other—when you do that -consistently—the community becomes the infrastructure. - -This is how we build resilience. This is how we help make the world better. **Together.** diff --git a/_posts/2026-05-04-ship-it-cohort-one.md b/_posts/2026-05-04-ship-it-cohort-one.md deleted file mode 100644 index 228740cd..00000000 --- a/_posts/2026-05-04-ship-it-cohort-one.md +++ /dev/null @@ -1,128 +0,0 @@ ---- -layout: single -title: "Ship It Cohort One: What We Built, What We Learned, and What's Next" -blog_topic: education -excerpt: "pyOpenSci recently wrapped up the first cohort of Ship It: Python Packaging in the Generative AI Era — a 10-day async course that took 55 university researchers from zero to a published Python package. Here's what we built, what participants said, and what's coming next." -author: "Leah Wasser" -permalink: /blog/ship-it-cohort-one.html -blog_image: images/landing-pages/scipy-sprint-working.png -blog_image_alt: "Three people collaborating during a pyOpenSci sprint, working together on laptops at a conference table." -header: - overlay_image: images/headers/pyopensci-floral.png -categories: - - blog-post - - training - - community -toc: true -classes: wide -comments: true -last_modified: 2026-05-04 ---- - -Python packaging can be overwhelming — standards and tools evolve quickly, and the terminology is technical. To address these challenges, pyOpenSci recently launched **Ship It: Python Packaging in the Generative AI Era**. This 10-day asynchronous course connects university researchers and learners with open source experts, guiding them through the process of transforming code into a published, tested, and documented package — while weaving in a framework for using generative AI thoughtfully. The curriculum is informed by our community-driven resources, including the [pyOpenSci Python packaging guide](https://www.pyopensci.org/python-package-guide/) and our beginner-friendly [packaging tutorials](https://www.pyopensci.org/python-package-guide/tutorials/create-python-package.html). - -## What is Ship It? - -Ship It is a 10-day, fully online, asynchronous cohort course that takes you from zero to a published Python package — with tests, documentation, and automated workflows built on the [pyOpenSci packaging template](https://github.com/pyOpenSci/pyos-package-template). - -The course was designed as an accessible entry point for researchers, academics, research software engineers, and anyone who wants to package and share their work using modern best practices. Participants learn how to use packaging tools like [Hatch](https://hatch.pypa.io/) for environment management and task running, [uv](https://docs.astral.sh/uv/) for fast dependency resolution, and [pytest](https://pytest.org/) for testing — all inside GitHub Codespaces, so nobody gets stuck on local setup. - -Each weekday, a new module drops, made up of videos and text lessons. Each week, participants complete interactive "milestones" in which they share their own package or day's work and then test a classmate's. This project-based learning approach mirrors the spirit of open source collaboration. Engagement was also encouraged by a gamified points system, where participants earned points working toward a final LinkedIn badge and completion certificate. For many, the community aspect of the course was an unexpected — but carefully designed — highlight. - -In response to "what was your favorite part of the course", one participant said: - -{% include pyos-blockquote.html quote="Probably the community part. Don't misunderstand me, the lectures are great, clear and well structured. Easy to follow. But interacting with the community, testing each other's code, has been sincerely great." class="highlight" %} - -## Learning from the people who build the tools - -Sprinkled throughout the course were interviews with open source leaders like Carol Willing (Jupyter, CPython) and Melissa Mendonça (NumPy, Napari), so learners got more than technical skills — they received insights from the people building the tools they use every day. - -The async format was a deliberate choice. pyOpenSci has run synchronous training events for years, but fixed timing limits who can attend, leaves little room for digestion, and tends to keep engagement within single institutions. An async cohort format gives participants flexibility to learn at their own pace while still building real connections across universities. - -We also wove a thread of responsible AI throughout the course. We've been thinking deeply about this topic for a few years, and we wanted to offer a clear framework for using AI tools in ways that grow your skills and sustain — rather than drain — the open source ecosystem. - -## Ship It by the numbers - -This first cohort was generously sponsored by Open Source at Stanford University and supported by the [CURIOSS OSPO network](https://curioss.org/), making it free for all 55 participants. - -- **55** registered participants from universities across the US -- **27** videos across 10 modules -- **6** gamified milestones — 14 participants completed all of them -- **~30** participants created and published a Python package — for many, their first ever -- Sponsored by [Stanford University](https://opensource.stanford.edu/) and supported by the CURIOSS OSPO network - -It was incredible to see that ~30 people published a Python package, many for the very first time. But the numbers only tell part of the story. - -## What participants experienced - -Ship It was designed to be hands-on from day one — and that showed in the outcomes. One participant, a structural biologist,was able to clean up a package they had been working on and share it at her departmental retreat. She shared her story with us: - -{% include pyos-blockquote.html quote="This week I presented my Python package 'PDBCleanV2' at the Structural Biology Departmental Retreat. It was well received by attendees and, overall, a great experience. I wanted to thank you because pyOpenSci was a great influence in getting my package in shape." author="Fatima Pardo, Structural Biologist" class="highlight" %} - -That's the goal. Real code, real learning, real impact. - -### The community made it real - -The milestone structure pushed participants to engage with each other's work — and that turned out to be one of the most talked-about parts of the course. - -{% include pyos-blockquote.html quote="I finally caught up on the content of the workshop, and I have been loving it. Some concepts used to seem very intimidating, but I liked how they were explained clearly. I have also been enjoying the community aspect, checking other people's packages and getting comments on mine." class="highlight" %} - -### Structure and clarity mattered - -Participants consistently called out the clear, step-by-step progression as a highlight. Going from "what even is a `pyproject.toml`" on Day 2 to "I just published my package to TestPyPi on Day 3 allowed for big early wins that helped people stay oriented and keep moving. - -{% include pyos-blockquote.html quote="My favorite part of the course has been the step-by-step guidance and the hands-on experience of building a package. I also really liked the videos where the mentor shares the challenges they faced — it made the process feel more real and relatable. Testing classmates' packages was another highlight, as it helped me learn from others and see different approaches." class="highlight" %} - -{% include pyos-blockquote.html quote="Loved how interactive the workshop was, and how responsive the instructors were. The content was great, and the videos made it so easy to understand the concepts. And having the text below helped me when I wanted to review." class="highlight" %} - -### Hands-on from day one - -Learners also commented on how the course broke down technical packaging terms and content into easy to digest lessons. -Imposter syndrome is real and pyOpenSci works hard to break down -those barriers for everyone. - -{% include pyos-blockquote.html quote="I am very motivated. The workshop made me less afraid of certain aspects of creating the package; the explanations have been very clear and to the point. I am planning to implement what we learned on my own package, and I feel confident that I can do it." class="highlight" %} - -### Honest takes on GenerativeAI - -Several participants specifically called out the AI content — not just the technical pieces, but the perspective. The responsible AI framing resonated, as did the expert interviews. - -In response to a question about the Generative AI content, one participant had this to say about the associated Generative AI milestone activities: - -{% include pyos-blockquote.html quote="[the genAi module] It was an interesting session. I got more than I expected, in particular from the milestone. Seeing how other people interacted with AI and what we learned from it. I think it is important to have these discussions." class="highlight" %} - -GenerativeAI has such a profound impact on all aspects of writing code right now. We encouraged participants to experiment with using GenAI tools to write tests and implement "defensive code strategies" in their package modules and then reflect and share the outcomes of this effort with each other. - -### The course design flexibility made it accessible - -{% include pyos-blockquote.html quote="I attended a conference during this course (full day for two days) and was still able to complete the course once the conference ended. I appreciate the structure of the course, and the flexibility was also needed." class="highlight" %} - -## A window into the Python ecosystem — and the AI conversation - -Ship It was about more than packaging mechanics. Through interviews with Python leaders like Carol Willing, CPython core developer, and Melissa Mendonça from Quansight, participants got an inside look at the Python ecosystem that most of us simply aren't exposed to in traditional academic environments. - -That included an honest conversation about AI — specifically, how the explosion of generative AI is placing real pressure on the open source human ecosystem. The people maintaining the tools you rely on are volunteers. That context matters. - -The course also explored how to use AI thoughtfully — as a tool for building more robust test suites, writing defensive code, and deepening your own understanding. Not something to become reliant on, but something to grow with. - -## What we learned from cohort one - -The flexibility of the async format resonated more than we expected. Participants were logging in at 2 am, at 7 am, whenever it fit their lives and time zones. - -My favorite part of running this course was the cross-institutional engagement. Learners at Stanford, Berkeley, UC Davis, UC Santa Barbara, Harvard, Wisconsin, and universities across the country were connecting with each other in ways that simply don't happen within a single institution. That's exactly what pyOpenSci is built for — bringing the broader community together rather than keeping expertise siloed. - -That community connection extended to the experts, too. Participants learned directly from the people building the tools they use every day through real conversations. That kind of engagement is what makes pyOpenSci's trainings shine, and it's hard to replicate. - -For pyOpenSci, the async cohort model also supports our sustainability goals. Running the course multiple times for different groups, at their own pace, means we can reach more people — and build toward long-term sustainability by doing what we do best: making packaging and sharing code more accessible so researchers can spend more time solving the world's greatest challenges and less time fighting with tooling. - -## Bringing Ship It to your institution - -If you're running an OSPO, a research computing program, or any initiative that supports academics who write and share code, ShipIt was built with you in mind. The course gives your community direct access to modern Python packaging practices, responsible AI frameworks, and — crucially — real connections to the people building the tools within the open source ecosystem. - -[Click here to learn more about our partnership program.]({{ site.baseurl }}/learn-universities-labs.html). We'd love to talk with you about how we can support your community. - -## What's next - -The first cohort proved the model works. Researchers published packages, made connections across institutions, and left with skills and confidence they didn't have before. We're building on that for fall 2026. - -Keep an eye on our [events page](https://www.pyopensci.org/events.html) for cohort announcements. diff --git a/_posts/2026-05-21-pycon-us-2026-community-connection.md b/_posts/2026-05-21-pycon-us-2026-community-connection.md deleted file mode 100644 index ddfd2fe4..00000000 --- a/_posts/2026-05-21-pycon-us-2026-community-connection.md +++ /dev/null @@ -1,76 +0,0 @@ ---- -layout: single -title: "PyCon US 2026: Community, connection, and what comes next" -blog_topic: community -excerpt: "Times are hard, but communities like pyOpenSci still show up. At PyCon US 2026 in Long Beach, we saw people connecting, sprinting, and supporting each other—and why the humans are what make open source work." -author: "Leah Wasser" -permalink: /blog/pycon-us-2026-community-connection.html -blog_image: images/blog/2026/pycon-us-2026-sprint-attendees.png -blog_image_alt: "Three pyOpenSci community members smiling together at an outdoor patio during PyCon US 2026 in Long Beach." -header: - overlay_image: images/blog/headers/pycon-us-2026-header.png -categories: - - blog-post - - community -classes: wide -toc: true -comments: true -last_modified: 2026-05-27 ---- - -Times are hard right now. Communities like pyOpenSci matter more than ever. We saw it at [PyCon US](https://us.pycon.org/2026/) this year — the political strain, the GenAI hype cycle and anxiety, the rapid changes reshaping open source. People are tired. People are frustrated. But here's what else we saw: people still showing up. Still connecting. Still supporting each other. - -When things get hard, when the ground shifts beneath us, we need each other more than ever. We need spaces to connect, to learn together, to solve challenging problems as a community. PyCon reminded me why I love this work so much. - -I'll never forget my very first PyCon in Salt Lake City. I was intimidated. It's a really big conference, and I only knew a few people. That first sprint? We had one person show up who wasn't already part of our community. He sat down and spent the entire day working on our website, helping us improve our infrastructure. He gave his time to help a nonprofit organization like pyOpenSci grow. - -[Read more about that first PyCon experience in Salt Lake City](/blog/pycon-2023-packaging-presentation-sprints-leah-wasser.html). - -
- - - Two people collaborating on laptops at a round table during a PyCon US coding sprint. One person wears a PyCon US 2024 lanyard. The laptop on the right is covered in stickers for NumPy, NumFocus, Hubble, and other scientific open source projects. - -
Community members working together during a pyOpenSci sprint at PyCon US.
-
- -Fast forward three years to Long Beach, California. We filled rooms with our open spaces. We had tables of people sprinting, working on pyOpenSci projects, learning together. People who attended multiple sprints with us — people I remembered struggling to submit their first pull request, now confident in their GitHub skills. This is what community support looks like. This is what pyOpenSci's mission is all about: helping people grow. [Read about the Maintainer Summit](/blog/pycon-us-2026-maintainers-summit.html) and the [full sprint story](/blog/pycon-us-2026-sprints.html) from PyCon US 2026. - -
- - - Two smiling PyCon US 2026 attendees sitting at a round table with laptops during a conference sprint. Both wear purple PyCon US 2026 lanyards from Long Beach. - -
Building connections and working together during the PyCon US 2026 sprints in Long Beach.
-
- -PyCon US has become my favorite conference. Not because of the topics covered or how it's organized, but because of the people. The people who were strangers a few years ago and have now become my closest friends and colleagues. The hallway conversations. The way people show up for each other. - -
- - - Three pyOpenSci community members smiling together at an outdoor patio during PyCon US 2026 in Long Beach. The person in the center wears a PyCon US 2026 conference lanyard and name badge. - -
Friendships and connections that grow year after year at PyCon US.
-
- -Even in a hard year — especially in a hard year — I left the conference feeling fulfilled, feeling whole, and so grateful for the friendships and relationships I've deepened over time. - -## In this series - -**Part 1: Community, connection, and what comes next** — You're reading it. An overview of PyCon US 2026 and why the people in the room are what make open source work. - -**Part 2: [The Maintainer Summit](/blog/pycon-us-2026-maintainers-summit.html)** — A milestone year for the summit: formal PyCon registration, 150+ maintainers, and roundtables on packaging, security, and generative AI. - -**Part 3: [The sprints](/blog/pycon-us-2026-sprints.html)** — From one person in Salt Lake City to a room full of contributors working together. Growth, learning, and community in action. - -**Part 4: [Generative AI and open source](/blog/pycon-us-2026-generative-ai-open-source.html)** — GenAI was everywhere at PyCon this year. Not as hype, but as a real challenge the community is wrestling with. Between our open space, the Maintainer Summit BoF, and Amanda Casari's powerful closing keynote, a theme emerged: the path forward is human. - -## What it all comes down to - -If there's one thing that tied all of these experiences together, it's this: **the humans are what make open source work.** The tools change, the challenges evolve, but the connections, the learning, the care people bring to this work — that's the irreplaceable part. - - ---- - -*This is Part 1 of a 4-part series on pyOpenSci at PyCon US 2026. [Part 2: Maintainer Summit](/blog/pycon-us-2026-maintainers-summit.html) · [Part 3: pyOpenSci sprints](/blog/pycon-us-2026-sprints.html) · [Part 4: generative AI and open source](/blog/pycon-us-2026-generative-ai-open-source.html).* diff --git a/_posts/2026-05-29-pycon-us-2026-maintainers-summit.md b/_posts/2026-05-29-pycon-us-2026-maintainers-summit.md deleted file mode 100644 index 600ca33c..00000000 --- a/_posts/2026-05-29-pycon-us-2026-maintainers-summit.md +++ /dev/null @@ -1,135 +0,0 @@ ---- -layout: single -title: "Tools Track, GenAI Bof and Roundtables: This year's PyCon US Maintainer Summit was something special" -blog_topic: community -excerpt: "As a co-organizer of the PyCon US Maintainer Summit, Leah Wasser reflects on a milestone year—150+ maintainers, formal PyCon registration, new community tools, and roundtables on packaging, security, and generative AI." -author: "Leah Wasser" -permalink: /blog/pycon-us-2026-maintainers-summit.html -blog_image: images/blog/2026/pycon-us-2026-maintainers-summit-organizers.png -blog_image_alt: "Maintainer Summit co-organizers Leah Wasser, Inessa Pawson, and Mariatta Wijaya smile together for a selfie on an outdoor patio at PyCon US in Long Beach, wearing conference lanyards." -header: - overlay_image: images/blog/headers/pycon-us-2026-header.png -categories: - - blog-post - - community -classes: wide -toc: true -comments: true -last_modified: 2026-05-29 ---- - -*This post is Part 2 of a 4-part series on pyOpenSci at PyCon US 2026. [Part 1](/blog/pycon-us-2026-community-connection.html) covers community, connection, and what comes next.* - -I first attended the [PyCon US Maintainer Summit](https://us.pycon.org/2026/events/maintainers-summit/) five years ago as a speaker. I'm still here — now as a co-organizer. This year felt like a milestone for many different reasons. - -
- - - Maintainer Summit organizers slide with headshots of Inessa Pawson, Leah Wasser, and Mariatta Wijaya beneath the Maintainers Summit title and PyCon US Long Beach branding. - -
Maintainer Summit co-organizers Inessa Pawson, Leah Wasser, and Mariatta Wijaya.
-
- -## Building the summit: a year-round labor of love - -What most people don't realize is that organizing the PyCon US Maintainer Summit is a year-round effort. We start thinking about the next one almost as soon as the last one wraps — collecting participant feedback, reflecting on what worked, and thinking about what to change. - -This year we focused on something we've been working toward for a while: making the Maintainer Summit a more formal part of the PyCon US schedule. The Maintainer Summit has been running since 2019; this year we took a big step forward. We could communicate directly with the conference attendees registered for the Summit through the PyCon US registration website, and our schedule was available through the PyCon US schedule webpage. These might seem like small steps, but it takes a lot to make these types of changes in a large event like PyCon US that is organized by both PyCon US staff but also depends on a large team of volunteers. - -This year's successes wouldn't have happened without the incredible support of PyCon US co-chairs [Elaine Wong](https://github.com/elainewong) and [Jon Bonafato](https://github.com/jonafato), and PSF infrastructure lead [Jacob Coffee](https://github.com/jacobcoffee). - -## New structure and event features for the day - -We also rethought the structure of the Maintainer Summit this year. We added a Tools Track, inspired by the Tools Track at SciPy. We also added a GenAI Birds of a Feather session, and roundtables focused on lively discussions around packaging, security, and AI tools in open source. - -
- - - Cary Hawkins presents on Hatch in the Tools Track at the PyCon US Maintainer Summit in Long Beach, standing at a podium beside a slide titled From Sidelines to Maintainer: How one conversation sparked new development in Hatch. - -
Cary Hawkins presents on Hatch in the Maintainer Summit during the newly added Tools Track that featured both Hatch and UV.
-
- -We also set up an unofficial Discord with an automated bot — built with help from some wonderful folks in the Python community — that provided real-time session updates and gave attendees a way to connect with each other throughout the days of the conference. A first for the summit. This was a great way to help attendees connect with each other and follow the conference schedule without having to keep checking the schedule website. - -None of this happens without an incredible team. [Inessa Pawson](https://github.com/InessaPawson) and [Mariatta Wijaya](https://github.com/mariatta) bring deep experience organizing events like this, and I've learned so much working alongside them. From the call for proposals, to reading submissions, to managing last-minute schedule changes — there's an enormous amount of work that goes into making a day like this feel seamless. - -
- - - Maintainer Summit co-organizers Leah Wasser, Inessa Pawson, and Mariatta Wijaya smile together for a selfie on an outdoor patio at PyCon US in Long Beach, wearing conference lanyards. - -
Maintainer Summit co-organizers Leah Wasser, Inessa Pawson, and Mariatta Wijaya at PyCon US in Long Beach.
-
- -## A day full of energy - -This year our registrations peaked at 150+ people for a full day of conversation around packaging, security, and AI. The event ran from 10am to 6pm, and while attendance dipped after lunch (this always happens at these types of events), energy in the room kept building. People sticking around until 6pm when they closed down the room was a great sign that the community is growing and that people are interested in the topics we were discussing. - -
- - - A wide view of the PyCon US Maintainer Summit in Long Beach, with dozens of maintainers seated at round tables, many with open laptops, listening to a session. - -
- -It was especially meaningful to have pyOpenSci community members represented. [Felipe Moreno](https://github.com/flpm) spoke about the translations infrastructure he built for our [Python packaging guide](https://www.pyopensci.org/python-package-guide/) — work that started at our [2024 sprint](/blog/pyopensci-pyconus-2024-sprints.html) and has grown into a multilingual contribution community. [Part 3 of this series](/blog/pycon-us-2026-sprints.html) tells the full 2026 sprint story, including Portuguese translation work that built on Felipe's infrastructure. [Avik Basu](https://github.com/ab93) shared work on a package he's been developing. Having pyOpenSci members on stage alongside maintainers of core Python tools like [uv](https://docs.astral.sh/uv/) ([Zanie Blue](https://github.com/zanieb)) and [Hatch](https://hatch.pypa.io/) ([Cary Hawkins](https://github.com/cjames23)), and community leaders like [Carol Willing](https://github.com/willingc) and [Mike Fiedler](https://github.com/miketheman) from the PSF covering security and AI, made for a powerful and well-rounded day. - -
- - - Felipe Moreno presents How Translations Work for pyOpenSci at the PyCon US Maintainer Summit in Long Beach, with a slide showing the pyOpenSci logo projected on screen. - -
Felipe Moreno presents on translation infrastructure for the pyOpenSci Python packaging guide at the Maintainer Summit.
-
- -## Roundtables: where the magic happens - -The roundtables are always the best part of this event. I will never forget my first roundtable discussion at this event four years ago. I sat with Travis Oliphant (creator of NumPy, SciPy, Numba) and Chase Million ([Million Concepts](https://millionconcepts.com/about.html) CEO) and we talked about business development in open source. Chase, to this day, is not only a long-standing colleague but also a good friend. These types of events allow attendees to build connections that they never forget. - -This year, roundtable topics included packaging led by [Zanie Blue](https://github.com/zanieb) from uv and [Cary Hawkins](https://github.com/cjames23) from Hatch, security led by [Mike Fiedler](https://github.com/miketheman) from the Python Software Foundation, and AI tools in open source led by the incredible [Carol Willing](https://github.com/willingc). - -What makes roundtables so powerful is that they're real conversations with experts and peers. At the end of a long day, when energy could easily be fading, the roundtables sparked connection and lifted the room. - -
- - - Two maintainers sit side by side at a round table during the PyCon US Maintainer Summit, focused on a session with laptops and conference lanyards on the table. - -
- -## The most electric moment - -Our Generative AI Birds of a Feather session, co-hosted with [Jackie Kazil](https://github.com/jackiekazil), was an energizing addition to this year's summit. Jackie reached out to us about hosting this part of the day. Maintainers everywhere are feeling overwhelmed and drained by the pace of AI change. Now more than ever, protecting our mental health and making space to connect, problem-solve together, and support each other matters. - -
- - - Jackie Kazil co-hosts the Generative AI Birds of a Feather session at the PyCon US Maintainer Summit, with a live Mentimeter poll on screen asking maintainers how useful they find GenAI in their workflows. - -
- -But there's also genuine hope. The majority of people in the room are already using GenAI tools thoughtfully, and that energy of working through hard things *together* was everywhere — echoed beautifully in Amanda Casari's keynote on hope in the age of AI. - - - -I have a lot more to say about AI tools and generative AI in open source in [Part 4 of this series](/blog/pycon-us-2026-generative-ai-open-source.html). - -## Wrap up - -
- - - Two attendees have a focused one-on-one discussion at a table during the PyCon US Maintainer Summit, leaning in as they talk through a shared challenge. - -
- -This year's Maintainer Summit felt like a turning point — 150+ maintainers in one room, a more formal home within PyCon US, and new formats that gave people space to connect, learn, and work through hard problems together. That's exactly what this community needs. - -pyOpenSci is proud to be an organizing partner for the PyCon US Maintainer Summit. Maintainer work can be isolating; days like this remind us we're not doing it alone. If you couldn't join us in Long Beach, we hope to see you next year. - -See you in there. - ---- - -*This is Part 2 of a 4-part series on pyOpenSci at PyCon US 2026. [Part 1](/blog/pycon-us-2026-community-connection.html): community and connection · [Part 3: pyOpenSci sprints](/blog/pycon-us-2026-sprints.html) · [Part 4: generative AI and open source](/blog/pycon-us-2026-generative-ai-open-source.html).* diff --git a/_posts/2026-06-02-pycon-us-2026-sprints.md b/_posts/2026-06-02-pycon-us-2026-sprints.md deleted file mode 100644 index f9370c78..00000000 --- a/_posts/2026-06-02-pycon-us-2026-sprints.md +++ /dev/null @@ -1,103 +0,0 @@ ---- -date: 2026-06-01 12:00:00 -0700 -layout: single -title: "It started with one contributor and now we fill the room: pyOpenSci sprints at PyCon US 2026" -blog_topic: community -excerpt: "Our fourth PyCon US sprint grew from a single contributor in Salt Lake City to ~20 people in Long Beach. Co-sprinting with Hatch, welcoming beginners, and a new Portuguese packaging guide translation show where this community is headed." -author: "Leah Wasser" -permalink: /blog/pycon-us-2026-sprints.html -blog_image: images/blog/2026/pycon-us-2026-sprint-round-table.png -blog_image_alt: "Several PyCon US 2026 attendees work together around a crowded round table during the pyOpenSci sprint. Laptops show code editors and project pages, with purple conference lanyards, charging cables, water bottles, and coffee cups spread across the table." -header: - overlay_image: images/blog/headers/pycon-us-2026-header.png -categories: - - blog-post - - community -classes: wide -toc: true -comments: true -last_modified: 2026-05-29 ---- - -*This post is Part 3 of a 4-part series on pyOpenSci at PyCon US 2026. [Part 1](/blog/pycon-us-2026-community-connection.html) covers community and connection. [Part 2](/blog/pycon-us-2026-maintainers-summit.html) covers the Maintainer Summit.* - -This year marked our fourth sprint at [PyCon US](https://us.pycon.org/2026/). If you want to really understand what pyOpenSci is about, our sprints are the place to look. - -
- - - The PyCon US 2026 sprints slide projected on the main keynote screen, listing Monday and Tuesday sprint projects including Django, Mailman, ScanAPI, Memray, PyStack, Djangonaut Space, BeeWare, and more. - -
Sprints at PyCon US 2026 — dozens of open source projects welcoming contributors on Monday and Tuesday.
-
- -When we held our very first sprint at PyCon US 2023 in Salt Lake City, it was my first time running a formal sprint. I missed the introductions to the sprints and pyOpenSci was not well known in the broader Python community--yet. One person showed up. [Read more about that first PyCon experience](/blog/pycon-2023-packaging-presentation-sprints-leah-wasser.html). This year, ~20 people showed up throughout the day. That arc — from a single contributor to a buzzing room of people at round tables — tells you everything about our community. - -
- - - A wide view of a busy conference room during the pyOpenSci sprint at PyCon US 2026 in Long Beach. In the foreground, three sprinters smile and work at a round table with open laptops while many more groups fill the room in the background. - -
- -If you're curious about how we run sprints, our [PyCon US 2024 sprint recap](/blog/pyopensci-pyconus-2024-sprints.html) walks through beginner-friendly setup, issue tagging, and supporting newer GitHub users. -{: .notice--success } - -## Beginner-friendly, always - -pyOpenSci sprints are intentionally beginner-friendly and welcoming. We get a mix of people. Including those who are newer to sprinting, a little nervous, maybe unsure if they belong. We also get seasoned contributors who are looking for a way to give back to the community. That mix is exactly who we want in the room. We thrive on seeing people come in unsure and leaving with confidence, new skills, and a new sense of belonging, even if it's just a little bit more. Our strength is in meeting people where they are and giving them the skills, support, and confidence to make their first open source contributions in a safe, welcoming space. - -But also our content is technical; so it's important to have experts to review, and validate contributions, contribute new lessons and educational content updates and to help support newer contributors. And what is so special about those experts is they just show up and dive in and know how to help others in a way that is welcoming and supportive. - - -
- - - Several PyCon US 2026 attendees work together around a crowded round table during the pyOpenSci sprint. Laptops show code editors and project pages, with purple conference lanyards, charging cables, water bottles, and coffee cups spread across the table. - -
- -## What keeps us going - -One of the most meaningful things about running sprints year after year is watching people come back to join us. We have sprinters who showed up for the first time at a previous event — maybe SciPy or another PyCon US meeting. They might have been nervous, or just finding their footing, working through their first pull request. Then they return with more confidence and skills they'd built in the time between. Watching that transformation happen over multiple sprints, seeing people go from uncertain beginners to contributors who can hit the ground running, is everything. - -That's pyOpenSci. That's why we exist. - -## Building community through collaboration - -This year we also saw something wonderful: sprinters connecting with each other and deciding together what to work on. One collaboration that emerged was a translation project — contributors working together to make our [Python packaging guide](https://www.pyopensci.org/python-package-guide/) available in Portuguese. - -Starting a Portuguese translation of our packaging guide means our resources will reach an entirely new audience of scientists and researchers over time. This translation work started because two people showed up to a sprint, connected, and decided to work together to make our resources more accessible. And that wouldn't have been possible if one person, Felipe, hadn't contributed the translation infrastructure to our guide back at a sprint in 2024! - -What an incredible full circle moment! - -
- - - At the pyOpenSci translation table during PyCon US 2026 in Long Beach, Felipe Moreno leans over a laptop to help sprinters add Portuguese translations to the packaging guide while other volunteers work at the same round table. - -
The translation table at PyCon US 2026. Felipe Moreno (in the middle) set up the translation infrastructure in 2024 at a sprint. Here Felipe is helping sprinters add Portuguese translations to our packaging guide! Felipe is both a pyOpenSci maintainer and one of the many long-time pyOpenSci contributors and volunteers who help make our supportive and welcoming sprints possible. This is the heart of pyOpenSci.
-
- -## Co-sprinting with Hatch - -This year we decided to share a room with [Hatch](https://hatch.pypa.io/), one of our favorite Python packaging projects, and its maintainer [Cary Hawkins](https://github.com/cjames23). Having a co-sprint with a project we genuinely love and recommend brought even more energy and expertise into the room. This got me thinking about how we might be able to support projects that we love more through future sprints. - -
- - - Two PyCon US 2026 sprinters sit side by side at a round table, focused on their laptops during the pyOpenSci sprint. pyOpenSci stickers, power cables, and drinks are scattered on the table while other groups work at tables across the conference hall behind them. - -
- -## What's next - -Sprint day is my favorite day of any conference — even when I'm exhausted, even when the week has been long. There is nothing like watching people make their first contributions, find their confidence, and connect with each other over shared work. For me that connection fills my soul as it encapsulates the true spirit of open source and the heart of pyOpenSci. - -Our next sprint will be at [EuroPython](https://europython.eu/) in Poland in July — a joint sprint with EuroPython and EuroSciPy. I'll also be keynoting there, so I hope to see many of you in the room. - -If you've been thinking about sprinting with us for the first time: come. We'll be there, and so will a community that genuinely wants to see you succeed. - ---- - -*This is Part 3 of a 4-part series on pyOpenSci at PyCon US 2026. [Part 1](/blog/pycon-us-2026-community-connection.html): community and connection · [Part 2: Maintainer Summit](/blog/pycon-us-2026-maintainers-summit.html) · [Part 4: generative AI and open source](/blog/pycon-us-2026-generative-ai-open-source.html).* diff --git a/_posts/2026-06-08-pycon-us-2026-generative-ai-open-source.md b/_posts/2026-06-08-pycon-us-2026-generative-ai-open-source.md deleted file mode 100644 index 5eb43c4c..00000000 --- a/_posts/2026-06-08-pycon-us-2026-generative-ai-open-source.md +++ /dev/null @@ -1,211 +0,0 @@ ---- -layout: single -title: "Generative AI and open source: what PyCon US 2026 taught us about the path forward" -excerpt: "Generative AI was a dominant theme at PyCon US 2026 — not as a buzzword, but as a lived reality the Python community is actively wrestling with. From our pyOpenSci open space to the Maintainers Summit BoF to Amanda Casari's powerful closing keynote, the path forward remains human." -author: "Leah Wasser" -permalink: /blog/pycon-us-2026-generative-ai-open-source.html -blog_image: images/blog/2026/pycon-us-2026-amanda-casari-keynote.png -blog_image_alt: "Amanda Casari delivers the closing keynote at PyCon US 2026 beside a slide that reads Disposable Code Is Here to Stay, but Durable Code Is What Runs the World, attributed to Charity Majors." -blog_topic: community -header: - overlay_image: images/blog/headers/pycon-us-2026-header.png -categories: - - blog-post - - community -classes: wide -toc: true -comments: true -last_modified: 2026-06-05 ---- - -*This post is Part 4 of a 4-part series on pyOpenSci at [PyCon US 2026](https://us.pycon.org/2026/). [Part 1](/blog/pycon-us-2026-community-connection.html) covers community and connection. [Part 2](/blog/pycon-us-2026-maintainers-summit.html) covers the Maintainer Summit. [Part 3](/blog/pycon-us-2026-sprints.html) covers the sprints.* - -******* - -## Generative AI and open source: what the community is wrestling with - -Generative AI was a dominant theme at PyCon US 2026 — prominent enough -to have its own dedicated conference track for the first time. But for -pyOpenSci, the conversation wasn't about machine learning applications -or what AI can build. It was about something more immediate: **what -generative AI tools are doing to open source communities themselves.** - -How are they changing who contributes? Who reviews? Who stays? What -does it mean for the humans who maintain these projects — often -voluntarily, often at the edges of burnout — when the barrier to -submitting code drops to near zero while the cost of reviewing it keeps -climbing? - -*At pyOpenSci, these questions have been front of mind for a few years — shaping our [peer review policy for AI-assisted submissions](https://www.pyopensci.org/software-peer-review/our-process/policies.html#policy-for-use-of-generative-ai-llms) and our course [Ship It: Python Packaging in the Generative AI Era](/blog/ship-it-cohort-one.html). PyCon US 2026 gave us a chance to bring that work into the broader conversation.* - -## Where we heard it: AI conversations across PyCon US 2026 - -AI conversations showed up everywhere — hallways, structured open spaces, and the keynote stage. - -**On day one, pyOpenSci hosted an open space** around a simple but hard question: *What can we do together to ease some of the pain points around generative AI in open source?* Small groups formed around specific tensions and came back with themes that will feel familiar to anyone in this space. - -
- - - Attendees gather around a round table during the pyOpenSci generative AI open space at PyCon US 2026, laptops open as one participant gestures while leading a small-group discussion. - -
Small-group discussion at the pyOpenSci generative AI open space on day one of PyCon US 2026.
-
- -**At the Maintainers Summit on Saturday,** pyOpenSci co-hosted a Generative AI BoF with [Jackie Kazil](https://github.com/jackiekazil), giving us a rare snapshot of how maintainers are actually using and feeling about these tools. Taken together, these conversations painted a remarkably consistent picture. - -## What the community shared - -Nearly two thirds of our event participants maintain projects with four or fewer -active co-maintainers. Over 80% are using generative AI tools — across -chat, IDEs, and the command line — for everything from generating code to -rubber ducking through problems. Most find them useful, but few called them -*very* useful. That pragmatism, and the frustrations underneath it, shaped -everything that follows. **Most people that we talk to use AI tools regularly.** - -
- - - Attendees sit together around a round table at PyCon US 2026, leaning in during a focused small-group conversation about generative AI and open source. - -
Maintainers and contributors working through generative AI questions together at PyCon US 2026.
-
- -## What we keep hearing: themes across the community - -These aren't new concerns — but PyCon US 2026 made clear they are intensifying. -One pattern worth naming upfront: usage is high across the board. Course participants -tend to be excited — energized by what the tools make possible. Maintainers are more -measured. They're using the same tools, but they're sitting on the other side of the -pull request. That difference in vantage point shapes everything that follows. - -### The maintainer burden is growing - -The barrier to submitting an open source contribution has dropped dramatically. The -barrier to reviewing one has not. More PRs, more issues, more noise — but the same -number of hours in the day. AI-generated code can look right while being wrong in ways -that take real expertise to catch: - -{% include pyos-blockquote.html quote="It's like a genius who's also a toddler — which is hard to explain to the people who want us to rely on it completely." author="PyCon US 2026 Maintainers Summit BoF attendee" class="highlight" %} - -Even course participants — people actively learning and enthusiastic about these tools -— are already noticing the gap: - -{% include pyos-blockquote.html quote="They are helpful and increase efficiency, but they make it harder to fully understand the code." author="Ship It: Python Packaging in the Generative AI Era participant" class="highlight" %} - -The review burden isn't just a workload problem. It's a trust problem. Reviewers used -to assume a pull request came from someone who wanted to learn and contribute. That -assumption no longer holds. Projects are asking hard questions about how to verify -human intent, but no consensus approach has emerged. - -### The contributor pipeline is at risk - -Open source has always had an informal mentorship model — small issues, feedback, -mistakes, gradual responsibility. That slow, messy process is how contributors become -maintainers. AI tools are short-circuiting that ramp. When a new contributor can -generate a plausible-looking pull request without deeply engaging with the codebase, -they skip the hard learning that builds judgment and intuition: - -{% include pyos-blockquote.html quote="It's made growing long-term contributors very hard." author="PyCon US 2026 Maintainers Summit BoF attendee" class="highlight" %} - -Our course participants are sensing this from the other side: - -{% include pyos-blockquote.html quote="Very helpful for prototyping and learning, but not for scaling production — still need a senior software engineer to make good judgement." author="Ship It: Python Packaging in the Generative AI Era participant" class="highlight" %} - -{% include pyos-blockquote.html quote="[AI tools are] Somewhere in between — they can be super helpful but I think they can fool people into thinking they don't need to understand the fundamentals." author="Ship It: Python Packaging in the Generative AI Era participant" class="highlight" %} - -There's a paradox here: the people who most need AI tools to bridge skill gaps are -often the least equipped to critically evaluate their outputs. That requires deep -knowledge of the codebase — exactly what newer contributors are still building. - -### AI policy shapes community culture - -Projects are taking wildly different stances — outright bans, permissive approaches, -everything in between. But policies aren't just legal governance. The policy you -create is a signal about what kind of community you're building. - -The [pip-tools policy discussion](https://github.com/jazzband/pip-tools/discussions/2278) -offered one of the clearest framings we heard all week: **"Don't let AI speak for you."** -A ban selects for contributors who want to engage deeply with the craft of developing software. A permissive -policy may attract volume but sacrifice quality code. Neither choice is inherently right or wrong — but the -choice determines the type of community that forms around your project. Every project is making that choice, whether -explicitly or not. - -### The power asymmetry no one is talking about - -Companies are requiring engineers to use these tools, setting up token consumption -leaderboards, tying job security to AI output volume. Meanwhile the individual -maintainer — often unpaid, often solo — absorbs the downstream costs: more -AI-generated PRs to review, more hallucinated issues to triage, no budget for tooling -to help manage it. - -{% include pyos-blockquote.html quote="Cost / access asymmetry between megacorporations with infinite token budgets and individuals without them." author="PyCon US 2026 Maintainers Summit BoF attendee" class="highlight" %} - -This asymmetry isn't just economic. It's a power dynamic reshaping who open source works for. - -### The ethics and sustainability conversation has gone quiet - -A few years ago, conversations about AI in open source were full of harder questions — -copyright, consent, environmental cost. Legal and licensing questions remain genuinely -unresolved: what is the copyright status of AI-generated code? How do licenses and attribution -interact with AI-assisted contributions? - -{% include pyos-blockquote.html quote="Copyright violations — something we were all talking about before, but not much any longer..." author="PyCon US 2026 Maintainers Summit BoF attendee" class="highlight" %} - -Those conversations haven't been resolved. They've just been crowded out by urgency -and pragmatism. The fact that we stopped having them doesn't mean the questions -were answered. - -## The human thread: Amanda Casari's keynote: a note on hope - -All of these conversations — the frustrations, the policy debates, the -pipeline worries — could easily leave you feeling like the ground is -shifting in ways that are hard to stand on. And for a lot of -maintainers, it is. - -But PyCon US 2026 didn't end on that note. - -The conference closed with one of the most moving keynote addresses -I've witnessed — Amanda Casari speaking about resilience, hope, and -what it means to be human in an era of rapid technological change. - -The message wasn't naive optimism. It was a recognition that yes, this -is hard, and yes, people are burnt out — and also that the path forward -is a human one. Connection, care, and community are not features that -any AI tool can replicate. They are the thing. - -
- - - Amanda Casari delivers the closing keynote at PyCon US 2026 beside a slide that reads Disposable Code Is Here to Stay, but Durable Code Is What Runs the World, attributed to Charity Majors. - -
Amanda Casari's closing keynote at PyCon US 2026, citing Charity Majors: "Disposable code is here to stay, but durable code is what runs the world."
-
- -That framing landed differently after a week of conversations about -reviewer burden and contributor pipelines and token consumption -leaderboards. It was a reminder that the reason any of this matters is -the people — the ones showing up, often voluntarily, often quietly, to -maintain the tools that the rest of the world depends on. - -## Wrapping it all up (but not with a pretty bow just yet) - -AI is a tool. A powerful one, with real implications for how open -source works. But the humans — the ones showing up, building trust, -passing knowledge on — that's the irreplaceable part. - -The conversations at PyCon US this year gave me genuine hope. Not because -we solved anything, but because the community is engaging with these -questions seriously, honestly, and together. That's how hard things -get figured out. - -I'll be bringing these conversations to the UN Open Source Summit, -EuroPython (where I'll be keynoting), EuroSciPy, and RSECon in -September. I hope to see many of you along the way. - ---- - -*This is Part 4 of a 4-part series on pyOpenSci at PyCon US 2026. -[Part 1: community and connection](/blog/pycon-us-2026-community-connection.html) · -[Part 2: Maintainer Summit](/blog/pycon-us-2026-maintainers-summit.html) · -[Part 3: pyOpenSci sprints](/blog/pycon-us-2026-sprints.html)* diff --git a/_posts/events/2024-04-29-create-your-first-python-package-pyopensci-online-workshop.md b/_posts/events/2024-04-29-create-your-first-python-package-pyopensci-online-workshop.md deleted file mode 100644 index 411db6f4..00000000 --- a/_posts/events/2024-04-29-create-your-first-python-package-pyopensci-online-workshop.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -layout: single -title: "pyOpenSci Online Workshop: Create your first Python package" -excerpt: "Python packaging can be tricky to navigate. pyOpenSci will lead a pilot workshop on creating your first Python package. The goal of this workshop is to help scientists learn how to package and make code installable and shareable. Read on to learn more!" -event: - start_date: 2024-04-29 - cost: 10$ - location: Online - event_type: training -permalink: /events/april-2024-create-python-package-pyopensci-online-workshop.html -header: - overlay_image: images/headers/pyopensci-sustainability.png -categories: - - events -start_date: 2024-04-29 -classes: wide -type: "event" -comments: true ---- - -## Create your first Python package: make your Python code easier to share and use - -* **What:** A hands-on workshop, titled: [Create Your First Python Package: Make Your Python Code Easier to Share and Use](https://cfp.scipy.org/2024/talk/QT9GBY/) -* **Where:** [SciPy 2024](https://www.scipy2024.scipy.org/), Ballroom B/C -* **When:** Tuesday, July 9th, 2024, from 13:30--17:30 Pacific -* Resources: - * [**Workshop GitHub repository:**](https://github.com/pyOpenSci/code-to-module-workshop/) - * [**pyOpenSci demo package:**](https://github.com/pyOpenSci/pyosPackage) - -## Event overview - -## How to enroll -
- - A line art robot standing in a field of purple flowers. The text reads 'From Python Code to Module, a live, online workshop with pyOpenSci, Thursday, April 25th 2024'. - -
- Tickets for our "From Python Code to Module" workshop are selling fast - get yours today! -
-
- -If you’re interested in participating in our first paid, online, real-time training, sign up for our upcoming workshop: [“From Python Code to Module”](https://www.eventbrite.com/e/879586546037?aff=oddtdtcreator). This three hour course is intended for individuals who have experience writing Python code and Python functions, and will be taught by pyOpenSci’s Executive Director and founder, [Leah Wasser](https://github.com/lwasser). Leah has over 20 years of experience teaching data-intensive science with an emphasis helping scientists work through the pain points of working with different types of data, and puts an incredible amount of care and attention into ensuring each learner is successful in their educational goals. This is definitely a workshop you don’t want to miss! - -In this workshop, Leah will cover: - -- How to identify and explain the use of the basic components of a Python package: (a specific directory structure, an __init__.py file, a pyroject.toml file and some code). -- Creating a basic python package that allows you to install your code into a local Python environment. -- Installing your package in editable mode into a Python environment. - -She will also briefly discuss how LLM’s can be used to support tasks such as documenting and formatting your code to improve usability and maintainability. While also considering the ethical and logistical challenges, pitfalls and concerns associated with using AI-based tools in software development. - -The course will take place on Thursday, April 25th, from 10AM–1PM Mountain time, and use the Spatial Chat platform. Tickets are $10, and can be purchased on [the workshop’s Eventbrite page](https://www.eventbrite.com/e/from-python-code-to-module-tickets-879586546037?aff=oddtdtcreator). The class size is capped at 35, and tickets are selling fast–we hope to see you there! - -## Connect with us! -You can stay up-to-date with all things pyOpenSci by following us on [LinkedIn](https://www.linkedin.com/company/pyopensci) and [Fosstodon](https://fosstodon.org/@pyOpenSci). And if you’re interested in our weekly newsletter where we share news, blog posts, and monthly updates, [subscribe on LinkedIn](https://www.linkedin.com/newsletters/pyopensci-newsletter-7179551305344933888!) diff --git a/_posts/events/2024-06-25-create-your-first-python-package-scipy-2024.md b/_posts/events/2024-06-25-create-your-first-python-package-scipy-2024.md deleted file mode 100644 index 3432bafe..00000000 --- a/_posts/events/2024-06-25-create-your-first-python-package-scipy-2024.md +++ /dev/null @@ -1,285 +0,0 @@ ---- -layout: single -title: "Workshop: pyOpenSci @ SciPy 2024--Create your first Python package" -excerpt: "Python packaging can be tricky to navigate. pyOpenSci will lead a workshop on creating your first Python package at SciPy 2024. The goal of this workshop is to help scientists learn how to package and make code installable and shareable. Read on to learn more!" -author: -permalink: /events/create-first-python-package-scipy-2024.html -header: - overlay_image: images/headers/scipy-2024-workshop.png -categories: - - events -event: - start_date: 2024-07-13 - cost: Registration through the scipy meeting - location: Online - event_type: training -classes: wide -type: "event" -comments: true ---- - -## Create your first Python package: make your Python code easier to share and use - -* **What:** A hands-on workshop, titled: [Create Your First Python Package: Make Your Python Code Easier to Share and Use](https://cfp.scipy.org/2024/talk/QT9GBY/) -* **Where:** [SciPy 2024](https://www.scipy2024.scipy.org/), Ballroom B/C -* **When:** Tuesday, July 9th, 2024, from 13:30--17:30 Pacific -* Resources: - * [**Workshop GitHub repository:**](https://github.com/pyOpenSci/code-to-module-workshop/) - * [**pyOpenSci demo package:**](https://github.com/pyOpenSci/pyosPackage) - -## Event overview - -Creating code that can be shared and reused is the gold-standard of open science. But the tools and skills to share your code can be tricky to learn. In this hands-on tutorial, you’ll learn how to turn your code into an installable Python module (a file containing Python code that defines functions, classes, and variables), that can be shared with others. We will provide pre-built code and module examples for each step of the tutorial to help make it more beginner-friendly, but you will need some basic Python skills to get the most out of this session. - -You will leave this tutorial understanding how to: - -* Create code that can be installed into different environments -* Use [Hatch](https://hatch.pypa.io/latest/) as a workflow tool, making setup and installation of your code easier -* Use Hatch to publish your package to the test version of [PyPI](https://pypi.org/) - -## Pre-requisites: What you need to know - -* Basic Python programming -* How to write functions in Python -* How to use a Python environment manager of your choosing - -## What you should have installed on your computer prior to the workshop - -* Python -* An environment manager -* Hatch -* Shell (e.g. terminal on MAC or GitBash on Windows) - -[ Click here for setup instructions.](https://github.com/pyOpenSci/code-to-module-workshop/tree/cf9e7d9937c10bde9ee03075ebe78eb7a605f549){: .btn .btn--info .btn--large} - -## Workshop agenda - - - - - - - - - - - - - - - - - - - - - -
Hour one: The structure of an installable module.
Key takeaways
    -
  • The purpose of the __init__.py file
  • -
  • How workflow tools such as Hatch can be useful when making code installable.
  • -
Breakdown
0-15 minutes: Here we will get to know each other. I’ll also briefly introduce pyOpenSci and the work that we are doing in open science education and training space.
15-30 minutes, interactive discussion: Here, we’ll discuss why shareable code is important. And we’ll explore some best practices for making code easier to work with. I’ll also introduce Hatch as a workflow tool that streamlines tasks.
30-60 minutes, hands-on: You will take an existing script and turn it into an installable module. You are welcome to use the provided scripts for this. If you are more comfortable with Python, then you can also bring your own script with you and work on it during the workshop.
-
- - - - - - - - - - - - - - - - - -
Hour two: Everything you need to know about the pyproject.toml file & project metadata.
Breakdown
0-15 minutes, interactive discussion: Here you’ll learn about the pyproject.toml and how it’s used to document dependencies, and metadata for your project.
15-30 minutes: A short break to stretch your legs and get a drink.
30-60 minutes, hands-on: In the hands-on part of this hour, you will modify your pyproject.toml file with required dependencies needed to run your code. You will also learn how to install your code in interactive or development mode using both pip and Hatch. Interactive mode will allow you to dynamically update your code and test it locally without reinstalling it. Finally, you will take your shiny new Python module for a test drive in your favorite Python environment.
-
- - - - - - - - - - - - - - - - - -
Hour three: The power of metadata and instructions for you, your future self & your colleagues.
Breakdown
0-15 minutes, interactive discussion: In this part of the tutorial we’ll discuss the power of documentation when sharing code and also for you when you have to update things in the future.
15-50 minutes, hands-on: Here you will create a README file that helps users of your module understand how to install it and how to get started using it. You will also add docstrings to your code. See how docstrings are useful as “hints” when coding real time. Optional: if you are speedy, you can also delve into typing your code on your own. However, we won’t directly cover typing in this tutorial.
50-60 minutes: Break
-
- - - - - - - - - - - - - - - - - -
Hour four: Publishing and sharing your code.
Breakdown
0-15 minutes, interactive discussion: Here we will discuss what it means to “publish” code. We will also discuss other important elements such as license files and codes of conduct if you intend to turn your code into a published package.
15-45 minutes, hands-on: Publishing to PyPI vs. installing from Github. Those who’d like to follow along interactively can do so here. However, if your brain is tired, sit back and learn how to build your module into a package distribution using Hatch. And then we will give you all of the tools needed to publish to the test version of PyPI.
45-60 minutes: Wrap up, answer any questions, and provide feedback on the session.
- -## Hatch & Python - -If you already have a working version of Python on your computer, then you are in good shape! **If you don’t have Python installed on your computer, then Hatch will install Python for you when you install it following the instructions below.** - -## Install Hatch - -_These instructions were adapted from the [Introduction to Hatch](https://www.pyopensci.org/python-package-guide/tutorials/get-to-know-hatch.html) section of the [pyOpenSci Python Packaging Guide](https://www.pyopensci.org/python-package-guide/)._ - -### For Mac users - -_These instructions are for installing Hatch using the GUI installer. If you’d prefer to use the command line installer, please see the [Hatch documentation](https://hatch.pypa.io/latest/install/#command-line-installer)._ - -1. In your browser, download the `.pkg` file: [hatch-universal.pkg](https://github.com/pypa/hatch/releases/latest/download/hatch-universal.pkg) -2. Run the downloaded file and follow the on-screen instructions to install Hatch. -3. Restart your terminal if it is already open. -4. To verify that shell can find and run the `hatch` command, run: - 1. `hatch --version` (in your Terminal / shell). - -### For Linux users - -_For Linux users, the easiest way to install Hatch is to use pipx which can be installed using apt install. Note: if you prefer to use a tool other than pipx, please refer to the [Hatch documentation](https://hatch.pypa.io/latest/) for more information_ - -* Install Hatch from the command line using [pipx](https://pipx.pypa.io/stable/): - -```bash -# First install pipx using apt install ->> apt install pipx -# Then use pipx to install hatch ->> pipx install hatch -``` - -### For Windows users - -_These instructions are for installing Hatch using the GUI installer. If you’d prefer to use the command line installer, please see the [Hatch documentation](https://hatch.pypa.io/latest/install/#command-line-installer_1)._ - -1. In your browser, download the `.msi` file: [hatch-x64.msi](https://github.com/pypa/hatch/releases/latest/download/hatch-x64.msi) -2. Run your downloaded file and follow the on-screen instructions. -3. Restart your terminal if it was already open. -4. To verify that the shell can find and run the `hatch` command in your `PATH`, in your terminal run: - 1. `hatch --version` - -### Configure Hatch (all systems) - -After installing Hatch, it’s useful to customize the Hatch configuration. The -configuration allows you to specify things like the default name and email to -use in your package’s metadata. If you don’t configure Hatch, you can always -edit files later! However your Hatch package outputs might look a bit different -than the ones in the workshop. (This is OK!) - -Hatch stores your configuration information in a `config.toml` file. - -While you can update the `config.toml` file through the command line, it might -be easier to look at it and update it in a text editor if you are using it for -the first time. - -1. Open and edit your `config.toml` file by either: - 1. Running `hatch config explore` in your shell, which will open up a directory window that will allow you to double click on the file and open it in your favorite text editor. - 2. Alternatively, you can retrieve the location of the Hatch config file by running `hatch config find` in your shell. -2. Update your email and name - 3. Once the file is open, update the [template] table of the `config.toml` file with your name and email. This information will be used in any `pyproject.toml` metadata files that you create using Hatch. -3. Set tests to `false` - - _While tests are important, setting the tests configuration in Hatch to true will create a more complex pyproject.toml file. We won’t be creating tests in this workshop._ - - Set tests to `false` in the `[template.plugins.default]` table. - -Your config file should look something like this: - -```toml -mode = "local" -project = "" -shell = "" - -[dirs] -project = [] -python = "isolated" -data = "/Users/leahawasser/Library/Application Support/hatch" -cache = "/Users/leahawasser/Library/Caches/hatch" - -[dirs.env] - -[projects] - -[publish.index] -repo = "main" - -[template] -name = "Leah Wasser" -email = "leah@pyopensci.org" - -[template.licenses] -headers = true -default = [ - "MIT", -] - -[template.plugins.default] -tests = false -ci = false -src-layout = true - -[terminal.styles] -info = "bold" -success = "bold cyan" -error = "bold red" -warning = "bold yellow" -waiting = "bold magenta" -debug = "bold" -spinner = "simpleDotsScrolling" -``` - -Note: for future packages you may want to enable both CI and tests. This -configuration is to simplify things for our beginner-friendly tutorial. - -4. Close the config file and run `hatch config show` - - `hatch config show` - -This command prints out the contents of your config.toml file in your shell. -Look at the values and ensure that your name and email are set and also make -sure that `tests=false`. - -## Useful Commands - -### Conda environments - -* **Create environment:** `conda create -n env_name python=3.11` -* **Activate environment:** `conda activate env_name` -* **Leave environment:** `conda deactivate` - -### Venv environments - -Create environment - -* `python -m venv env_name` -* Activate_windows: `env_name\Scripts\activate` -* Activate MAC / LINUX: `source env_name/bin/activate` -* Leave environment: `deactivate` - -## Helpful links - -* Our [example Python package repo, `pyosPackage`](https://github.com/pyOpenSci/pyosPackage), that goes along with pyOpenSci tutorials. -* [Workshop information on the SciPy 2024 website](https://cfp.scipy.org/2024/talk/QT9GBY/). - -## Connect with pyOpenSci - -Stay up-to-date with all things pyOpenSci by following us on [LinkedIn](https://linkedin.com/company/pyopensci) and [Fosstodon](https://fosstodon.org/@pyOpenSci). If you’re interested in our weekly newsletter where we share news, blog posts, and monthly updates, [subscribe on LinkedIn](https://www.bit.ly/pyOSNewsletter). diff --git a/_posts/events/2024-08-09-pyopensci-fall-festival.md b/_posts/events/2024-08-09-pyopensci-fall-festival.md deleted file mode 100644 index 3c795270..00000000 --- a/_posts/events/2024-08-09-pyopensci-fall-festival.md +++ /dev/null @@ -1,256 +0,0 @@ ---- -layout: single -title: "pyOpenSci Fall Festival 2024" -excerpt: "Join us for pyOpenSci's inaugural Fall Festival, happening October 28 - November 1, 2024! This multi-day training event brings together scientists and Pythonistas for inspiring talks, hands-on workshops, and office hours to help you grow your open science and open source skills. Whether you're a new or experienced Pythonista, there's something for you!" -author: -event: - start_date: "2024-10-28" - end_date: "2024-11-01" - location: Online using Spatial Chat - event_type: community -permalink: /events/pyopensci-2024-fall-festival.html -header: - overlay_image: images/headers/pyopensci-sustainability.png -categories: - - events -classes: wide -type: "event" -last_modified: 2024-09-26 -comments: true ---- - -## Announcing the pyOpenSci Fall Festival! - -*This post was last updated on {{ page.last_modified | date: "%B %d, %Y" }}* - -Happening October 28--November 1, 2024, the pyOpenSci Fall Festival is an online community training and networking event designed to empower scientists with in-demand open science skills. Our goals for the Fall Festival are to: - -* empower you with technically-relevant open science skills, -* call attention to and celebrate new and upcoming tools that support open reproducible science -* build community, and -* help raise funds for pyOpenSci operations that allow us to continue supporting community work. - -Interested? Read on to learn more! - -## pyOpenSci's 2024 Fall Festival logistics: - -* **What:** An online community training and networking event -* **Where:** Online using SpatialChat -* **When:** Monday, October 28--Friday, November 01, 2024 -* **Who:** If you are a scientist, a researcher, a student, or anyone interested in writing better, cleaner code that can be installed into different environments and shared, then this event is for you! -* **Cost - Day 1 Keynote talks:** free. -* **Cost- workshops:** $350--$625, with scholarships available - -## pyOpenSci's Fall Festival registration, cost, and scholarship information - -
- -[--> Click Here to Register now for the pyOpenSci Open Science Fall Festival <--](https://www.eventbrite.com/e/pyopensci-fall-festival-2024-registration-1000762657047){: .btn .btn--success .btn--large } -
- -**Important:** The agenda items in EventBrite are listed in UTC-6:00- This is an EventBrite bug. If you need clarification on start times, please email us at [media@pyopensci.org](mailto:media@pyopensci.org). -{: .notice .notice--primary } - -### Pricing tiers - -We've created several pricing tiers to make the Fall Festival accessible to as many people as possible. - -#### Day one - October 28, 2024: Keynote talks - -Keynote talks are free for anyone to attend. Register now before we fill up! - -| Time | Speaker | Topic | -|----------|----------|----------| -| 8:30am - 8:40 AM UTC-6 | Leah Wasser | Meeting room opens | -| 8:40am - 8:55 AM UTC-6 | Leah Wasser | Welcome to the Fall Festival - pyOpenSci, open source and open science | -| 9:00am - 9:50 AM UTC-6 | Eric Mah | Open Science, biomedical work and LLMs | -| 10:00am - 10:50 AM UTC-6 | Melissa Mendoça | The value of open source for open science | -| 11:00am - 11:50 PM UTC-6 | Rowan Crocket | Catalyzing how scientists publish using MystMarkdown | - - -#### Days 2-4 - 29 October - November 1, 2024: Technical Workshops - -The general schedule for each days' event is below: - -| Time | Event | -|----------|----------|----------| -| 8:45am - 1:00 PM UTC-6 | Online Training | -| 2:00pm - 4:00 PM UTC-6 | Online Office Hours | - -Friday's event will begin at 8:30 to support 2 speakers. - -You can register for a single-day workshop or the full event. Workshop registration includes office hours each day for more one-on-one help in the afternoon. - -| Registration Type | Cost | -|----------|----------| -| Day One Keynote Talks - October 28 2024 | Free | | -| Full 4-day Workshop: Student | $350 | | -| Full 4-day Workshop: Standard | $475 | | -| Full 4-day Workshop: Corporate | $625 | | -| | | | -| Single Day Workshop: Student | $100 | | -| Single Day Workshop: Standard | $150 | | -| Single Day Workshop: Corporate | $200 | | - - -### Scholarships - -If you are financially unable to attend pyOpenSci's Fall Festival, you can apply for a scholarship using [this form](https://www.surveymonkey.com/r/pyOSFF2024). We will be accepting scholarship applications through October 24th, but review will begin on October 15, with priority given to early applications. - -[Click to apply for a scholarship.](https://www.surveymonkey.com/r/pyOSFF2024){: .btn .btn--success } - -## pyOpenSci Fall Festival events - -Our Fall Festival is a series of events that make it easy to connect with others, listen to inspiring talks, and learn at a pace that is comfortable for you. - -### Monday, October 28th: Kickoff and keynotes - -Join us on Monday, October 28, for a morning of impactful keynote talks from 3 incredible speakers. - -These Fall Festival keynote talks are free for anyone to attend. They will be recorded and published on the pyOpenSci YouTube channel after the event. - -Our speakers for the Fall Festival are: - -* [Eric Ma](https://ericmjl.github.io/) -* [Melissa Mendoça](https://github.com/melissawm) -* [Rowan Cockett](https://curvenote.com/mission) - -We'll also hold "Day 0" office hours for all registered workshop attendees. Drop into our Day 0 office hours to say hello, get used to using our online platform, Spatial Chat, and get help with any computer setup issues you might have before the workshops begin. - -We want to set you up for learning success! - -#### Keynote speaker bios - -**[Eric Ma](https://ericmjl.github.io/)** -As Senior Principal Data Scientist at [Moderna](https://www.modernatx.com/en-US) Eric leads the Data Science and Artificial Intelligence (Research) team to accelerate science to the speed of thought. Prior to Moderna, he was at the [Novartis Institutes for Biomedical Research](https://www.novartis.com/research-and-development) conducting biomedical data science research with a focus on using Bayesian statistical methods in the service of discovering medicines for patients. Prior to Novartis, he was an Insight Health Data Fellow in the summer of 2017 and defended his doctoral [thesis](https://ericmjl.github.io/thesis/) in the [Department of Biological Engineering](https://be.mit.edu/) at [MIT](https://web.mit.edu/) in the spring of 2017. - -Eric is also an open-source software developer and has led the development of [pyjanitor](https://github.com/pyjanitor-devs/pyjanitor), a clean API for cleaning data in Python, and [nxviz](https://github.com/ericmjl/nxviz), a visualization package for NetworkX. He is also on the core developer team of NetworkX and PyMC. In addition, he gives back to the community through [code contributions](https://ericmjl.github.io/open-source/), [blogging](https://ericmjl.github.io/blog/), [teaching](https://ericmjl.github.io/teaching/), and [writing](https://ericmjl.github.io/books/). - -His personal life motto is found in the Gospel of Luke 12:48. - -**[Melissa Mendoça](https://github.com/melissawm)** -I am a Senior DevEx Engineer at Quansight, working on NumPy, SciPy and other open source projects. I care deeply about teaching, mentoring, and have been involved in the Python community for some time. You can find most of my talk slides here on github or in [my website](https://melissawm.github.io/); feel free to use those according to the licenses stated in each repo/presentation. - -💬 Ask me about [NumPy](https://numpy.org/), SciPy, [napari](https://napari.org/stable/), Fortran, LaTeX, mathematical optimization, numerical linear algebra, [Contributor Experience](https://contributor-experience.org/). - -**[Rowan Cockett](https://curvenote.com/mission)** - -Rowan is the CEO and founder of [Curvenote](https://curvenote.com), where we build tools to free science from static PDF documents such that the scientific community can share more interactive, reproducible, and richly-linked scientific content. Curvenote provides an all-in-one publishing platform for researchers, societies and institutes, with a focus on computational research. - -Rowan is also on the steering-council for JupyterBook and MyST Markdown, which is part of Project Jupyter and provides widely used open-source tools for authoring and sharing scientific content. Rowan has a Ph.D. in computational geophysics from the University of British Columbia (UBC). While at UBC, Rowan helped start [SimPEG](https://simpeg.xyz), a large-scale simulation and parameter estimation package for geophysical processes (electromagnetics, fluid-flow, gravity, etc.), which is used in industry, national labs, and universities globally. - -Rowan has won multiple awards for innovative dissemination of research and open-educational resources, including a geoscience modeling application, Visible Geology, that has been used by more than a million geoscience students to interactively explore conceptual geologic models. In his previous role as the VP of Cloud Architecture at Seequent, Rowan ran a large software team working on computational software platforms, visualization tools, and version control systems for geoscientists. - -### Tuesday, October 29th--Friday, November 01st - Technical workshops - -Every workshop day of the pyOpenSci Fall Festival will follow a similar format. - -*Some days will kick off with an inspirational and technically related talk* - -All days will include: -* A hands-on, interactive workshop with highly-qualified instructors and a group of knowledgeable, dedicated support volunteers. -* A lengthy lunch break -* Optional afternoon office hours, where registered attendees can get further help and support with the topics covered during the morning workshop - -## pyOpenSci's Fall Festival workshop agendas - -### Tuesday, October 29th: Write modular, clean code - -When developing a data processing workflow, it's tempting to start at the "top" and write each line of code needed to process your data. Or you may ask a LLM to write your code for you. However, this approach often makes maintaining code less efficient and complex. In this workshop, you will learn how to think about developing the code needed to process your data more efficiently. - -You will learn how to: - -1. Describe and organize the steps needed to create your workflow using pseudocode. Pseudocode is not only helpful in organizing your workflow; it is also what an AI-coding assistant tool such as ChatGPT or GitHub CoPilot requires to build a workflow for you. -1. Create expressive, human-readable variable and function names to make your code easier to read and maintain. -1. Create well-documented functions to perform repeated tasks. Functions allow you to make your code more modular and reduce the number of variables your code produces and stores in memory. LLMs can be helpful here, too, once you understand what you need to accomplish. -1. Add tests and checks to your function to ensure your workflow runs as expected and handles messy data issues gracefully. -1. Organize your workflow into modules, functions, and scripts that run and process your data. - -This workshop will help you transform messy, hard-to-manage code into clean, efficient, and reusable Python workflows. Over 3-4 hours, you'll learn the core concepts of refactoring your code so that it's easier to understand, maintain, and share with others. We'll start by looking at how to break down repetitive tasks into smaller, reusable functions or objects. Refactoring will make your code more organized and save you time in the long run by reducing the amount of repetitive work. - -Next, we'll focus on writing code that's functional and easy to read. Using clear, descriptive names and well-structured logic, you'll learn to write code that others (and your future self) can easily understand and modify. We'll also cover how to manage your code's use of memory and compute power, ensuring that your scripts run efficiently. Once the core concepts are understood, you will use LLM's to make your code more modular and easier to read and maintain. You will add functions needed to process your data reusable into a new Python module. - -By the end of the workshop, you'll have refactored your code into a well-organized module. This module will then serve as the foundation for the second workshop, where you'll learn how to turn it into a fully-fledged Python package that you can install and reuse across different projects and in different Python environments. This step-by-step approach will equip you with the skills to create robust, maintainable Python workflows for processing, visualizing, and analyzing data. - -### Wednesday, October 30th: Create a Python package - -**Summary** - -In the second Fall Festival workshop, you'll learn how to turn a Python module and script into an installable package. Optionally, you'll also learn how to add a script to a Python package so you can call it at the command line using Python package entry points. We will provide a module and script from Workshop 1 that you can use to complete Workshop 2. - -Packaging your code makes sharing, reproducing, and reusing your work easier—a fundamental element of open science. Packaging your Python code ensures it can be easily reused across different environments and workflows, whether locally, in the cloud, or when collaborating with others. Packaged code simplifies your work by allowing you to reuse your code in various projects. It also enhances your ability to share your tools with the broader scientific community, making your contributions more accessible and impactful. - -In the first workshop of the pyOpenSci Fall Festival, you learned how to turn a Python script into a maintainable command-line script that imports modular functionality from a well-written Python module. You can use the work you did in workshop 1 in this workshop. -{: .notice .notice--info } - -In this workshop, you'll learn how to use the pyproject.toml file, a modern and straightforward way to define your package's metadata, dependencies, and setup instructions. We'll also introduce Hatch, a powerful tool that simplifies the packaging process, ensuring you can install your package into any Python environment with a single command. By the end of the workshop, you'll have the skills to transform your code into a Python package ready for distribution and use by others. - -This workshop will help you make your code more accessible and reproducible. You'll leave with a fully functional Python package created from your own module. Whether you're new to Python packaging or looking to refine your skills, this workshop will equip you with the tools and knowledge to distribute your scientific code efficiently and effectively. - -### Thursday, October 31st: Share your code - -**Summary** - -In Workshop 3: share your code, you will learn the essential steps to make your Python packages publicly available and easily installable. You'll start by setting up your package on GitHub, enabling others to `pip install` it directly. You'll also learn how to integrate Zenodo to assign a DOI to your package, allowing it to be cited in academic works, with easy updates for each new release. If there is time, the workshop will guide you through creating package releases, setting up GitHub Actions to automate the publication process to PyPI, and using Test PyPI with Hatch to ensure everything works smoothly before going live. Finally, you will learn how to publish your package to (test) PyPI and conda-forge as a stepping stone to publishing to the real PyPI. While the workshop will introduce the process of creating a Conda-Forge recipe, the actual publication to Conda-forge will be something you can explore further on your own - -By the end of this workshop, you know how to share your Python packages with the broader community effectively. You'll understand how to automate critical aspects of the release process, making it easier to maintain and update your packages over time. Overall, you'll leave this workshop empowered to contribute your code to the open-source ecosystem in a way that is accessible, citable, and well-maintained. - -### Friday, November 1: Reproducible reports and presentations with Quarto and Great Tables - -#### Morning talks - -We'll be opening this session with two incredible talks on Quarto from [James Balamuta](https://github.com/coatless) and [George Stagg](https://gws.phd/)! - -#### Workshop - -If you want to use data to make decisions, answer scientific questions, inform people on issues or participate in data-driven journalism, just conducting the data analysis is not enough. Effective communication requires weaving together narrative text and code to produce elegantly formatted output that people can easily read and understand. In this workshop, you'll learn how to use [Quarto](https://quarto.org/) for reports and presentations and Great Tables for elegantly formatted tables to convey information that's great for the readers, and easy for you to create too. -Quarto is an open source tool based on Pandoc that allows you to create and publish reproducible, production-quality articles, presentations, dashboards, websites, blogs, and books in HTML, PDF, MS Word, ePub, and more, right from your Jupyter notebooks. - -With Great Tables you can make wonderful-looking tables in Python. Great Tables is an open source Python package that lets you mix and match things like a header and footer, attach a stub (which contains row labels), arrange spanner labels over top of the column labels, and much more. Not only that, but you can format the cell values in a variety of awesome ways. - -## Speakers and instructors - -*We'll continue to update this section with more information as we confirm speakers and instructors for the event!* - -* [Leah Wasser](https://www.leahwasser.com/) -* [Eric Ma](https://ericmjl.github.io/) -* [Melissa Mendoça](https://github.com/melissawm) -* [Rowan Cockett](https://curvenote.com/blog/myst-markdown-pyopensci-2024) -* [Carol Willing](https://www.willingconsulting.com/) -* [Jeremiah Paige](https://github.com/ucodery) -* [James Balamuta](https://github.com/coatless) -* [George Stagg](https://gws.phd/) -* [Tracy Teal](https://tkteal.com/) -* [Rich Iannone](https://github.com/rich-iannone) - -## Volunteers - -Are you interested in volunteering? Please contact [fallfestival@pyopensci.org](mailto:fallfestival@pyopensci.org) to stay up to date about any event needs! - -## FAQ - -**How do I apply for a scholarship?** - -If you are financially unable to attend pyOpenSci's Fall Festival, you can apply for a scholarship using [this form](https://www.surveymonkey.com/r/pyOSFF2024). We will be accepting scholarship applications through October 24th, but review will begin on October 15, with priority given to early applications. - -**Can I register for a single day?** - -Yes! You can register for the entire event or buy tickets for one, two, or three days. To stay up to date on any changes to our ticketing for the pyOpenSci Fall Festival, be sure to follow us on [Fosstodon](https://fosstodon.org/@pyOpenSci) or [LinkedIn](https://linkedin.com/company/pyopensci). - -**What if I purchased a ticket but can no longer attend?** - -All refunds are processed through Eventbrite, and will be accepted until October 21, 2024. You will receive a refund for the price of your ticket minus any service fees. - -If you purchased your ticket after October 21, 2024 you will be ineligible for a refund, however you may donate it to another learner. To do so, please email [fallfestival@pyopensci.org](mailto:fallfestival@pyopensci.org). - -**How will pyOpenSci use my registration information?** - -pyOpenSci will use your registration information to send you information related to the Fall Festival, as well as to send you both pre- and post-surveys related to the Fall Festival. - -**How can I help promote the Fall Festival?** - -We would love for you to share your excitement and enthusiasm with your peers, both in-person and on social media! You're welcome to use any of the images that we've shared on social media related to the event. We'd also love it if you tag us in anything that you share! - -**Will the Fall Festival workshops be recorded?** - -We will only record the keynote talks on Monday, October 28, 2024. The additional talks and workshops will not be recorded. If workshop instructors share their content online, we will be sure to share the relevant links both here and on social media. diff --git a/_posts/events/2025-04-22-community-call-contribute-open-source.md b/_posts/events/2025-04-22-community-call-contribute-open-source.md deleted file mode 100644 index 7f78edef..00000000 --- a/_posts/events/2025-04-22-community-call-contribute-open-source.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -layout: single -title: "pyOpenSci Community Call 5 May 2025 @ 10 am Mountain Time: Right Time, Right Community: My OSS Journey" -excerpt: "Join pyOpenSci for a special Open Source Voices session! Learn about Pavithra's open source journey and what helped her grow into a contributor and maintainer in the open source world." -author: "pyopensci" -event: - start_date: "2025-05-05" - location: Online - event_type: community -permalink: /events/pyopensci-community-call-may-2025.html -header: - overlay_image: images/headers/pyopensci-sustainability.png -categories: - - events -classes: wide -type: "event" -last_modified: 2025-04-22 -comments: true ---- - -## Join us for a talk on contributing to open source! -Open Source Voices: *A community call for learning, sharing, and growing together* - -* **When:** Monday, 5 May 2025 @ 10:00-10:50 AM Mountain Time [(click this link to find the time and date in your timezone)](https://www.timeanddate.com/worldclock/fixedtime.html?msg=Open+Source+Voices%3A+pyOpenSci+Contribute+Week&iso=20250505T10&p1=75&ah=1) -* **Where:** Online via Google Meet (Register for link) -* **Speaker:** Pavithra Eswaramoorthy, Developer Advocate, Quansight - -[ Register here](https://bit.ly/pyos-contribute){: .btn .btn--success .text-center } - -
- - - An image of Pavithra wearing a dark shirt and looking to the side. She has dark curly hair and is smiling. - -
- -We’re kicking off the week with a special Open Source Voices session! Pavithra will share her open source journey and what helped her grow in the open source world. - -### Right Time, Right Community: My OSS Journey - -Pavithra Eswaramoorthy is a Developer Advocate at Quansight, where she works to improve the developer experience and community engagement for several open source projects in the PyData community. She has been involved in the open source community for over five years. Currently, she is part of the core team for the Bokeh visualization library and Nebari (JupyterHub-based data science platform). In her spare time, she enjoys a good book and hot coffee. - -## All are welcome - -We welcome anyone and everyone to join and learn more about contributing to open source. This is a space for learning, questions, and community. Come and listen, get inspired, and meet others learning about open source. diff --git a/_posts/events/2025-06-24-scipy25-bof.md b/_posts/events/2025-06-24-scipy25-bof.md deleted file mode 100644 index d347ef72..00000000 --- a/_posts/events/2025-06-24-scipy25-bof.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -layout: single -title: "SciPy 2025: Open Code, Open Science: What’s Getting in Your Way?" -excerpt: "Join us for an interactive discussion on pain points that you are experiencing with developing and maintaining reusable software and code." -author: "pyopensci" -event: - start_date: "2025-07-10" - location: Online - event_type: community -permalink: /events/pyopensci-scipy25-bof-packaging-challenges.html -header: - overlay_image: images/headers/pyopensci-sustainability.png -categories: - - events -classes: wide -type: "event" -last_modified: 2025-07-03 -comments: true ---- - -## Learn how to create your first Python package! - -* **When:** Thursday, 10 July 2025 @ 1:15–2:10 PM Pacific Time -* **Where:** Room 315 @ SciPy Meeting 2025 in Tacoma, Washington -* **Session leaders:** - * Leah Wasser - * Inessa Pawson - * Carol Willing - * Jeremiah Paige - * Tetsuo Koyama - * Avik Basu - - - -[ View our session abstract](https://cfp.scipy.org/scipy2025/talk/3ZHYMH/){: .btn .btn--success .text-center } - -Collaborating on code and software is essential to open science—but it’s not always easy. Join this BoF for an interactive discussion on the real-world challenges of open source collaboration. We’ll explore common hurdles like Python packaging, contributing to existing codebases, and emerging issues around LLM-assisted development and AI-generated software contributions. - -We’ll kick off with a brief overview of pyOpenSci—an inclusive community of Pythonistas, from novices to experts—working to make it easier to create, find, share, and contribute to reusable code. We’ll then facilitate small-group discussions and use an interactive Mentimeter survey to help you share your experiences and ideas. - -Your feedback will directly shape pyOpenSci’s priorities for the coming year, as we build new programs and resources to support your work in the Python scientific ecosystem. Whether you’re just starting out or a seasoned developer, you’ll leave with clear ways to get involved and make an impact on the broader Python ecosystem in service of advancing scientific discovery. diff --git a/_posts/events/2025-06-24-scipy25-create-python-package.md b/_posts/events/2025-06-24-scipy25-create-python-package.md deleted file mode 100644 index 9fd076ef..00000000 --- a/_posts/events/2025-06-24-scipy25-create-python-package.md +++ /dev/null @@ -1,178 +0,0 @@ ---- -layout: single -title: "Create Your First Python Package: SciPy 2025" -excerpt: "Learn how to create your first Python package using the pyOpenSci template." -author: "pyopensci" -event: - start_date: "2025-07-08" - location: Online - event_type: training -permalink: /events/pyopensci-scipy25-create-python-package-workshop.html -header: - overlay_image: images/headers/pyopensci-sustainability.png -categories: - - events -classes: wide -type: "event" -last_modified: 2025-04-22 -comments: true ---- - -## Learn how to create your first Python package! - -* **When:** Tuesday, 8 July 2025 @ 8:00–12:00 PM Pacific Time -* **Where:** SciPy Meeting 2025 in Tacoma, Washington -* **Instructors:** - * Leah Wasser - * Carol Willing - * Jeremiah Paige - * Tetsuo Koyama - -[ View our workshop abstract](https://cfp.scipy.org/scipy2025/talk/Z3VBWR/){: .btn .btn--success .text-center } - - -## Setup instructions - -You can choose to work on your own machine during the workshop or use the GitHub Codespace we've set up for you. Codespaces include all the tools needed to create your first package—preinstalled and ready to go. - -We encourage you to use GitHub Codespaces! If you prefer to install things locally, please do so **before** the workshop. - - -### 1. Make sure you have a GitHub and Test PyPI account - -Before the workshop, please create and log into the following accounts: - -1. [Create a free GitHub account](https://www.github.com) if you don’t already have one. -2. [Create a free account on Test PyPI](https://test.pypi.org/account/register) — we’ll use this to practice publishing packages. - - -> **Important:** To follow along when we publish to Test PyPI, you must enable **two-factor authentication (2FA)** on your Test PyPI account. We strongly recommend doing this **before** the workshop! -{: .notice .notice-important} - -## 2a. Setup for working locally on your laptop - -We recommend using GitHub Codespaces (see next section) to avoid issues with environment setup. However, if you prefer to work locally, please ensure your environment is set up and ready before the workshop. - -If you're comfortable with Python environments, arrive with an environment that has: - -- [Hatch installed](https://www.pyopensci.org/python-package-guide/tutorials/get-to-know-hatch.html#install-hatch) -- [Copier installed](https://copier.readthedocs.io/en/stable/#installation) - - You can install both via `pipx`, `uv`, pixi, or any preferred package manager. -- [VS Code](https://code.visualstudio.com/download) (or any IDE you prefer for packaging) - -> **Note:** -> * We strongly suggest that you go to GitHub.com and [Fork our SciPy 2025 workshop repository](https://github.com/pyOpenSci/pyopensci-scipy25-create-python-package) just in case something with your local installation isn't working. This will ensure that you can follow along with us regardless of your environment setup! -> * JupyterLab or Jupyter Notebook is **not ideal** for the packaging work we’ll be doing. -{: .notice } - - -## 2b. If you plan to use GitHub Codespaces (SUGGESTED) - -We strongly recommend using our curated [GitHub Codespace](https://codespaces.new/pyOpenSci/ff-2024-create-python-package?quickstart=1), which can be found in your forked workshop repository. This workspace is already set up with: - -* VSCode, copier, Hatch, and everything you need to create your first Python package successfully. Codespaces also allows you to easily commit your work during the workshop to your own forked GitHub repository. - -**Before the workshop do the following** - -* Go to GitHub.com and [Fork our SciPy 2025 workshop repository](https://github.com/pyOpenSci/pyopensci-scipy25-create-python-package) - -
- - . - -
- -* Launch the codespace before the workshop: - - The initial spin-up for a codespace can take up to 15 minutes or longer. Once GitHub creates the cloud environment, it will be quicker to relaunch. Please do this before attending the workshop session. -{: .notice } - -To open your Codespace, - -* Go to your forked SciPy 2025 workshop repository on GitHub, -* Switch to a branch that you want to work on during the workshop, -* Click the code drop-down button (where you'd find a link to clone a repo) -* Click on the Codespaces tab in the drop-down. - -Within the Codespaces tab you will see a button: -Create codespace on main -. This button launches a Codespace from the currently active branch. It will default to the -`main` branch if you haven't changed your branch in the repository. - -The animated gif below walks you through how this works. - -
- - - Create codespace on main button - -
- -> To follow GitHub best practices, always create a new branch before making -> changes to a repository, even if you own the repository. Avoid working directly on the `main` branch. - -## About GitHub Codespaces: Working in the cloud - -## What is a codespace - -GitHub Codespaces are cloud-based development environments that let you -code directly in your browser—no local setup needed. They provide fully -configured, container-based environments connected to your GitHub -repository. - -You can customize Codespaces with `.devcontainer` files to match your -development setup. The Codespace for this workshop opens in Visual Studio -Code by default, but you can configure it to use any IDE you prefer. - -Learn more in the [GitHub Codespaces docs](https://docs.github.com/en/codespaces/overview). - -Codespaces are free for up to 60 hours/month on GitHub’s free plan. Be sure -to shut yours down after the workshop to conserve your allocated time. -{: .notice} - -> Codespaces are associated with your **personal GitHub account**, even if -you open one from the pyOpenSci repository. The usage counts toward your -own GitHub credits. -{: .notice } - - -### View & manage open codespaces - -Once you've opened a Codespace, you can return to it later without needing -to start from scratch. GitHub will reuse the environment, making it launch -significantly faster the second time. - -If possible, resume the **same Codespace** you started in, rather than -creating a new one in your fork. Read on to learn how to find and reopen a codespace. - - -[View all active Codespaces associated with your account](https://github.com/codespaces) - -{: .notice} -> When you delete a Codespace, you're -> ending the session, but the configuration remains available so you can -> launch a new one anytime. -> [Learn more in the GitHub docs](https://docs.github.com/en/codespaces/developing-in-a-codespace/stopping-and-starting-a-codespace) - - - - -### How to relaunch a codespace - -To relaunch a Codespace: - -1. Go to your fork of the workshop repository. -2. Click the **Code** dropdown (same as when you first created the Codespace). -3. Select the **Codespaces** tab to view existing environments. -4. Select the codespace that you want to resume from the list (see image below). - -As you can see in the animated gif below, GitHub codespaces always have fun names. - -
- - GIF showing how to reopen a GitHub Codespace - -
diff --git a/_posts/events/2025-06-24-stanford-ospo-peer-review-deep-dive.md b/_posts/events/2025-06-24-stanford-ospo-peer-review-deep-dive.md deleted file mode 100644 index 1280d81f..00000000 --- a/_posts/events/2025-06-24-stanford-ospo-peer-review-deep-dive.md +++ /dev/null @@ -1,71 +0,0 @@ ---- -layout: single -title: "pyOpenSci: Improving research software and code through open and accessible peer review" -excerpt: "Discover how peer review improves research software quality and reproducibility. Hosted for the community within Stanford’s Open Source Program Office, this deep dive introduces pyOpenSci’s review process and ways for researchers to get involved and grow their open source and leadership skills through participating in software peer review." -author: "pyopensci" -event: - start_date: "2025-08-07" - location: Online - event_type: talk -permalink: /events/pyopensci-stanford-ospo-peer-review.html -header: - overlay_image: images/headers/pyopensci-sustainability.png -categories: - - events -classes: wide -type: "event" -last_modified: 2025-07-03 -comments: true ---- - -## pyOpenSci: Improving research software and code through open and accessible peer review - -* **When:** Thursday, 7 August @ 11:00 AM Mountain Time -* **Where:** Zoom: this event is for the Stanford Open Source Community - -This event, hosted in partnership with [Stanford’s Open Source Program -Office](https://opensource.stanford.edu/), explores how [software peer review](https://www.pyopensci.org/about-peer-review/index.html) can help you build more reliable research tools, improve your coding and software development skills, and gain academic recognition while also connecting with a global community of practice that is dedicated to supporting the broader research and scientific Python community build better research software. - -You’ll learn how pyOpenSci’s community-driven review -process supports open, sustainable Python software—and how to get -involved as a contributor or reviewer. - -Peer review improves software quality, documentation, and long-term -sustainability—and gives contributors academic recognition through our -partnership with the [Journal of Open Source Software (JOSS)]( -https://www.pyopensci.org/software-peer-review/partners/joss.html). - -The process is grounded in our [community-developed packaging -guidelines](https://www.pyopensci.org/python-package-guide/index.html) -tailored to scientific software. - -Whether you're building tools or reviewing them, you're helping shape a -more open, collaborative, and reproducible future for science. Join us -to improve research software—and get recognized for doing it. - -### Why attend - -- Learn what peer review - looks like for research software -- Improve your own coding practices by reviewing - real-world tools -- See how others structure code, write - tests, and document their work -- Gain visibility and academic credit for - contributions -- Take a low-barrier first step into the - open-source research ecosystem - - -### Building bridges: The power of our Stanford partnership - -Through our partnership with Stanford, we're building a bridge between -the University Open Source Program Offices and the global open-source community that powers scientific -Python. Researchers and students gain access to high-quality, community-based -training in research software practices—without Stanford needing to create -that infrastructure from scratch. - -This collaboration supports Stanford’s leadership in open, reproducible -science while giving its researchers meaningful opportunities to engage with -the pyOpenSci community and contribute to a broader, inclusive ecosystem of -scientific software development. diff --git a/_posts/events/2025-09-02-stanford-ospo-how-to-create-a-python-package-workshop.md b/_posts/events/2025-09-02-stanford-ospo-how-to-create-a-python-package-workshop.md deleted file mode 100644 index f317fca0..00000000 --- a/_posts/events/2025-09-02-stanford-ospo-how-to-create-a-python-package-workshop.md +++ /dev/null @@ -1,170 +0,0 @@ ---- -layout: single -title: "pyOpenSci Workshop: Create a Python package -- Stanford Open Source Program Office" -excerpt: "Python packaging can be tricky to navigate. pyOpenSci will lead a pilot workshop on creating your first Python package. The goal of this workshop is to help scientists learn how to package and make code installable and shareable. Read on to learn more!" -author: "pyopensci" -event: - start_date: "2025-10-02" - location: Online - cost: This workshop is paid through Stanford's membership with pyOpenSci. - event_type: training -permalink: /events/pyopensci-stanford-create-python-package-workshop.html -header: - overlay_image: images/headers/pyopensci-sustainability.png -categories: - - events -classes: wide -type: "event" -last_modified: 2025-09-02 -comments: true ---- - -## Create a Python package: make your Python code easier to share and use - -* **When:** Thursday, 2 October @ 10:00 AM Mountain Time // 9:00AM Pacific Time -* **Where:** We will use the Spatial Chat platform to create a classroom-like learning experience. - -This event, hosted in partnership with [Stanford’s Open Source Program -Office](https://opensource.stanford.edu/), teaches participants best practices for packaging their Python code. You will learn how to build more reliable research tools and software development skills. - -## Setup instructions - -You can choose to work on your own machine during the workshop or use the GitHub Codespace we've set up for you. Codespaces include all the tools needed to create your first package—preinstalled and ready to go. - -We encourage you to use GitHub Codespaces! If you prefer to install things locally, please do so **before** the workshop. - -### 1. Make sure you have a GitHub and Test PyPI account - -Before the workshop, please create and log into the following accounts: - -1. [Create a free GitHub account](https://www.github.com) if you don’t already have one. -2. [Create a free account on Test PyPI](https://test.pypi.org/account/register) — we’ll use this to practice publishing packages. - - -> **Important:** To follow along when we publish to Test PyPI, you must enable **two-factor authentication (2FA)** on your Test PyPI account. We strongly recommend doing this **before** the workshop! -{: .notice .notice-important} - -## 2a. Setup for working locally on your laptop - -We recommend using GitHub Codespaces (see next section) to avoid issues with environment setup. However, if you prefer to work locally, please ensure your environment is set up and ready before the workshop. - -If you're comfortable with Python environments, arrive with an environment that has: - -- [Hatch installed](https://www.pyopensci.org/python-package-guide/tutorials/get-to-know-hatch.html#install-hatch) -- [Copier installed](https://copier.readthedocs.io/en/stable/#installation) - - You can install both via `pipx`, `uv`, pixi, or any preferred package manager. -- [VS Code](https://code.visualstudio.com/download) (or any IDE you prefer for packaging) - -> **Note:** -* We strongly suggest that you visit GitHub.com and [Fork our packaging workshop repository](https://github.com/pyOpenSci/pyopensci-workshop-create-python-package) in case any issues arise with your local installation. This will ensure that you can follow along with us regardless of your environment setup. -> * JupyterLab or Jupyter Notebook is **not ideal** for the packaging work we’ll be doing. -{: .notice } - - - -## 2b. If you plan to use GitHub Codespaces (SUGGESTED) - -We strongly recommend using our curated [GitHub Codespace](https://codespaces.new/pyOpenSci/pyopensci-workshop-create-python-package?quickstart=1), which can be found in your forked workshop repository. This workspace is already set up with: - -* VSCode, copier, Hatch, and everything you need to create your first Python package successfully. Codespaces also allows you to easily commit your work during the workshop to your own forked GitHub repository. - -**Before the workshop do the following** - -* Go to GitHub.com and [Fork our Python package workshop repository](https://github.com/pyOpenSci/pyopensci-workshop-create-python-package) - -
- - . - -
- -* Launch the codespace before the workshop: - - The initial spin-up for a codespace can take up to 15 minutes or longer. Once GitHub creates the cloud environment, it will be quicker to relaunch. Please do this before attending the workshop session. -{: .notice } - -To open your Codespace, - -* Go to your forked workshop repository on GitHub, -* Switch to a branch that you want to work on during the workshop, -* Click the code drop-down button (where you'd find a link to clone a repo) -* Click on the Codespaces tab in the drop-down. - -Within the Codespaces tab you will see a button: -Create codespace on main -. This button launches a Codespace from the currently active branch. It will default to the -`main` branch if you haven't changed your branch in the repository. - -The animated gif below walks you through how this works. - -
- - - Create codespace on main button - -
- -> To follow GitHub best practices, always create a new branch before making -> changes to a repository, even if you own the repository. Avoid working directly on the `main` branch. - -## About GitHub Codespaces: Working in the cloud - -## What is a codespace - -GitHub Codespaces are cloud-based development environments that let you -code directly in your browser—no local setup needed. They provide fully -configured, container-based environments connected to your GitHub -repository. - -You can customize Codespaces with `.devcontainer` files to match your -development setup. The Codespace for this workshop opens in Visual Studio -Code by default, but you can configure it to use any IDE you prefer. - -Learn more in the [GitHub Codespaces docs](https://docs.github.com/en/codespaces/overview). - -Codespaces are free for up to 60 hours/month on GitHub’s free plan. Be sure -to shut yours down after the workshop to conserve your allocated time. -{: .notice} - -> Codespaces are associated with your **personal GitHub account**, even if -you open one from the pyOpenSci repository. The usage counts toward your -own GitHub credits. -{: .notice } - -### View & manage open codespaces - -Once you've opened a Codespace, you can return to it later without needing -to start from scratch. GitHub will reuse the environment, making it launch -significantly faster the second time. - -If possible, resume the **same Codespace** you started in, rather than -creating a new one in your fork. Read on to learn how to find and reopen a codespace. - - -[View all active Codespaces associated with your account](https://github.com/codespaces) - -{: .notice} -> When you delete a Codespace, you're -> ending the session, but the configuration remains available so you can -> launch a new one anytime. -> [Learn more in the GitHub docs](https://docs.github.com/en/codespaces/developing-in-a-codespace/stopping-and-starting-a-codespace) - -### How to relaunch a codespace - -To relaunch a Codespace: - -1. Go to your fork of the workshop repository. -2. Click the **Code** dropdown (same as when you first created the Codespace). -3. Select the **Codespaces** tab to view existing environments. -4. Select the codespace that you want to resume from the list (see image below). - -As you can see in the animated gif below, GitHub codespaces always have fun names. - -
- - GIF showing how to reopen a GitHub Codespace - -
diff --git a/_posts/events/2025-09-08-workshop-create-python-package-2hour.md b/_posts/events/2025-09-08-workshop-create-python-package-2hour.md deleted file mode 100644 index 4fd803f0..00000000 --- a/_posts/events/2025-09-08-workshop-create-python-package-2hour.md +++ /dev/null @@ -1,117 +0,0 @@ ---- -layout: single -title: "From Zero to Python Package: A 2-Hour Python Packaging Workshop" -excerpt: "Turn your Python code into a professional package that others can easily install, use, and contribute to. Learn the complete packaging workflow with hands-on demos and proven templates." -author: "pyopensci" -event: - start_date: "2025-11-06" - location: Online - event_type: training -permalink: /events/python-packaging-workshop-november-2025.html -header: - overlay_image: images/headers/pyopensci-sustainability.png -categories: - - events -classes: wide -type: "event" -last_modified: 2025-09-08 -comments: true ---- - -## Everything that you need to know - -* **When:** Thursday, 6 November 2025 10:00 AM Mountain Time - 12:15 PM Mountain Time -* **Where:** Interactive webinar with live demos - -### Cost - -Prices are in US Dollars. This workshop not only provides you with expert training, but it also supports the work that pyOpenSci does to support scientific open source. - -| Ticket Type |Early Bird (Before 5 November 2025 ) | Regular | -| Regular | $149 | $199 | -| Student | $45 | $85 | -| Partner Community (Astropy, PyHC, OpenAstronomy, Pangeo) | $85 | $85 | -| Partner Community Student (Astropy, PyHC, OpenAstronomy, Pangeo) | $40 | $40 | - - - [Register Now on Eventbrite](https://www.eventbrite.com/e/pyopensci-zero-to-pure-python-package-in-minutes-tickets-1677115350159) -{: .btn .btn--success } - -
-Scholarships are available to individuals who have been affected by job loss and funding cuts and who don't have the resources to attend our training. We will ensure that scholarship recipients represent a diverse range of voices, backgrounds, and experiences. Scholarships are also available to Undergraduate and Graduate level students. Scholarships will reduce the cost of the workshop to 20$ (Students) or 45$ (continuing education, academics and others). We offer a limited number of scholarships. Please be sure to explain why this workshop will benefit your work in Science and the greater open science community when you apply. - -[To apply for a scholarship, please fill out this Google form.](https://forms.gle/tnV3tj13cChQXLEH7) -
- -Your research code deserves to reach more people. In this hands-on workshop, you'll learn how to transform your Python scripts into professional packages that others can easily install, use, and contribute to. Walk away with a complete packaging workflow and the confidence to share your work with the world. - -This workshop is part of pyOpenSci's commitment to building better research software through accessible packaging practices. Whether you're sharing analysis tools with colleagues or building the next great Python library, you'll gain practical skills to package like a pro. - -### Workshop Structure - -#### Part 1: Modern Python packaging foundations (30 minutes) - -- **Package Structure:** Set up your project the right way from day one -- **Essential Files Walkthrough:** pyproject.toml, README, and what actually matters -- **Tool Decision Framework:** Navigate the packaging ecosystem with confidence - -#### Part 2: Build your Python package (30 minutes) - -- **Live Build Demo:** See packaging in action with real code -- **Setup Tests:** Make sure your package works everywhere and as new pull requests come in -- **Quality Checklist:** Professional packaging standards made simple - -#### Break (10 minutes) - -#### Part 3: Publish and share your Python package (30 minutes) - -- **PyPI Publishing:** Get your package on the Python Package Index -- **Security Best Practices:** Protect your package and users when you are publishing using Trusted Publishing. -- **Automation Setup:** Never manually release your package again; instead use GitHub actions with trusted publishing. - -#### Part 4: Documentation for a healthy project (20 minutes) - -- **What docs matter most?:** A brief overview of documentation to make it easier to onboard both contributors and new maintainers to your project. - - -#### Wrap up: Questions (20 Minutes) - -- **Q&A:** Get your questions answered - -### Why attend - -- Learn modern packaging practices used by successful Python projects -- Get production-ready templates and automation you can use immediately -- Understand publishing security best practices from day one -- See live demos of the complete packaging workflow -- Learn from pyOpenSci's experience reviewing dozens of packages - -### Perfect for you if... - -- You have Python code that others could benefit from using -- You're tired of sharing code via email or messy file transfers -- You want people to be able to install and use your tools -- You've heard of PyPI but don't know how to get your package there and how to do it securely. -- You want to better understand the complex Python packaging ecosystem - -### What you'll walk away with - -- **Complete Package Template:** Ready-to-use project structure -- **Automation Workflows:** GitHub Actions for testing and publishing -- **Quality Checklists:** Never miss important packaging steps -- **Publishing Guide:** Step-by-step instructions for secure PyPI deployment using GitHub actions - -### Pre-requisites - -To successfully follow along in this workshop, you should: - -* Know how to write Python code -* Understand how to write and use functions -* Have a Free account to create a GitHub account (you can work using GitHub codespaces during this workshop, and setting this account up is free if you don't already have one. -* You should have internet access - -### Your instructors & helpers - -* [**Leah Wasser**](https://www.github.com/lwasser), the Executive Director and Founder of pyOpenSci, brings over 20 years of experience teaching technical data science topics in the scientific space. pyOpenSci has worked with hundreds of scientific Python package maintainers and has helped over 50 packages improve their structure and distribution through our peer review process. -* [**Jeremiah Paige:**](https://www.github.com/ucodery) is an open source author, contributor, and speaker, specifically working with Python for over 12 years. He is also a software engineer in the secure supply chain industry during work hours. -* [**Avik Basu:**](https://github.com/ab93) is an open source contributor, speaker, and mentor. He is a seasoned machine learning engineer with extensive experience in both major tech companies and fast-moving startups. diff --git a/_posts/events/2026-03-03-course-python-packaging-gen-ai.md b/_posts/events/2026-03-03-course-python-packaging-gen-ai.md deleted file mode 100644 index 43488485..00000000 --- a/_posts/events/2026-03-03-course-python-packaging-gen-ai.md +++ /dev/null @@ -1,187 +0,0 @@ ---- -layout: splash -classes: flowing -title: "Ship It: Research Software Development in the GenAI Era" -excerpt: > - A trusted 10-day hands-on asynchronous cohort-based course developed by - pyOpenSci for Stanford University and the CURIOSS network. -event: - start_date: "2026-04-10" - end_date: "2026-04-23" - location: Online - event_type: training -permalink: /events/shipit-python-package-gen-ai-april-2026.html -header: - overlay_image: images/headers/pyopensci-sustainability.png -categories: - - events -type: "event" -last_modified: 2026-03-03 -comments: true -author_profile: false -intro: - - excerpt: "Transform your code into something others can easily install, - use, and trust." -instructors: - - name: Leah Wasser - github: lwasser - bio: > - The Executive Director and Founder of pyOpenSci, Leah brings over 20 - years of experience teaching technical data science topics in the - scientific space. pyOpenSci has worked with hundreds of scientific - Python package maintainers and has helped over 50 packages improve - their structure and distribution through our peer review process. - - name: Jeremiah Paige - github: ucodery - bio: > - An open source author, contributor, and speaker, specifically working - with Python for over 12 years. He is also a software engineer in the - secure supply chain industry during work hours. - - name: Inessa Pawson - github: inessaPawson - bio: Bio coming soon. ---- - -{% include feature_row id="intro" type="center" %} - -
- -
-## Everything you need to know - -* **When:** Friday, 10 April 2026 – Thursday, 23 April 2026 -* **Where:** Online asynchronous course with live office hours - -### Cost - -This course was paid for by the Stanford University Open Source -Program Office (OSPO) and is available to both Stanford OSPO -researchers and other OSPOs in the CURIOSS network. - -Learn more about our partnership with Stanford in this short video: -[Ship It with Stanford OSPO](https://www.youtube.com/watch?v=9HDAZJS_Wck) - -
- -## About the course -Learn directly from leaders in the open source space. Featuring hands-on -activities, expert interviews with Python core developers and scientific Python -maintainers, and live office hours where you can get help and ask questions, -this 10-day asynchronous course empowers university researchers and -practitioners to reliably build and share critical tools. - -Developed by pyOpenSci in partnership with the Stanford Open Source Program -Office (OSPO), this program delivers a trusted, expert-led curriculum that -guides you through the lifecycle of a modern Python package. You will evolve a -working script into a tested, documented, and automated package published on -PyPI. - -By participating in this course, you're also joining a vibrant open source -ecosystem. Expect to work with today’s industry-standard -tools—including uv, -Hatch, pytest, and Sphinx—while exploring open-source workflows and learning -how to use Generative AI critically, thoughtfully, and responsibly. - -## What you’ll learn - -* Structure a Python package from scratch. -* Configure your package using pyproject.toml. -* Build and publish your package to (test) PyPI. -* Write and run different types of tests using pytest. -* Automate your development workflows with task runners. -* Create the core documentation files every package needs. -* Use AI tools responsibly in your development workflow. - -This course is part of pyOpenSci’s **Ship It** curriculum — designed for -researchers, scientists, and developers who want to build sustainable, -production-ready open source software. - -## Who this course is for - -This course is designed for: - -* **Researchers, scientists, and postdocs** who write Python code and want to - share it -* **University faculty, staff, and students** looking to teach or adopt modern - Python packaging practices -* **Developers** who are new to open source packaging and want a structured, - guided path - -*No prior packaging experience is required.* If you are comfortable with Python -programming and can write a Python function, then you're ready for this course. - -## What makes this course different - -pyOpenSci's unique advantage is its community. You're not just watching videos; -you're learning alongside — and from — the people who maintain Python's -packaging ecosystem. - -The course is delivered in a flexible, async format designed for busy -researchers: each day you’ll discover 10–15 minutes of video content, and -then you’ll complete a short bite-sized activity. Use the weekend to catch up -if you fall behind. - -
- -
-
- -## Course Schedule at a Glance - -### Week 0: Setup - -The week before the course begins is focused on setup. You can complete the -course either using GitHub Codespaces or locally on your computer. - -### Week 1: Foundation & Structure - -* Day 1 — Create and - run your package -* Day 2 — Metadata and - dependencies: the pyproject.toml file -* Day 3 — Build and publish - your package -* Day 4 — Introduction to - testing -* Day 5 — Live office - hours: show up, ask questions, get help. - -### Week 2: Task runners, GenAI & Documentation - -* Day 6 — Automate your - workflows: task runners and environments -* Day 7 — GenAI to support - your workflows -* Day 8 — Documentation that - matters most to build your community and maintainer team -* Day 9 — Office - hours & wrap up - -
-
- -
- -### What you'll walk away with - -- **Complete Package Template:** Ready-to-use project structure -- **Automation Workflows:** GitHub Actions for testing and publishing -- **Quality Checklists:** Never miss important packaging steps -- **Publishing Guide:** Step-by-step instructions for secure PyPI deployment - using GitHub Actions - -### Pre-requisites - -To successfully follow along in this workshop, you should: - -* Know how to write Python code -* Understand how to write and use Python functions -* Have a free GitHub account. You can work using GitHub Codespaces during this - workshop, and setting up this account is free if you don't already have one. -* Have internet access to access the course materials and tools. - -### Your instructors & helpers - -{% include event_instructors.html %} - -
diff --git a/_posts/events/2026-05-16-pycon-us-maintainers-summit.md b/_posts/events/2026-05-16-pycon-us-maintainers-summit.md deleted file mode 100644 index 21045879..00000000 --- a/_posts/events/2026-05-16-pycon-us-maintainers-summit.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -layout: single -title: "PyCon US Maintainers Summit" -date: 2025-01-01 -excerpt: "Join pyOpenSci and the broader Python community for a day of talks and roundtables on sustainable open source maintainership." -event: - start_date: 2026-05-16 - end_date: 2026-05-16 - location: "Long Beach, CA" - event_type: community -permalink: /events/2026-pycon-us-maintainers-summit.html -header: - overlay_image: images/headers/pyopensci-sprints-2025.png -categories: - - events -classes: wide -type: "event" -comments: false ---- - -## PyCon US Maintainers Summit - -* **What:** Maintainers Summit at PyCon US 2026 -* **When:** Saturday, May 16, 2026 -* **Where:** Room 201A, Long Beach Convention & Entertainment Center, Long Beach, CA -* **More info:** [PyCon US Maintainers Summit page](https://us.pycon.org/2026/events/maintainers-summit/) - -The Maintainers Summit brings together maintainers and contributors across the Python ecosystem to share strategies for healthy, sustainable open source projects. The program includes talks, tools demos, and roundtable discussions. diff --git a/_posts/events/2026-05-17-pycon-us-development-sprint.md b/_posts/events/2026-05-17-pycon-us-development-sprint.md deleted file mode 100644 index e19aee9b..00000000 --- a/_posts/events/2026-05-17-pycon-us-development-sprint.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -layout: single -title: "PyCon US Development Sprint" -date: 2025-01-02 -excerpt: "Join pyOpenSci contributors for a one-day collaborative sprint focused on improving open science tooling, docs, and contributor workflows." -event: - start_date: 2026-05-17 - end_date: 2026-05-17 - location: "Long Beach, CA" - event_type: sprint -permalink: /events/2026-pycon-us-development-sprint.html -header: - overlay_image: images/headers/pyopensci-sprints-2025.png -categories: - - events -classes: wide -type: "event" -comments: false ---- - -## PyCon US Development Sprint - -* **What:** One-day collaborative sprint following the Maintainers Summit -* **When:** Sunday, May 17, 2026 -* **Where:** PyCon US Sprints area, Long Beach Convention & Entertainment Center, Long Beach, CA -* **More info:** [PyCon US sprints information](https://us.pycon.org/2026/events/dev-sprints/) - -This sprint day is dedicated to hands-on collaboration. Participants can contribute to packaging resources, documentation, peer review tooling, and community onboarding materials with support from maintainers and experienced contributors. diff --git a/_sass/minimal-mistakes.scss b/_sass/minimal-mistakes.scss deleted file mode 100644 index 512a7ff7..00000000 --- a/_sass/minimal-mistakes.scss +++ /dev/null @@ -1,58 +0,0 @@ -/*! - * Minimal Mistakes Jekyll Theme 4.24.0 by Michael Rose - * Copyright 2013-2020 Michael Rose - mademistakes.com | @mmistakes - * Licensed under MIT (https://github.com/mmistakes/minimal-mistakes/blob/master/LICENSE) -*/ - - - -/* Variables & fonts */ -@import "minimal-mistakes/variables"; -@import "minimal-mistakes/fonts-load"; - -/* Mixins and functions */ -@import "minimal-mistakes/vendor/breakpoint/breakpoint"; -@include breakpoint-set("to ems", true); -@import "minimal-mistakes/vendor/magnific-popup/magnific-popup"; // Magnific Popup -@import "minimal-mistakes/vendor/susy/susy"; -@import "minimal-mistakes/mixins"; - -/* Core CSS */ -@import "minimal-mistakes/reset"; -@import "minimal-mistakes/base"; -@import "minimal-mistakes/forms"; -@import "minimal-mistakes/tables"; -@import "minimal-mistakes/animations"; - -/* Components */ -@import "minimal-mistakes/buttons"; -@import "minimal-mistakes/notices"; -@import "minimal-mistakes/pyos-connect"; -@import "minimal-mistakes/masthead"; -@import "minimal-mistakes/navigation"; -@import "minimal-mistakes/footer"; -@import "minimal-mistakes/search"; -@import "minimal-mistakes/syntax"; - -/* Utility classes */ -@import "minimal-mistakes/utilities"; - -/* Layout specific */ -@import "minimal-mistakes/page"; -@import "minimal-mistakes/archive"; -@import "minimal-mistakes/sidebar"; -@import "minimal-mistakes/print"; - - -/* Pyos specific */ -@import "minimal-mistakes/pyos-functions"; -@import "minimal-mistakes/pyos-main"; -@import "minimal-mistakes/pyos-isotope"; -@import "minimal-mistakes/pyos-dropdown"; -@import "minimal-mistakes/pyos-twitter"; -@import "minimal-mistakes/pyos-grid"; -@import "minimal-mistakes/pyos-flowing-page"; -@import "minimal-mistakes/pyos-cards"; -@import "minimal-mistakes/pyos-blog-index"; -@import "minimal-mistakes/pyos-testimonial-showcase"; -@import "minimal-mistakes/cookie-consent"; diff --git a/_sass/minimal-mistakes/_animations.scss b/_sass/minimal-mistakes/_animations.scss deleted file mode 100644 index dcd8055e..00000000 --- a/_sass/minimal-mistakes/_animations.scss +++ /dev/null @@ -1,21 +0,0 @@ -/* ========================================================================== - ANIMATIONS - ========================================================================== */ - -@-webkit-keyframes intro { - 0% { - opacity: 0; - } - 100% { - opacity: 1; - } -} - -@keyframes intro { - 0% { - opacity: 0; - } - 100% { - opacity: 1; - } -} diff --git a/_sass/minimal-mistakes/_archive.scss b/_sass/minimal-mistakes/_archive.scss deleted file mode 100644 index 63504536..00000000 --- a/_sass/minimal-mistakes/_archive.scss +++ /dev/null @@ -1,514 +0,0 @@ -/* ========================================================================== - ARCHIVE - ========================================================================== */ - -.archive { - margin-top: 1em; - margin-bottom: 2em; - - @include breakpoint($large) { - float: right; - width: calc(100% - #{$right-sidebar-width-narrow}); - padding-right: $right-sidebar-width-narrow; - } - - @include breakpoint($x-large) { - width: calc(100% - #{$right-sidebar-width}); - padding-right: $right-sidebar-width; - } -} - -.archive__item { - position: relative; - - a { - position: relative; - z-index: 10; - } - - a[rel="permalink"] { - position: static; - } -} - -.archive__subtitle { - margin: 1.414em 0 0.5em; - padding-bottom: 0.5em; - font-size: $type-size-5; - color: $muted-text-color; - border-bottom: 1px solid $border-color; - - + .list__item .archive__item-title { - margin-top: 0.5em; - } -} - -.archive__item-title, h2.archive__item-title, h3.archive__item-title { - margin-bottom: 0.25em; - margin-top: 0!important; - overflow: hidden; - font-size: $type-size-card-title; - font-weight: $semibold-weight; - - a[rel="permalink"]::before { - content: ''; - position: absolute; - left: 0; - top: 0; - right: 0; - bottom: 0; - } - - a + a { - opacity: 0.5; - } -} - -/* remove border*/ -.page__content { - .archive__item-title { - border-bottom: none; - } -} - -.archive__item-excerpt { - margin-top: 0; - margin-bottom: 0; - font-size: $type-size-5; - - p { - max-width: 95%; - margin-top: 0; - margin-bottom: 0; - } - - & + p { - text-indent: 0; - - } - - a { - position: relative; - } -} - -// make the titles in archive blocks full width -h2.archive__item-title, -h3.archive__item-title { - max-width: 100%; -} - -.archive__item-teaser { - position: relative; - border-radius: $border-radius; - overflow: hidden; - - img { - width: 100%; - } -} - -.archive__item-caption { - position: absolute; - bottom: 0; - right: 0; - margin: 0 auto; - padding: 2px 5px; - color: #fff; - font-family: $caption-font-family; - font-size: $type-size-8; - background: #000; - text-align: right; - z-index: 5; - opacity: 0.5; - border-radius: $border-radius 0 0 0; - - @include breakpoint($large) { - padding: 5px 10px; - } - - a { - color: #fff; - text-decoration: none; - } -} - -/* - List view - ========================================================================== */ - -.list__item { - .page__meta { - margin: 0 0 4px; - font-size: 0.6em; - } -} - -/* - Grid view - ========================================================================== */ - -.archive { - .grid__wrapper { - /* extend grid elements to the right */ - - @include breakpoint($large) { - margin-right: -1 * $right-sidebar-width-narrow; - } - - @include breakpoint($x-large) { - margin-right: -1 * $right-sidebar-width; - } - } -} - -.grid__item { - margin-bottom: 2em; - - @include breakpoint($small) { - float: left; - width: span(5 of 10); - - &:nth-child(2n + 1) { - clear: both; - margin-left: 0; - } - - &:nth-child(2n + 2) { - clear: none; - margin-left: gutter(of 10); - } - } - - @include breakpoint($medium) { - margin-left: 0; /* override margin*/ - margin-right: 0; /* override margin*/ - width: span(3 of 12); - - &:nth-child(2n + 1) { - clear: none; - } - - &:nth-child(4n + 1) { - clear: both; - } - - &:nth-child(4n + 2) { - clear: none; - margin-left: gutter(1 of 12); - } - - &:nth-child(4n + 3) { - clear: none; - margin-left: gutter(1 of 12); - } - - &:nth-child(4n + 4) { - clear: none; - margin-left: gutter(1 of 12); - } - } - - .page__meta { - margin: 0 0 4px; - font-size: 0.6em; - } - - .page__meta-sep { - display: block; - - &::before { - display: none; - } - } - - .archive__item-title { - margin-top: 0.5em; - font-size: $type-size-card-title; - } - - .archive__item-excerpt { - display: none; - - @include breakpoint($medium) { - display: block; - font-size: $type-size-6; - } - } - - .archive__item-teaser { - @include breakpoint($small) { - max-height: 200px; - } - - @include breakpoint($medium) { - max-height: 120px; - } - } -} - -/* - Features - ========================================================================== */ - -.feature__wrapper { - @include clearfix(); - margin-bottom: 0.5em; - margin-top: 0.5em; - - // Further reduce spacing when followed by pyos-section - + .pyos-section { - margin-top: 0; - padding-top: 0; - } - - // Reduce spacing when it's the first element after header - .page__content > &:first-child { - margin-top: 0.5em; - } - - h2 { - max-width: 100%; - } - - .archive__item-title { - margin-bottom: 0; - } - - // Reduce spacing for centered feature items (excerpts) - .feature__item--center { - margin-top: 0; - margin-bottom: 0; - - .archive__item-excerpt { - margin-top: 0; - margin-bottom: 0; - } - } -} - -.feature__item { - position: relative; - margin-bottom: 2em; - font-size: 1.125em; - background-color: #fff; - - @include breakpoint($small) { - float: left; - margin-bottom: 0; - width: span(4 of 12); - - &:nth-child(3n + 1) { - clear: both; - margin-left: 0; - } - - &:nth-child(3n + 2) { - clear: none; - margin-left: gutter(of 12); - } - - &:nth-child(3n + 3) { - clear: none; - margin-left: gutter(of 12); - } - - .feature__item-teaser { - max-height: 200px; - overflow: hidden; - } - } - - .archive__item-body { - padding-left: gutter(1 of 12); - padding-right: gutter(1 of 12); - } - - a.btn::before { - content: ''; - position: absolute; - left: 0; - top: 0; - right: 0; - bottom: 0; - } - - &--left { - position: relative; - float: left; - margin-left: 0; - margin-right: 0; - width: 100%; - clear: both; - font-size: 1.125em; - - .archive__item { - float: left; - } - - .archive__item-teaser { - margin-bottom: 2em; - } - - a.btn::before { - content: ''; - position: absolute; - left: 0; - top: 0; - right: 0; - bottom: 0; - } - - @include breakpoint($small) { - .archive__item-teaser { - float: left; - width: span(5 of 12); - } - - .archive__item-body { - float: right; - padding-left: gutter(0.5 of 12); - padding-right: gutter(1 of 12); - width: span(7 of 12); - } - } - } - - &--right { - position: relative; - float: left; - margin-left: 0; - margin-right: 0; - width: 100%; - clear: both; - font-size: 1.125em; - - .archive__item { - float: left; - } - - .archive__item-teaser { - margin-bottom: 2em; - } - - a.btn::before { - content: ''; - position: absolute; - left: 0; - top: 0; - right: 0; - bottom: 0; - } - - @include breakpoint($small) { - text-align: left; - - .archive__item-teaser { - float: right; - width: span(5 of 12); - } - - .archive__item-body { - float: left; - width: span(7 of 12); - padding-left: gutter(0.5 of 12); - padding-right: gutter(1 of 12); - } - } - } - - &--center { - position: relative; - float: left; - margin-left: 0; - margin-right: 0; - width: 100%; - clear: both; - font-size: 1.125em; - margin-top: 0; - margin-bottom: 0; - - .archive__item { - float: left; - width: 100%; - margin: 0; - padding: 0; - } - - .archive__item-teaser { - margin-bottom: 2em; - } - - .archive__item-body { - margin-top: 0; - margin-bottom: 0; - padding-top: 0; - padding-bottom: 0; - } - - a.btn::before { - content: ''; - position: absolute; - left: 0; - top: 0; - right: 0; - bottom: 0; - } - - @include breakpoint($small) { - text-align: center; - - .archive__item-teaser { - margin: 0 auto; - width: span(5 of 12); - } - - .archive__item-body { - margin: 0 auto; - width: span(7 of 12); - } - } - } -} - -/* Place inside an archive layout */ - -.archive { - .feature__wrapper { - .archive__item-title { - margin-top: 0.25em; - font-size: 1em; - } - } - - .feature__item, - .feature__item--left, - .feature__item--center, - .feature__item--right { - font-size: 1em; - } -} - -/* - Wide Pages - ========================================================================== */ - - .wide { - .archive { - @include breakpoint($large) { - padding-right: 0; - } - - @include breakpoint($x-large) { - padding-right: 0; - } - } -} - -/* Place inside a single layout */ - -.layout--single { - .feature__wrapper { - display: inline-block; - } -} diff --git a/_sass/minimal-mistakes/_base.scss b/_sass/minimal-mistakes/_base.scss deleted file mode 100644 index d8104c71..00000000 --- a/_sass/minimal-mistakes/_base.scss +++ /dev/null @@ -1,472 +0,0 @@ -/* ========================================================================== - BASE ELEMENTS - ========================================================================== */ - - html { - /* Sticky footer fix */ - position: relative; - min-height: 100%; - } - - body { - margin: 0; - padding: 0; - color: $text-color; - font-family: $global-font-family; - line-height: 1.6; - font-weight: $weight-3; - font-size: 1rem; - - &.overflow--hidden { - /* when primary navigation is visible, the content in the background won't scroll */ - overflow: hidden; - } - } - - strong {font-weight:$xbold-weight} - ul li {margin-bottom: 0} - - h1, - h2, - h3, - h4, - h5, - h6 { - margin: 3.5rem 0 1rem 0; - line-height: 1.2; - color: #542668; - } - - // Use poppins for subheaders - h2, h3, h4, h5, h6 { - font-family: $header-font-family; - font-weight: $bold-weight; - letter-spacing: -.03em; - max-width: $medium; - } - - h1 { - margin-top: 0; - font-size: $h-size-1; - font-weight: 400 !important; - font-family: $title-font-family; - } - - h2 { - font-size: $h-size-2; - - &:first-of-type { - //margin-top: 1rem!important; - } - } - - h3 { - font-size: $h-size-3; - line-height: 1.1em; - margin-top: 2.7em; - } - - h4 { - font-size: $h-size-4; - } - - h5 { - font-size: $h-size-5; - } - - h6 { - font-size: $h-size-6; - } - - small, - .small { - font-size: $type-size-6; - } - - p { - margin-bottom: 1.3em; - } - - u, - ins { - text-decoration: none; - border-bottom: 1px solid $text-color; - a { - color: inherit; - } - } - - del a { - color: inherit; - } - - /* Font awesome */ - .fa-solid { - font-size: .7em; - } - - .benefits-list .fa-solid { - font-size: 1.1rem; - color: $pyos-teal; - margin-right: 0.4rem; - } - - /* reduce orphans and widows when printing */ - - p, - pre, - blockquote, - ul, - ol, - dl, - figure, - table, - fieldset { - orphans: 3; - widows: 3; - } - - /* abbreviations */ - - abbr[title], - abbr[data-original-title] { - text-decoration: none; - cursor: help; - border-bottom: 1px dotted $text-color; - } - - /* blockquotes */ - - blockquote { - margin: 2em 1em 2em 0; - padding-left: 1em; - padding-right: 1em; - font-style: italic; - border-left: 0.25em solid $primary-color; - - cite { - font-style: italic; - - &:before { - content: "\2014"; - padding-right: 5px; - } - } - } - - %quote-style { - font-weight: bold; - //font-size: $type-size-2; - color: $pyos-teal; - line-height: 0; - font-size: 5rem; - } - - /* Pull quote — pale green card with green accent bar */ - blockquote.highlight-quote { - border: none; - border-left: 4px solid $pyos-teal; - margin: 2rem 0; - padding: 1.25rem 1.5rem; - max-width: $large; - background-color: mix(#fff, $pyos-teal, 92%); - font-family: $body-font; - font-style: normal; - - p, - p.quote { - margin: 0 0 0.5rem; - font-family: $title-font-family; - font-size: $type-size-4; - font-weight: $semibold-weight; - line-height: 1.5; - color: $text-color; - } - - footer, - .highlight-quote__cite { - margin: 0; - text-align: left; - } - - cite { - display: block; - font-family: $body-font; - font-size: $type-size-6; - font-style: normal; - font-weight: $weight-4; - line-height: 1.4; - color: $muted-text-color; - - &:before { - content: "\2014"; - padding-right: 0.35rem; - } - } - - &.magenta { - border-left-color: $pyos-magenta; - } - - &.purple { - border-left-color: $pyos-mediumpurple; - } - - @include breakpoint('max-width: #$mobile') { - padding: 1rem 1.25rem; - } - - /* wider variant for landing page hero quotes */ - &.wide { - max-width: none; - width: 100%; - margin: 2rem auto; - } - - &.wide p, - &.wide p.quote { - font-size: $type-size-4; - line-height: 1.5; - } - } - - /* links */ - - a { - text-underline-offset: .1em; - text-decoration-color: rgba($text-color, 0.7); - text-decoration: underline; - - - &:focus { - @extend %tab-focus; - } - - &:visited { - color: $link-color-visited; - } - - &:hover { - color: $link-color-hover; - outline: 0; - text-decoration: none !important; - text-decoration-line: none !important; - transition: color 0.3s ease, text-decoration-color 0.3s ease; - } - } - - /* buttons */ - - button:focus { - @extend %tab-focus; - } - - /* code */ - - tt, - code, - kbd, - samp, - pre { - font-family: $monospace; - font-weight: $weight-5; - font-size: 1em; - } - - - - - kbd { - background-color: $kbd-color-background; - color: $kbd-color-text; - border-radius: 0.25rem; - box-shadow: 0 2px 0 1px $kbd-color-border; - line-height: 1; - font-size: .75em; - padding: .15em .25em; - - &:hover { - box-shadow: 0 1px 0 0.5px $kbd-color-border; - top: 1px; - } - } - - pre { - overflow-x: auto; /* add scrollbars to wide code blocks*/ - } - - /* horizontal rule */ - - hr { - display: block; - margin: 1em 0; - border: 0; - border-top: 1px solid $border-color; - } - - /* lists */ - - ul, - ol { - margin-bottom: 1.9em; - font-weight: $weight-3; - } - ul li, - ol li { - margin-bottom: 0.3em; - font-size: $type-size-5; - list-style: 1.4em; - } - - ul li::marker { - color: #9C939F; /* Change bullet color */ - } - - li ul, - li ol { - margin-top: 0.5em; - } - - /* - Media and embeds - ========================================================================== */ - - /* Figures and images */ - - figure { - display: -webkit-box; - display: flex; - -webkit-box-pack: justify; - justify-content: space-between; - -webkit-box-align: start; - align-items: flex-start; - flex-wrap: wrap; - margin: 2em 0; - - img, - iframe, - .fluid-width-video-wrapper { - margin-bottom: 1em; - } - - img { - width: 100%; - border-radius: $border-radius; - -webkit-transition: $global-transition; - transition: $global-transition; - } - - > a { - display: block; - } - - &.half { - > a, - > img { - @include breakpoint($small) { - width: calc(50% - 0.5em); - } - } - - figcaption { - width: 100%; - } - } - - &.third { - > a, - > img { - @include breakpoint($small) { - width: calc(33.3333% - 0.5em); - } - } - - figcaption { - width: 100%; - } - } - } - - /* Figure captions */ - - figcaption { - margin-bottom: 0.5em; - color: $muted-text-color; - font-family: $caption-font-family; - font-size: $type-size-6; - - a { - -webkit-transition: $global-transition; - transition: $global-transition; - - &:hover { - color: $link-color-hover; - } - } - } - - /* Fix IE9 SVG bug */ - - svg:not(:root) { - overflow: hidden; - } - - /* - Navigation lists - ========================================================================== */ - - nav { - ul { - margin: 0; - padding: 0; - } - - li { - list-style: none; - } - - a { - text-decoration: none; - } - - /* override white-space for nested lists */ - ul li, - ol li { - margin-bottom: 0; - } - - li ul, - li ol { - margin-top: 0; - } - } - - /* - Global animation transition - ========================================================================== */ - - b, - i, - strong, - em, - blockquote, - p, - q, - span, - figure, - img, - h1, - h2, - header, - input, - a, - tr, - td, - form button, - input[type="submit"], - .btn, - .highlight, - .archive__item-teaser { - -webkit-transition: $global-transition; - transition: $global-transition; - } diff --git a/_sass/minimal-mistakes/_buttons.scss b/_sass/minimal-mistakes/_buttons.scss deleted file mode 100644 index c268c74d..00000000 --- a/_sass/minimal-mistakes/_buttons.scss +++ /dev/null @@ -1,131 +0,0 @@ -/* ========================================================================== - BUTTONS - ========================================================================== */ - -/* - Default button - ========================================================================== */ - -.btn { - /* default */ - display: inline-block; - margin-bottom: 0.25em; - padding: 0.5em 1em; - font-family: $header-font; // poppins for buttons and headers - font-size: $type-size-5; - font-weight: 500; - text-align: center; - text-decoration: none; - border-width: 0; - border-radius: $border-radius; - cursor: pointer; - - .icon { - margin-right: 0.5em; - } - - .icon + .hidden { - margin-left: -0.5em; /* override for hidden text*/ - } - - /* button colors */ - $buttoncolors: - (primary, $primary-color), - (inverse, #fff), - (light-outline, transparent), - (success, $success-color), - (warning, $warning-color), - (danger, $danger-color), - (info, $info-color), - (facebook, $facebook-color), - (twitter, $twitter-color), - (linkedin, $linkedin-color); - - @each $buttoncolor, $color in $buttoncolors { - &--#{$buttoncolor} { - @include yiq-contrasted($color); - @if ($buttoncolor == inverse) { - border: 1px solid $border-color; - } - @if ($buttoncolor == light-outline) { - border: 1px solid #fff; - } - - &:visited { - @include yiq-contrasted($color); - } - - &:hover { - @include yiq-contrasted(mix(#000, $color, 20%)); - } - } - } - - /* fills width of parent container */ - &--block { - display: block; - width: 100%; - - + .btn--block { - margin-top: 0.25em; - } - } - - /* disabled */ - &--disabled { - pointer-events: none; - cursor: not-allowed; - filter: alpha(opacity=65); - box-shadow: none; - opacity: 0.65; - } - - /* extra large button */ - &--x-large { - font-size: $type-size-4; - } - - /* large button */ - &--large { - font-size: $type-size-5; - } - - /* medium button (between default and small) */ - &--medium { - font-size: 0.9rem; - } - - /* homepage header button style */ - &--header { - font-family: $header-font; - font-size: 0.85rem; - font-weight: $weight-5; - } - - /* homepage header outline button */ - &--header-outline { - font-family: $header-font; - font-size: 0.85rem; - font-weight: $weight-5; - background-color: transparent; - border: 1px solid #fff; - color: #fff; - } - - /* small button */ - &--small { - font-size: $type-size-7; - } -} - - - /* max 480 px */ - @media screen and (max-width: 480px) { - // large buttons are disproportionately large - a.btn.btn--large { - line-height: 1.1; - padding: .5rem; - width: 100%; // this was set to 90% but made tutorial buttons short. - font-size: 1.5rem!important; - } - } diff --git a/_sass/minimal-mistakes/_cookie-consent.scss b/_sass/minimal-mistakes/_cookie-consent.scss deleted file mode 100644 index 9a746f8c..00000000 --- a/_sass/minimal-mistakes/_cookie-consent.scss +++ /dev/null @@ -1,137 +0,0 @@ -/* ========================================================================== - Cookie Consent Banner - ========================================================================== */ - -#cookie-consent-banner { - position: fixed; - bottom: 0; - left: 0; - right: 0; - background: #fff; - border-top: 3px solid #6e3667; - box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1); - z-index: 9999; - padding: 20px; - animation: slideUp 0.4s ease-out; -} - -@keyframes slideUp { - from { - transform: translateY(100%); - } - - to { - transform: translateY(0); - } -} - -.cookie-consent-content { - max-width: 1200px; - margin: 0 auto; - display: flex; - flex-direction: column; - gap: 20px; -} - -.cookie-consent-text { - h3 { - margin: 0 0 10px 0; - color: #6e3667; - font-size: 1.3em; - font-weight: 600; - } - - p { - margin: 0 0 10px 0; - color: #333; - font-size: 0.95em; - line-height: 1.6; - - &:last-child { - margin-bottom: 0; - } - } -} - -.cookie-consent-buttons { - display: flex; - gap: 15px; - justify-content: flex-start; - flex-wrap: wrap; - - .btn { - padding: 12px 24px; - border: none; - border-radius: 5px; - font-size: 1em; - font-weight: 500; - cursor: pointer; - transition: all 0.3s ease; - font-family: inherit; - - &.btn-primary { - background-color: #6e3667; - color: #fff; - - &:hover { - background-color: #8e4687; - transform: translateY(-2px); - box-shadow: 0 4px 8px rgba(110, 54, 103, 0.3); - } - } - - &.btn-secondary { - background-color: #f0f0f0; - color: #333; - border: 1px solid #ccc; - - &:hover { - background-color: #e0e0e0; - border-color: #999; - } - } - } -} - -/* Responsive design */ -@media (min-width: 768px) { - .cookie-consent-content { - flex-direction: row; - align-items: center; - gap: 30px; - } - - .cookie-consent-text { - flex: 1; - } - - .cookie-consent-buttons { - flex-shrink: 0; - justify-content: flex-end; - } -} - -@media (max-width: 767px) { - #cookie-consent-banner { - padding: 15px; - } - - .cookie-consent-text { - h3 { - font-size: 1.1em; - } - - p { - font-size: 0.9em; - } - } - - .cookie-consent-buttons { - flex-direction: column; - - .btn { - width: 100%; - text-align: center; - } - } -} diff --git a/_sass/minimal-mistakes/_fonts-load.scss b/_sass/minimal-mistakes/_fonts-load.scss deleted file mode 100644 index d2f01a00..00000000 --- a/_sass/minimal-mistakes/_fonts-load.scss +++ /dev/null @@ -1,63 +0,0 @@ -@font-face { - font-family: 'Nunito Sans'; - src: url('/assets/fonts/NunitoSans-VariableFont.woff2') format('woff2'); - font-weight: 100 900; /* Supports all weights */ - font-display: swap; -} - -@font-face { - font-family: 'Nunito Sans'; - src: url('/assets/fonts/NunitoSans-Italic-VariableFont.woff2') format('woff2'); - font-weight: 100 900; - font-stretch: 75% 100%; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Itim'; - src: url('/assets/fonts/Itim-Regular.woff2') format('woff2'); - font-weight: 400; - font-display: swap; -} -@font-face { - font-family: 'Poppins'; - src: url('/assets/fonts/Poppins-Light.woff2') format('woff2'); - font-weight: 300; - font-display: swap; -} - -@font-face { - font-family: 'Poppins'; - src: url('/assets/fonts/Poppins-Regular.woff2') format('woff2'); - font-weight: 400; - font-display: swap; -} - -@font-face { - font-family: 'Poppins'; - src: url('/assets/fonts/Poppins-Medium.woff2') format('woff2'); - font-weight: 500; - font-display: swap; -} - -@font-face { - font-family: 'Poppins'; - src: url('/assets/fonts/Poppins-SemiBold.woff2') format('woff2'); - font-weight: 600; - font-display: swap; -} - -@font-face { - font-family: 'Poppins'; - src: url('/assets/fonts/Poppins-Bold.woff2') format('woff2'); - font-weight: 700; - font-display: swap; -} - -@font-face { - font-family: 'Poppins'; - src: url('/assets/fonts/Poppins-Italic.woff2') format('woff2'); - font-style: italic; - font-display: swap; -} diff --git a/_sass/minimal-mistakes/_footer.scss b/_sass/minimal-mistakes/_footer.scss deleted file mode 100644 index de758380..00000000 --- a/_sass/minimal-mistakes/_footer.scss +++ /dev/null @@ -1,169 +0,0 @@ -/* ========================================================================== - FOOTER - ========================================================================== */ - -.page__footer { - @include clearfix; - float: left; - margin-left: 0; - margin-right: 0; - width: 100%; - margin-top: 3em; - color: rgba($pyos-white, 0.9); - -webkit-animation: $intro-transition; - animation: $intro-transition; - -webkit-animation-delay: 0.45s; - animation-delay: 0.45s; - background-color: darken($pyos-deeppurple, 8%); - font-size: 0.85rem; - line-height: 1.3rem; - font-family: "Poppins", $sans-serif; - - footer { - @include clearfix; - margin-left: auto; - margin-right: auto; - margin-top: 2em; - max-width: 100%; - padding: 0 1em 2em; - display: flex; - flex-direction: column; - align-items: center; - gap: 0.5em; - - @include breakpoint($x-large) { - max-width: $x-large; - } - } - - a { - color: rgba($pyos-white, 0.9); - text-decoration: none; - - &:hover { - color: $pyos-white; - text-decoration: underline; - } - } - - .fas, - .fab, - .far, - .fal { - color: rgba($pyos-white, 0.9); - } -} - -.page__footer-copyright { - font-family: "Poppins", $sans-serif; - font-size: $type-size-6; - padding: 1em; - max-width: 900px; - width: 100%; - margin: 0 auto; - text-align: left; - - p { - margin: 0 0 0.5em 0; - } -} - -.page__footer-follow { - width: 100%; - max-width: $x-large; - margin: 0 auto; - text-align: center; - - ul { - margin: 0; - padding: 0; - list-style-type: none; - display: flex; - flex-wrap: nowrap; - justify-content: center; - gap: 0.2rem 0.4rem; - } - - @include breakpoint(max-width $large) { - ul { - flex-wrap: wrap; - } - } - - li { - display: inline-flex; - padding-top: 5px; - padding-bottom: 5px; - font-family: "Poppins", $sans-serif; - font-size: $type-size-5; - } - - li + li:before { - content: ""; - padding-right: 5px; - } - - a { - padding-right: 10px; - font-weight: bold; - } -} - -// Footer social icons - consolidated styles with higher specificity -.page__footer .page__footer-follow .social-icons, -.page__footer-follow .social-icons { - a { - white-space: nowrap; - color: rgba($pyos-white, 0.9); - text-decoration: none; - - &:hover { - color: $pyos-white; - text-decoration: underline; - } - } - - i, - .fas, - .fab, - .far, - .fal { - font-size: 0.75em; - margin-right: 0.3em; - color: rgba($pyos-white, 0.85); - } - - // Override all brand colors to be white - higher specificity without !important - .fa-github, - .fa-github-alt, - .fa-github-square, - .fa-gitlab, - .fa-linkedin, - .fa-linkedin-in, - .fa-mastodon, - .fa-mastodon-square, - .fa-reddit, - .fa-rss, - .fa-rss-square, - .fa-stack-exchange, - .fa-stack-overflow, - .fa-youtube { - color: rgba($pyos-white, 0.85); - } -} - -.page__footer-netlify { - margin-top: 1em; - text-align: center; - - a { - display: inline-block; - line-height: 0; - } - - img { - display: block; - height: auto; - max-width: 5rem; - } -} diff --git a/_sass/minimal-mistakes/_forms.scss b/_sass/minimal-mistakes/_forms.scss deleted file mode 100644 index 0dd9b480..00000000 --- a/_sass/minimal-mistakes/_forms.scss +++ /dev/null @@ -1,359 +0,0 @@ -/* ========================================================================== - Forms - ========================================================================== */ - -form { - margin: 0 0 5px 0; - padding: 1em; - background-color: $form-background-color; - - fieldset { - margin-bottom: 5px; - padding: 0; - border-width: 0; - } - - legend { - display: block; - width: 100%; - margin-bottom: 5px * 2; - *margin-left: -7px; - padding: 0; - color: $text-color; - border: 0; - white-space: normal; - } - - p { - margin-bottom: (5px / 2); - } - - ul { - list-style-type: none; - margin: 0 0 5px 0; - padding: 0; - } - - br { - display: none; - } -} - -label, -input, -button, -select, -textarea { - vertical-align: baseline; - *vertical-align: middle; -} - -input, -button, -select, -textarea { - box-sizing: border-box; - font-family: $sans-serif; -} - -label { - display: block; - margin-bottom: 0.25em; - color: $text-color; - cursor: pointer; - - small { - font-size: $type-size-6; - } - - input, - textarea, - select { - display: block; - } -} - -input, -textarea, -select { - display: inline-block; - width: 100%; - padding: 0.25em; - margin-bottom: 0.5em; - color: $text-color; - background-color: $background-color; - border: $border-color; - border-radius: $border-radius; - box-shadow: $box-shadow; -} - -.input-mini { - width: 60px; -} - -.input-small { - width: 90px; -} - -input[type="image"], -input[type="checkbox"], -input[type="radio"] { - width: auto; - height: auto; - padding: 0; - margin: 3px 0; - *margin-top: 0; - line-height: normal; - cursor: pointer; - border-radius: 0; - border: 0 \9; - box-shadow: none; -} - -input[type="checkbox"], -input[type="radio"] { - box-sizing: border-box; - padding: 0; - *width: 13px; - *height: 13px; -} - -input[type="image"] { - border: 0; -} - -input[type="file"] { - width: auto; - padding: initial; - line-height: initial; - border: initial; - background-color: transparent; - background-color: initial; - box-shadow: none; -} - -input[type="button"], -input[type="reset"], -input[type="submit"] { - width: auto; - height: auto; - cursor: pointer; - *overflow: visible; -} - -select, -input[type="file"] { - *margin-top: 4px; -} - -select { - width: auto; - background-color: #fff; -} - -select[multiple], -select[size] { - height: auto; -} - -textarea { - resize: vertical; - height: auto; - overflow: auto; - vertical-align: top; -} - -input[type="hidden"] { - display: none; -} - -.form { - position: relative; -} - -.radio, -.checkbox { - padding-left: 18px; - font-weight: normal; -} - -.radio input[type="radio"], -.checkbox input[type="checkbox"] { - float: left; - margin-left: -18px; -} - -.radio.inline, -.checkbox.inline { - display: inline-block; - padding-top: 5px; - margin-bottom: 0; - vertical-align: middle; -} - -.radio.inline + .radio.inline, -.checkbox.inline + .checkbox.inline { - margin-left: 10px; -} - -/* - Disabled state - ========================================================================== */ - -input[disabled], -select[disabled], -textarea[disabled], -input[readonly], -select[readonly], -textarea[readonly] { - opacity: 0.5; - cursor: not-allowed; -} - -/* - Focus & active state - ========================================================================== */ - -input:focus, -textarea:focus { - border-color: $primary-color; - outline: 0; - outline: thin dotted \9; - box-shadow: inset 0 1px 3px rgba($text-color, 0.06), - 0 0 5px rgba($primary-color, 0.7); -} - -input[type="file"]:focus, -input[type="radio"]:focus, -input[type="checkbox"]:focus, -select:focus { - box-shadow: none; -} - -/* - Help text - ========================================================================== */ - -.help-block, -.help-inline { - color: $muted-text-color; -} - -.help-block { - display: block; - margin-bottom: 1em; - line-height: 1em; -} - -.help-inline { - display: inline-block; - vertical-align: middle; - padding-left: 5px; -} - -/* - .form-group - ========================================================================== */ - -.form-group { - margin-bottom: 5px; - padding: 0; - border-width: 0; -} - -/* - .form-inline - ========================================================================== */ - -.form-inline input, -.form-inline textarea, -.form-inline select { - display: inline-block; - margin-bottom: 0; -} - -.form-inline label { - display: inline-block; -} - -.form-inline .radio, -.form-inline .checkbox, -.form-inline .radio { - padding-left: 0; - margin-bottom: 0; - vertical-align: middle; -} - -.form-inline .radio input[type="radio"], -.form-inline .checkbox input[type="checkbox"] { - float: left; - margin-left: 0; - margin-right: 3px; -} - -/* - .form-search - ========================================================================== */ - -.form-search input, -.form-search textarea, -.form-search select { - display: inline-block; - margin-bottom: 0; -} - -.form-search .search-query { - padding-left: 14px; - padding-right: 14px; - margin-bottom: 0; - border-radius: 14px; -} - -.form-search label { - display: inline-block; -} - -.form-search .radio, -.form-search .checkbox, -.form-inline .radio { - padding-left: 0; - margin-bottom: 0; - vertical-align: middle; -} - -.form-search .radio input[type="radio"], -.form-search .checkbox input[type="checkbox"] { - float: left; - margin-left: 0; - margin-right: 3px; -} - -/* - .form--loading - ========================================================================== */ - -.form--loading:before { - content: ""; -} - -.form--loading .form__spinner { - display: block; -} - -.form:before { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: rgba(255, 255, 255, 0.7); - z-index: 10; -} - -.form__spinner { - display: none; - position: absolute; - top: 50%; - left: 50%; - z-index: 11; -} diff --git a/_sass/minimal-mistakes/_masthead.scss b/_sass/minimal-mistakes/_masthead.scss deleted file mode 100644 index c5cbb301..00000000 --- a/_sass/minimal-mistakes/_masthead.scss +++ /dev/null @@ -1,187 +0,0 @@ -/* -========================================================================== -MASTHEAD - -Layout and styling of the top-level items within the masthead. -For dropdown menu functionality, see _pyos-dropdown.scss -========================================================================== -*/ - -.masthead { - position: relative; - border-bottom: 1px solid $border-color; - padding-bottom: 0.15rem; // Add space below navigation to prevent overlap - -webkit-animation: $intro-transition; - animation: $intro-transition; - -webkit-animation-delay: 0.15s; - animation-delay: 0.15s; - z-index: 20; - - - &__menu { - @include clearfix; - margin: auto; - padding: 0 1em; - max-width: 100%; - height: 4rem; // Increased from 3rem to accommodate taller logo - display: flex; - justify-content: space-between; - font-family: $sans-serif-narrow; - position: relative; - align-items: stretch; - min-height: 3rem; // Increased from 2em to ensure adequate height - background: #fff; - - & > * { - margin: auto; - } - - .nav__links { - margin: 0; - } - - a { - text-decoration: none; - } - - @include breakpoint($x-large) { - max-width: $max-width; - } - - .spacer { - flex-grow: 1; - } - } -} - -.site-logo { - height: auto; - width: auto; - top: 0; - left: 0; - position: relative; - background-color: transparent; - border: none; - box-shadow: none; - border-radius: 0; - display: flex; - justify-content: center; - align-items: center; - padding: 0.5em 0; - margin-bottom: 0; // Ensure no extra margin causing overlap - flex-shrink: 0; // Prevent logo from shrinking in flexbox - flex-grow: 0; // Prevent logo from growing in flexbox - - // Responsive adjustments for mobile - @media (max-width: $medium) { - padding: 0.25em 0; // Reduce padding on mobile but keep logo size - } - - &::after { - display: none; - } - - img { - object-fit: contain; - height: 2.5rem; // Use rem instead of em for consistent size across breakpoints - width: auto; - margin: 0; - display: block; - box-shadow: none; - align-self: center; - max-height: 2.5rem; // Ensure it never exceeds this size - } -} - -.site-title { - display: -webkit-box; - display: -ms-flexbox; - display: flex; - -ms-flex-item-align: center; - align-self: center; - font-weight: bold; -} - -.site-subtitle { - display: block; - font-weight: 400; - font-size: .8em; - color: $nav-font-color; -} - -/* -========================================================================== -NAVIGATION LINKS STYLING -========================================================================== -*/ - -/* Donate button and search in navigation */ -.nav__links { - display: flex; - align-items: stretch; - height: 100%; - - li { - display: flex; - align-items: center; - } - - .search__toggle { - margin-left: 0 !important; - margin-right: 0.75em !important; - padding: 14px 12px; - background: transparent; - border: none; - color: $nav-font-color; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - font-size: 1em; - height: 100%; - transition: color 0.2s ease; - - &:hover { - color: $pyos-darkpurple; - } - - i { - font-size: 1em; - } - } - - .donate-btn { - margin-left: 0; - padding: 0.5em 0.75em; - font-size: 0.8em; - font-weight: 500; - white-space: nowrap; - text-decoration: none; - display: inline-flex; - align-items: center; - justify-content: center; - height: auto; - line-height: 1.3; - border: 1px solid $pyos-darkpurple; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.06); - transition: box-shadow 0.3s ease, transform 0.2s ease, color 0.3s ease; - background-color: $pyos-darkpurple; - color: $pyos-white !important; - - &:hover { - box-shadow: 0 10px 15px rgba(0, 0, 0, 0.15), 0 4px 6px rgba(0, 0, 0, 0.1); - transform: translateY(-1px); - color: $pyos-lightpurple !important; - } - - @include breakpoint($medium) { - margin-left: 0; - } - - @media (max-width: $medium) { - margin-left: 0; - width: 100%; - justify-content: center; - } - } -} diff --git a/_sass/minimal-mistakes/_mixins.scss b/_sass/minimal-mistakes/_mixins.scss deleted file mode 100644 index 02973704..00000000 --- a/_sass/minimal-mistakes/_mixins.scss +++ /dev/null @@ -1,100 +0,0 @@ -/* ========================================================================== - MIXINS - ========================================================================== */ - -%tab-focus { - /* Default*/ - outline: thin dotted $focus-color; - /* Webkit*/ - outline: 5px auto $focus-color; - outline-offset: -2px; -} - -/* - em function - ========================================================================== */ - -@function em($target, $context: $doc-font-size) { - @return ($target / $context) * 1em; -} - - -/* - Bourbon clearfix - ========================================================================== */ - -/* - * Provides an easy way to include a clearfix for containing floats. - * link http://cssmojo.com/latest_new_clearfix_so_far/ - * - * example scss - Usage - * - * .element { - * @include clearfix; - * } - * - * example css - CSS Output - * - * .element::after { - * clear: both; - * content: ""; - * display: table; - * } -*/ - -@mixin clearfix { - clear: both; - - &::after { - clear: both; - content: ""; - display: table; - } -} - -/* - Compass YIQ Color Contrast - https://github.com/easy-designs/yiq-color-contrast - ========================================================================== */ - -@function yiq-is-light( - $color, - $threshold: $yiq-contrasted-threshold -) { - $red: red($color); - $green: green($color); - $blue: blue($color); - - $yiq: (($red*299)+($green*587)+($blue*114))/1000; - - @if $yiq-debug { @debug $yiq, $threshold; } - - @return if($yiq >= $threshold, true, false); -} - -@function yiq-contrast-color( - $color, - $dark: $yiq-contrasted-dark-default, - $light: $yiq-contrasted-light-default, - $threshold: $yiq-contrasted-threshold -) { - @return if(yiq-is-light($color, $threshold), $yiq-contrasted-dark-default, $yiq-contrasted-light-default); -} - -@mixin yiq-contrasted( - $background-color, - $dark: $yiq-contrasted-dark-default, - $light: $yiq-contrasted-light-default, - $threshold: $yiq-contrasted-threshold -) { - background-color: $background-color; - color: yiq-contrast-color($background-color, $dark, $light, $threshold)!important; -} - - -// breakpoint max width mixin -@mixin breakpoint-max-width($max-width) { - @media screen and (max-width: #{$max-width}) { - @content; - } -} diff --git a/_sass/minimal-mistakes/_navigation.scss b/_sass/minimal-mistakes/_navigation.scss deleted file mode 100644 index a211dbca..00000000 --- a/_sass/minimal-mistakes/_navigation.scss +++ /dev/null @@ -1,584 +0,0 @@ -/* ========================================================================== - NAVIGATION - ========================================================================== */ - -/* - Breadcrumb navigation links - ========================================================================== */ - -.breadcrumbs { - @include clearfix; - margin: 0 auto; - max-width: 100%; - padding-left: 1em; - padding-right: 1em; - font-family: $sans-serif; - -webkit-animation: $intro-transition; - animation: $intro-transition; - -webkit-animation-delay: 0.3s; - animation-delay: 0.3s; - - @include breakpoint($x-large) { - max-width: $x-large; - } - - ol { - padding: 0; - list-style: none; - font-size: $type-size-6; - - @include breakpoint($large) { - float: right; - width: calc(100% - #{$right-sidebar-width-narrow}); - } - - @include breakpoint($x-large) { - width: calc(100% - #{$right-sidebar-width}); - } - } - - li { - display: inline; - } - - .current { - font-weight: bold; - } -} - -/* - Post pagination navigation links - ========================================================================== */ - -.pagination { - @include clearfix(); - float: left; - margin-top: 1em; - padding-top: 1em; - width: 100%; - - ul { - margin: 0; - padding: 0; - list-style-type: none; - font-family: $sans-serif; - } - - li { - display: block; - float: left; - margin-left: -1px; - - a { - display: block; - margin-bottom: 0.25em; - padding: 0.5em 1em; - font-family: $sans-serif; - font-size: 14px; - font-weight: bold; - line-height: 1.5; - text-align: center; - text-decoration: none; - color: $muted-text-color; - border: 1px solid mix(#000, $border-color, 25%); - border-radius: 0; - - &:hover { - color: $link-color-hover; - } - - &.current, - &.current.disabled { - color: #fff; - background: $primary-color; - } - - &.disabled { - color: rgba($muted-text-color, 0.5); - pointer-events: none; - cursor: not-allowed; - } - } - - &:first-child { - margin-left: 0; - - a { - border-top-left-radius: $border-radius; - border-bottom-left-radius: $border-radius; - } - } - - &:last-child { - a { - border-top-right-radius: $border-radius; - border-bottom-right-radius: $border-radius; - } - } - } - - /* next/previous buttons */ - &--pager { - display: block; - padding: 1em 2em; - float: left; - width: 50%; - font-family: $sans-serif; - font-size: $type-size-5; - font-weight: bold; - text-align: center; - text-decoration: none; - color: $muted-text-color; - border: 1px solid mix(#000, $border-color, 25%); - border-radius: $border-radius; - - &:hover { - @include yiq-contrasted($muted-text-color); - } - - &:first-child { - border-top-right-radius: 0; - border-bottom-right-radius: 0; - } - - &:last-child { - margin-left: -1px; - border-top-left-radius: 0; - border-bottom-left-radius: 0; - } - - &.disabled { - color: rgba($muted-text-color, 0.5); - pointer-events: none; - cursor: not-allowed; - } - } -} - -.page__content + .pagination, -.page__meta + .pagination, -.page__share + .pagination, -.page__comments + .pagination { - margin-top: 2em; - padding-top: 2em; - border-top: 1px solid $border-color; -} - -/* - Priority plus navigation - ========================================================================== */ - -.greedy-nav { - position: relative; - display: -webkit-box; - display: -ms-flexbox; - display: flex; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - min-height: $nav-height; - background: $background-color; - - a { - display: block; - margin: 0 1rem; - color: $masthead-link-color; - text-decoration: none; - -webkit-transition: none; - transition: none; - - &:hover { - color: $masthead-link-color-hover; - } - - &.site-logo { - margin-left: 0; - margin-right: 0.5rem; - } - - &.site-title { - margin-left: 0; - } - } - - img { - -webkit-transition: none; - transition: none; - } - - &__toggle { - -ms-flex-item-align: center; - align-self: center; - height: $nav-toggle-height; - border: 0; - outline: none; - background-color: transparent; - cursor: pointer; - } - - .visible-links { - display: -webkit-box; - display: -ms-flexbox; - display: flex; - -webkit-box-pack: end; - -ms-flex-pack: end; - justify-content: flex-end; - -webkit-box-flex: 1; - -ms-flex: 1; - flex: 1; - overflow: hidden; - - li { - -webkit-box-flex: 0; - -ms-flex: none; - flex: none; - } - - a { - position: relative; - - &:before { - content: ""; - position: absolute; - left: 0; - bottom: 0; - height: 4px; - background: $primary-color; - width: 100%; - -webkit-transition: $global-transition; - transition: $global-transition; - -webkit-transform: scaleX(0) translate3d(0, 0, 0); - transform: scaleX(0) translate3d(0, 0, 0); // hide - } - - &:hover:before { - -webkit-transform: scaleX(1); - -ms-transform: scaleX(1); - transform: scaleX(1); // reveal - } - } - } - - .hidden-links { - position: absolute; - top: 100%; - right: 0; - margin-top: 15px; - padding: 5px; - border: 1px solid $border-color; - border-radius: $border-radius; - background: $background-color; - -webkit-box-shadow: - 0 2px 4px 0 rgba(#000, 0.16), - 0 2px 10px 0 rgba(#000, 0.12); - box-shadow: - 0 2px 4px 0 rgba(#000, 0.16), - 0 2px 10px 0 rgba(#000, 0.12); - - &.hidden { - display: none; - } - - a { - margin: 0; - padding: 10px 20px; - font-size: $type-size-5; - - &:hover { - color: $masthead-link-color-hover; - background: $navicon-link-color-hover; - } - } - - &:before { - content: ""; - position: absolute; - top: -11px; - right: 10px; - width: 0; - border-style: solid; - border-width: 0 10px 10px; - border-color: $border-color transparent; - display: block; - z-index: 0; - } - - &:after { - content: ""; - position: absolute; - top: -10px; - right: 10px; - width: 0; - border-style: solid; - border-width: 0 10px 10px; - border-color: $background-color transparent; - display: block; - z-index: 1; - } - - li { - display: block; - border-bottom: 1px solid $border-color; - - &:last-child { - border-bottom: none; - } - } - } -} - -.no-js { - .greedy-nav { - .visible-links { - -ms-flex-wrap: wrap; - flex-wrap: wrap; - overflow: visible; - } - } -} - -/* - Navigation list - ========================================================================== */ - -.nav__list { - margin-bottom: 1.5em; - - input[type="checkbox"], - label { - display: none; - } - - @include breakpoint(max-width $large - 1px) { - label { - position: relative; - display: inline-block; - padding: 0.5em 2.5em 0.5em 1em; - color: $gray; - font-size: $type-size-6; - font-weight: bold; - border: 1px solid $light-gray; - border-radius: $border-radius; - z-index: 20; - -webkit-transition: 0.2s ease-out; - transition: 0.2s ease-out; - cursor: pointer; - - &:before, - &:after { - content: ""; - position: absolute; - right: 1em; - top: 1.25em; - width: 0.75em; - height: 0.125em; - line-height: 1; - background-color: $gray; - -webkit-transition: 0.2s ease-out; - transition: 0.2s ease-out; - } - - &:after { - -webkit-transform: rotate(90deg); - -ms-transform: rotate(90deg); - transform: rotate(90deg); - } - - &:hover { - color: #fff; - border-color: $gray; - background-color: mix(white, #000, 20%); - - &:before, - &:after { - background-color: #fff; - } - } - } - - /* selected*/ - input:checked + label { - color: white; - background-color: mix(white, #000, 20%); - - &:before, - &:after { - background-color: #fff; - } - } - - /* on hover show expand*/ - label:hover:after { - -webkit-transform: rotate(90deg); - -ms-transform: rotate(90deg); - transform: rotate(90deg); - } - - input:checked + label:hover:after { - -webkit-transform: rotate(0); - -ms-transform: rotate(0); - transform: rotate(0); - } - - ul { - margin-bottom: 1em; - } - - a { - display: block; - padding: 0.25em 0; - - @include breakpoint($large) { - padding-top: 0.125em; - padding-bottom: 0.125em; - } - - &:hover { - text-decoration: underline; - } - } - } -} - -.nav__list .nav__items { - margin: 0; - font-size: 1.25rem; - - a { - color: inherit; - } - - .active { - margin-left: -0.5em; - padding-left: 0.5em; - padding-right: 0.5em; - font-weight: bold; - } - - @include breakpoint(max-width $large - 1px) { - position: relative; - max-height: 0; - opacity: 0%; - overflow: hidden; - z-index: 10; - -webkit-transition: 0.3s ease-in-out; - transition: 0.3s ease-in-out; - -webkit-transform: translate(0, 10%); - -ms-transform: translate(0, 10%); - transform: translate(0, 10%); - } -} - -@include breakpoint(max-width $large - 1px) { - .nav__list input:checked ~ .nav__items { - -webkit-transition: 0.5s ease-in-out; - transition: 0.5s ease-in-out; - max-height: 9999px; /* exaggerate max-height to accommodate tall lists*/ - overflow: visible; - opacity: 1; - margin-top: 1em; - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - transform: translate(0, 0); - } -} - -.nav__title { - margin: 0; - padding: 0.5rem 0.75rem; - font-family: $sans-serif-narrow; - font-size: $type-size-5; - font-weight: bold; -} - -.nav__sub-title { - display: block; - margin: 0.5rem 0; - padding: 0.25rem 0; - font-family: $sans-serif-narrow; - font-size: $type-size-6; - font-weight: bold; - text-transform: uppercase; - border-bottom: 1px solid $border-color; -} - -/* - Table of contents navigation - ========================================================================== */ - -.toc { - font-family: $sans-serif-narrow; - color: $gray; - background-color: $background-color; - border: 1px solid $border-color; - border-radius: $border-radius; - -webkit-box-shadow: $box-shadow; - box-shadow: $box-shadow; - max-width: $medium; - - .nav__title { - color: #fff; - font-size: $type-size-6; - background: $primary-color; - border-top-left-radius: $border-radius; - border-top-right-radius: $border-radius; - } - - // Scrollspy marks toc items as .active when they are in focus - .active a { - @include yiq-contrasted($active-color); - } - - @include breakpoint($large) { - max-width: $medium; - font-size: $type-size-4; - } -} - - -// Force the sub menu in a toctree to be the same size as the main menu -.toc__menu { - margin: 0; - padding: 0; - width: 100%; - list-style: none; - font-size: $type-size-5; - - @include breakpoint($large) { - font-size: $type-size-6; - } - - a { - display: block; - padding: 0.25rem 0.75rem; - color: $muted-text-color; - font-weight: bold; - line-height: 1.5; - border-bottom: 1px solid $border-color; - - &:hover { - color: $text-color; - } - } - - li ul > li a { - padding-left: 1.25rem; - font-weight: normal; - } - - li ul li ul > li a { - padding-left: 1.75rem; - } - - li ul li ul li ul > li a { - padding-left: 2.25rem; - } - - li ul li ul li ul li ul > li a { - padding-left: 2.75rem; - } - - li ul li ul li ul li ul li ul > li a { - padding-left: 3.25rem; - } -} diff --git a/_sass/minimal-mistakes/_notices.scss b/_sass/minimal-mistakes/_notices.scss deleted file mode 100644 index fb0ed2f7..00000000 --- a/_sass/minimal-mistakes/_notices.scss +++ /dev/null @@ -1,201 +0,0 @@ -/* ========================================================================== - NOTICE TEXT BLOCKS - ========================================================================== */ - -/** - * Default Kramdown usage (no indents!): - *
- * #### Headline for the Notice - * Text for the notice - *
- */ - - - -/* Notice elements with figures */ -.notice { - - ul li { - font-size: 1rem !important; - } - - h2, h3 { - margin-top: 0 !important; - } - - figcaption { - font-family: $body-font; - font-size: 1.2em; // This will override the previous 1.3em - } - - &--info { - .btn.btn--info { - color: #fff; - } - - .archive__item-excerpt { - font-size: 0.95em; - } - - h2 { - font-size: 1.5em; - } - - h3 a, - h2 a:visited { - font-size: 1.5em; - text-decoration: none; - } - } -} - - -@mixin notice($notice-color) { - margin: 2rem 0 !important; /* override*/ - padding: 3rem; - color: $text-color; - font-family: $global-font-family; - font-size: $type-size-4 !important; - font-weight: 300!important; - text-indent: initial; /* override*/ - background-color: mix($background-color, $notice-color, $notice-background-mix); - border-radius: $border-radius; - box-shadow: 0 1px 1px rgba(darken($notice-color, 20%), 0.25); - line-height: 1.7rem!important; - - // We might want a header like element in a notice but not an actual h4 etc - // this style mimicks a header within a notice block. - .header { - font-size: 1.5rem!important; - font-weight: 700; - margin-bottom: 1rem; - } - - h4 { - margin-top: 0 !important; - margin-bottom: 0.75em; - line-height: inherit; - } - - @at-root .page__content #{&} h4 { - /* using at-root to override .page-content h4 font size*/ - margin-bottom: 0; - font-size: 1.6em; - } - - p, li { - font-size: 1rem!important; - font-weight: 300; - } - - p { - line-height: 1.6rem; - - &:last-child { - margin-bottom: 0 !important; /* override*/ - } - } - - h4 + p { - /* remove space above paragraphs that appear directly after notice headline*/ - margin-top: 0; - padding-top: 0; - } - - a { - color: mix(#000, $notice-color, 10%); - - &:hover { - color: mix(#000, $notice-color, 50%); - } - } - - @at-root #{selector-unify(&, "blockquote")} { - border-left-color: mix(#000, $notice-color, 10%); - } - - code { - background-color: mix($background-color, $notice-color, $code-notice-background-mix) - } - - pre code { - background-color: inherit; - } - - ul { - &:last-child { - margin-bottom: 0; /* override*/ - } - } - @include breakpoint-max-width($mobile) { - padding: 0.5em; - } -} - -/* Default notice */ - -.notice { - @include notice($light-gray); -} - - -/* Primary notice */ - -.notice--primary { - @include notice($primary-color); -} - -/* Info notice */ - -.notice--info { - @include notice($info-color); -} - -/* Warning notice */ - -.notice--warning { - @include notice($warning-color); -} - -/* Success notice */ - -.notice--success { - @include notice($success-color); -} - -/* Danger notice */ - -.notice--danger { - @include notice($danger-color); -} - -/* Quotes in notice blocks */ - -.notice blockquote { - font-size: 1.5rem; - margin: 7em 2em; - max-width: 100%; - - &.highlight-quote { - margin: 2rem 0; - padding: 1.25rem 1.5rem; - max-width: $medium; - - p.quote { - font-size: $type-size-4 !important; - } - } -} - - -/* max 480 px */ -@media screen and (max-width: 480px) { - .notice { - padding: .2em; - - h2 { - padding: .1em .2em - } - } - -} diff --git a/_sass/minimal-mistakes/_page.scss b/_sass/minimal-mistakes/_page.scss deleted file mode 100644 index 7a777fa2..00000000 --- a/_sass/minimal-mistakes/_page.scss +++ /dev/null @@ -1,741 +0,0 @@ -/* ========================================================================== - SINGLE PAGE/POST - ========================================================================== */ - - #main { - @include clearfix; - margin-left: auto; - margin-right: auto; - padding-left: 1em; - padding-right: 1em; - -webkit-animation: $intro-transition; - animation: $intro-transition; - max-width: 100%; - -webkit-animation-delay: 0.15s; - animation-delay: 0.15s; - - @include breakpoint($x-large) { - max-width: $max-width; - } - } - - body { - display: -webkit-box; - display: -ms-flexbox; - display: flex; - min-height: 100vh; - -webkit-box-orient: vertical; - -webkit-box-direction: normal; - -ms-flex-direction: column; - flex-direction: column; - } - - .initial-content, - .search-content { - flex: 1 0 auto; - } - -//add spacing for dates used in blog posts -span.date { - padding-left: 2rem; -} - - .page { - @include breakpoint($large) { - float: right; - width: calc(100% - #{$right-sidebar-width-narrow}); - padding-right: $right-sidebar-width-narrow; - } - - @include breakpoint($x-large) { - width: calc(100% - #{$right-sidebar-width}); - padding-right: $right-sidebar-width; - } - - .page__inner-wrap { - float: left; - margin-top: 1em; - margin-left: 0; - margin-right: 0; - width: 100%; - clear: both; - - .page__content, - .page__meta, - .page__share { - position: relative; - float: left; - margin-left: 0; - margin-right: 0; - width: 100%; - clear: both; - } - } - } - - .page__title { - margin-top: 0; - line-height: 1; - font-weight: $semibold-weight!important; - - a { - color: $text-color; - text-decoration: none; - } - - & + .page__meta { - margin-top: -0.5em; - } - } - - // in our flowing layout the title should be max width - .page__content .pyos-section h2 { - max-width: 100%; - - } - .page__lead { - font-family: $header-font; - font-size: $type-size-5; - font-weight: $weight-3; - } - - - - .page__content { - figure { - max-width: 35rem; - } - - p, - li, - pre { - max-width: $medium; - font-weight: $weight-4; - } - - h2 { - margin-bottom: 2.0rem; - margin-top: 2.1rem; - } - - // Reduce top margin for h2 that follows feature wrapper - .feature__wrapper + h2 { - margin-top: 2rem; - } - - h1, - h2, - h3, - h4, - h5, - h6 { - max-width: $large; - - .header-link { - position: relative; - left: 0.5em; - opacity: 0; - font-size: 0.8em; - -webkit-transition: opacity 0.2s ease-in-out 0.1s; - -moz-transition: opacity 0.2s ease-in-out 0.1s; - -o-transition: opacity 0.2s ease-in-out 0.1s; - transition: opacity 0.2s ease-in-out 0.1s; - } - - &:hover .header-link { - opacity: 1; - } - } - - p, - dl { - font-size: $type-size-5; - line-height: 1.6rem; - } - - /* paragraph indents */ - p { - margin: 0 0 $indent-var; - - /* sibling indentation*/ - @if $paragraph-indent == true { - & + p { - text-indent: $indent-var; - margin-top: -($indent-var); - } - } - } - - a:not(.btn) { - &:hover { - text-decoration: underline; - - img { - box-shadow: 0 0 10px rgba(#000, 0.25); - } - } - } - - :not(pre) > code { - padding-top: 0.1rem; - padding-bottom: 0.1rem; - font-size: 0.8em; - background: $code-background-color; - border-radius: $border-radius; - - &::before, - &::after { - letter-spacing: -0.2em; - content: "\00a0"; /* non-breaking space*/ - } - } - - dt { - margin-top: 1em; - font-family: $sans-serif; - font-weight: bold; - } - - dd { - margin-left: 1em; - font-family: $sans-serif; - font-size: $type-size-6; - } - - .small { - font-size: $type-size-6; - } - - /* blockquote citations */ - blockquote + .small { - margin-top: -1.5em; - padding-left: 1.25rem; - } - } - - .page__hero { - position: relative; - margin-bottom: 2em; - @include clearfix; - -webkit-animation: $intro-transition; - animation: $intro-transition; - -webkit-animation-delay: 0.25s; - animation-delay: 0.25s; - - &--overlay { - position: relative; - margin-bottom: 2em; - padding: 3em 0; - @include clearfix; - background-size: cover; - background-repeat: no-repeat; - background-position: center; - -webkit-animation: $intro-transition; - animation: $intro-transition; - -webkit-animation-delay: 0.25s; - animation-delay: 0.25s; - - a { - color: #fff; - } - - .wrapper { - padding-left: 1em; - padding-right: 1em; - display: flex; - align-items: center; - justify-content: space-between; - gap: 1.5rem; - min-width: 0; - - @include breakpoint($x-large) { - max-width: $x-large; - } - } - - .page__hero-content { - flex: 1 1 auto; - min-width: 0; - } - - .page__hero-side-image-wrap { - flex: 0 1 auto; - align-self: center; - min-width: 0; - width: min( - $hero-side-image-width, - $hero-side-image-max-width-percent, - $hero-side-image-max-width-vw - ); - max-width: 100%; - aspect-ratio: $hero-side-image-width / $hero-side-image-height; - height: auto; - border-radius: 12px; - overflow: hidden; - border: 2px solid rgba(#000, 0.75); - - picture { - display: block; - width: 100%; - height: 100%; - } - } - - .page__hero-side-image { - width: 100%; - height: 100%; - max-width: 100%; - display: block; - object-fit: cover; - } - - .page__title, - .page__meta, - .page__lead, - .btn { - color: #fff; - text-shadow: 1px 1px 4px rgba(#000, 0.5); - } - - .page__lead { - max-width: 98%; - } - - .page__title { - font-size: clamp($hero-title-min-size, $hero-title-fluid-size, $hero-title-max-size); - font-weight: $bold-weight; - line-height: 1.1; - max-width: $hero-title-max-width; - white-space: normal; - - @include breakpoint($x-large) { - max-width: min(100%, 30ch); - white-space: normal; - text-wrap: balance; - } - } - - @include breakpoint(max-width $medium) { - .wrapper { - flex-direction: column; - align-items: stretch; - } - - .page__hero-side-image-wrap { - width: 100%; - max-width: 100%; - } - } - } - } - - .page__hero-image { - width: 100%; - height: auto; - -ms-interpolation-mode: bicubic; - } - - .page__hero-caption { - position: absolute; - bottom: 0; - right: 0; - margin: 0 auto; - padding: 2px 5px; - color: #fff; - font-family: $caption-font-family; - font-size: $type-size-7; - background: #000; - text-align: right; - z-index: 5; - opacity: 0.5; - border-radius: $border-radius 0 0 0; - - @include breakpoint($large) { - padding: 5px 10px; - } - - a { - color: #fff; - text-decoration: none; - } - } - - /* - Social sharing - ========================================================================== */ - - .page__share { - margin-top: 2em; - padding-top: 1em; - border-top: 1px solid $border-color; - - @include breakpoint(max-width $small) { - .btn span { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - width: 1px; - } - } - } - - .page__share-title { - margin-bottom: 10px; - font-size: $type-size-6; - text-transform: uppercase; - } - - /* - Page meta - ========================================================================== */ - - .page__meta { - margin-top: 2em; - color: $muted-text-color; - font-family: $sans-serif; - font-size: $type-size-6; - - p { - margin: 0; - } - - a { - color: inherit; - } - } - - .page__meta-title { - margin-bottom: 10px; - font-size: $type-size-6; - text-transform: uppercase; - } - - .page__meta-sep::before { - content: "\2022"; - padding-left: 0.5em; - padding-right: 0.5em; - } - - /* - Page taxonomy - ========================================================================== */ - - .page__taxonomy { - .sep { - display: none; - } - - strong { - margin-right: 10px; - } - } - - .page__taxonomy-item { - display: inline-block; - margin-right: 5px; - margin-bottom: 8px; - padding: 5px 10px; - text-decoration: none; - border: 1px solid mix(#000, $border-color, 25%); - border-radius: $border-radius; - - &:hover { - text-decoration: none; - color: $link-color-hover; - } - } - - .taxonomy__section { - margin-bottom: 2em; - padding-bottom: 1em; - - &:not(:last-child) { - border-bottom: solid 1px $border-color; - } - - .archive__item-title { - margin-top: 0; - } - - .archive__subtitle { - clear: both; - border: 0; - } - - + .taxonomy__section { - margin-top: 2em; - } - } - - .taxonomy__title { - margin-bottom: 0.5em; - color: $muted-text-color; - } - - .taxonomy__count { - color: $muted-text-color; - } - - .taxonomy__index { - display: grid; - grid-column-gap: 2em; - grid-template-columns: repeat(2, 1fr); - margin: 1.414em 0; - padding: 0; - font-size: 0.75em; - list-style: none; - - @include breakpoint($large) { - grid-template-columns: repeat(3, 1fr); - } - - a { - display: -webkit-box; - display: -ms-flexbox; - display: flex; - padding: 0.25em 0; - -webkit-box-pack: justify; - -ms-flex-pack: justify; - justify-content: space-between; - color: inherit; - text-decoration: none; - border-bottom: 1px solid $border-color; - } - } - - .back-to-top { - display: block; - clear: both; - color: $muted-text-color; - font-size: 0.6em; - text-transform: uppercase; - text-align: right; - text-decoration: none; - } - - /* - Comments - ========================================================================== */ - - .page__comments { - float: left; - margin-left: 0; - margin-right: 0; - width: 100%; - clear: both; - } - - .page__comments-title { - margin-top: 2rem; - margin-bottom: 10px; - padding-top: 2rem; - font-size: $type-size-6; - border-top: 1px solid $border-color; - text-transform: uppercase; - } - - .page__comments-form { - -webkit-transition: $global-transition; - transition: $global-transition; - - &.disabled { - input, - button, - textarea, - label { - pointer-events: none; - cursor: not-allowed; - filter: alpha(opacity=65); - box-shadow: none; - opacity: 0.65; - } - } - } - - .comment { - @include clearfix(); - margin: 1em 0; - - &:not(:last-child) { - border-bottom: 1px solid $border-color; - } - } - - .comment__avatar-wrapper { - float: left; - width: 60px; - height: 60px; - - @include breakpoint($large) { - width: 100px; - height: 100px; - } - } - - .comment__avatar { - width: 40px; - height: 40px; - border-radius: 50%; - - @include breakpoint($large) { - width: 80px; - height: 80px; - padding: 5px; - border: 1px solid $border-color; - } - } - - .comment__content-wrapper { - float: right; - width: calc(100% - 60px); - - @include breakpoint($large) { - width: calc(100% - 100px); - } - } - - .comment__author { - margin: 0; - - a { - text-decoration: none; - } - } - - .comment__date { - @extend .page__meta; - margin: 0; - - a { - text-decoration: none; - } - } - - /* - Related - ========================================================================== */ - - .page__related { - @include clearfix(); - float: left; - margin-top: 2em; - padding-top: 1em; - border-top: 1px solid $border-color; - - @include breakpoint($large) { - float: right; - width: calc(100% - #{$right-sidebar-width-narrow}); - } - - @include breakpoint($x-large) { - width: calc(100% - #{$right-sidebar-width}); - } - - a { - color: inherit; - text-decoration: none; - } - } - - .page__related-title { - margin-bottom: 10px; - font-size: $type-size-6; - text-transform: uppercase; - } - - /* - Wide Pages - ========================================================================== */ - - .wide { - .page { - @include breakpoint($large) { - padding-right: 0; - } - - @include breakpoint($x-large) { - padding-right: 0; - } - } - - .page__content figure { - max-width: 40rem; - } - - .page__related { - @include breakpoint($large) { - padding-right: 0; - } - - @include breakpoint($x-large) { - padding-right: 0; - } - } - } - - .layout--splash .page__content h2 { - width: 100%; - max-width: 100%; - } - - /* Media queries */ - - @media screen and (max-width: 768px) { - .element-item { - width:calc(90% - 10px)!important; - } - - .narrow p { - font-size: 0.9em!important; - } - .narrow { - font-size: 0.9em!important; - } - - .page__meta.contributors { - font-size: .9em !important; - } - - h2.archive__item-title, h3.archive__item-title { - font-size: 1.3em!important; - } - - - .feature__item--center .archive__item-body, .archive__item-body { - width: 100%!important; - padding: 0!important; - } - - .wide #main { - padding-left: 2em; - } - .feature__item { - width: 95%!important; - } - - - .wide__p_text { - max-width: 90%!important; - } - ol { - font-size: 1em; - } - } - - /* End media query 48em / 768 px */ - - /* max 480 px */ - @media screen and (max-width: 480px) { - #main { - padding: 0 .6rem; - max-width: 100%; - overflow-x: hidden; - } - .page__title { - max-width: 100%; - } - } diff --git a/_sass/minimal-mistakes/_print.scss b/_sass/minimal-mistakes/_print.scss deleted file mode 100644 index ed850c99..00000000 --- a/_sass/minimal-mistakes/_print.scss +++ /dev/null @@ -1,252 +0,0 @@ -/* ========================================================================== - PRINT STYLES - ========================================================================== */ - -@media print { - - [hidden] { - display: none; - } - - * { - -moz-box-sizing: border-box; - -webkit-box-sizing: border-box; - box-sizing: border-box; - } - - html { - margin: 0; - padding: 0; - min-height: auto !important; - font-size: 16px; - } - - body { - margin: 0 auto; - background: #fff !important; - color: #000 !important; - font-size: 1rem; - line-height: 1.5; - -moz-osx-font-smoothing: grayscale; - -webkit-font-smoothing: antialiased; - text-rendering: optimizeLegibility; - } - - h1, - h2, - h3, - h4, - h5, - h6 { - color: #000; - line-height: 1.2; - margin-bottom: 0.75rem; - margin-top: 0; - } - - h1 { - font-size: 2.5rem; - } - - h2 { - font-size: 2rem; - } - - h3 { - font-size: 1.75rem; - } - - h4 { - font-size: 1.5rem; - } - - h5 { - font-size: 1.25rem; - } - - h6 { - font-size: 1rem; - } - - a, - a:visited { - color: #000; - text-decoration: underline; - word-wrap: break-word; - } - - table { - border-collapse: collapse; - } - - thead { - display: table-header-group; - } - - table, - th, - td { - border-bottom: 1px solid #000; - } - - td, - th { - padding: 8px 16px; - } - - img { - border: 0; - display: block; - max-width: 100% !important; - vertical-align: middle; - } - - hr { - border: 0; - border-bottom: 2px solid #bbb; - height: 0; - margin: 2.25rem 0; - padding: 0; - } - - dt { - font-weight: bold; - } - - dd { - margin: 0; - margin-bottom: 0.75rem; - } - - abbr[title], - acronym[title] { - border: 0; - text-decoration: none; - } - - table, - blockquote, - pre, - code, - figure, - li, - hr, - ul, - ol, - a, - tr { - page-break-inside: avoid; - } - - h2, - h3, - h4, - p, - a { - orphans: 3; - widows: 3; - } - - h1, - h2, - h3, - h4, - h5, - h6 { - page-break-after: avoid; - page-break-inside: avoid; - } - - h1 + p, - h2 + p, - h3 + p { - page-break-before: avoid; - } - - img { - page-break-after: auto; - page-break-before: auto; - page-break-inside: avoid; - } - - pre { - white-space: pre-wrap !important; - word-wrap: break-word; - } - - a[href^='http://']:after, - a[href^='https://']:after, - a[href^='ftp://']:after { - content: " (" attr(href) ")"; - font-size: 80%; - } - - abbr[title]:after, - acronym[title]:after { - content: " (" attr(title) ")"; - } - - #main { - max-width: 100%; - } - - .page { - margin: 0; - padding: 0; - width: 100%; - } - - .page-break, - .page-break-before { - page-break-before: always; - } - - .page-break-after { - page-break-after: always; - } - - .no-print { - display: none; - } - - a.no-reformat:after { - content: ''; - } - - abbr[title].no-reformat:after, - acronym[title].no-reformat:after { - content: ''; - } - - .page__hero-caption { - color: #000 !important; - background: #fff !important; - opacity: 1; - - a { - color: #000 !important; - } - } - -/* - Hide the following elements on print - ========================================================================== */ - - .masthead, - .toc, - .page__share, - .page__related, - .pagination, - .ads, - .page__footer, - .page__comments-form, - .author__avatar, - .author__content, - .author__urls-wrapper, - .nav__list, - .sidebar, - .adsbygoogle { - display: none !important; - height: 1px !important; - } -} diff --git a/_sass/minimal-mistakes/_pyos-blog-index.scss b/_sass/minimal-mistakes/_pyos-blog-index.scss deleted file mode 100644 index 74b72ec2..00000000 --- a/_sass/minimal-mistakes/_pyos-blog-index.scss +++ /dev/null @@ -1,426 +0,0 @@ -.layout--blog-index { - #main { - max-width: 100%; - overflow-x: clip; - } - - .archive { - float: none; - width: 100%; - max-width: $max-width; - margin-left: auto; - margin-right: auto; - padding-right: 0; - - @include breakpoint($large) { - width: 100%; - padding-right: 0; - } - - @include breakpoint($x-large) { - width: 100%; - padding-right: 0; - } - } -} - -.blog-index { - margin-top: 1.5rem; -} - -.blog-index__heading { - margin: 0 0 1.25rem; - font-family: $header-font-family; - font-size: $type-size-3; - font-weight: $semibold-weight; - color: $pyos-brand-dark-purple; -} - -$blog-card-media-ratio: 16 / 10; - -/* Featured row: 1 large + 2 stacked */ -.blog-index__featured { - display: grid; - gap: 1rem; - margin-bottom: 2.5rem; - - @media (min-width: $medium) { - grid-template-columns: 2fr 1fr; - grid-template-rows: repeat(2, minmax(0, 1fr)); - align-items: stretch; - } -} - -.blog-index__featured-primary { - @media (min-width: $medium) { - grid-column: 1; - grid-row: 1 / span 2; - } -} - -.blog-index__grid { - display: grid; - grid-template-columns: 1fr; - gap: 1.5rem; - - @media (min-width: $small) { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } - - @media (min-width: $medium) { - grid-template-columns: repeat(3, minmax(0, 1fr)); - } -} - -/* Shared card */ -.blog-card { - min-width: 0; - height: 100%; -} - -.blog-card__link { - display: flex; - flex-direction: column; - height: 100%; - overflow: hidden; - border: 1px solid rgba($pyos-brand-dark-purple, 0.12); - border-radius: $border-radius; - background: $pyos-white; - box-shadow: 0 2px 8px rgba($pyos-brand-dark-purple, 0.08); - color: inherit; - text-decoration: none; - transition: box-shadow 0.2s ease, transform 0.2s ease; - - &:hover, - &:focus-visible { - box-shadow: 0 6px 16px rgba($pyos-brand-dark-purple, 0.14); - transform: translateY(-2px); - } - - &:focus-visible { - outline: 2px solid $pyos-teal; - outline-offset: 2px; - } -} - -.blog-card__media { - overflow: hidden; - position: relative; -} - -.blog-card__image { - display: block; - width: 100%; - object-fit: cover; -} - -.blog-card__image--fallback { - min-height: 10rem; -} - -.blog-card__body { - display: flex; - flex: 1 1 auto; - flex-direction: column; - gap: 0.5rem; - padding: 1rem 1.1rem 1.15rem; -} - -.blog-card__badge { - display: inline-block; - align-self: flex-start; - padding: 0.2rem 0.55rem; - border-radius: 999px; - font-size: $type-size-card-meta; - line-height: $line-height-card-meta; - font-weight: $semibold-weight; - letter-spacing: 0.04em; - text-transform: uppercase; - color: $pyos-brand-dark-purple; - background: rgba($pyos-softpurple, 0.45); -} - -.blog-card__title { - margin: 0; - font-family: $header-font-family; - font-size: $type-size-card-title-lg; - line-height: 1.3; - color: $pyos-brand-dark-purple; -} - -.blog-card__meta { - margin: 0; - font-size: $type-size-card-meta; - line-height: $line-height-card-meta; - color: $muted-text-color; -} - -.blog-card__author { - &::before { - content: "·"; - margin: 0 0.35rem; - } -} - -.blog-card__excerpt { - margin: 0.25rem 0 0; - font-size: $type-size-card-body; - line-height: $line-height-card-body; - color: $text-color; -} - -/* Featured (large) card */ -.blog-card--featured { - height: 100%; - - .blog-card__media { - aspect-ratio: $blog-card-media-ratio; - } - - .blog-card__image, - .blog-card__image--fallback { - position: absolute; - inset: 0; - width: 100%; - height: 100%; - min-height: unset; - } - - .blog-card__title { - font-size: $type-size-blog-featured-title; - } - - .blog-card__excerpt { - font-size: $type-size-blog-featured-excerpt; - line-height: $line-height-blog-featured-excerpt; - } - - @media (min-width: $medium) { - .blog-card__link { - height: 100%; - } - } -} - -/* Compact featured cards (stacked right) */ -.blog-card--compact { - .blog-card__image, - .blog-card__image--fallback { - min-height: 7.5rem; - } - - .blog-card__body { - padding: 0.85rem 1rem 1rem; - } - - .blog-card__title { - font-size: $type-size-card-title; - } -} - -/* Standard grid card */ -.blog-card--grid { - .blog-card__media { - aspect-ratio: $blog-card-media-ratio; - } - - .blog-card__image, - .blog-card__image--fallback { - position: absolute; - inset: 0; - width: 100%; - height: 100%; - min-height: unset; - } - - .blog-card__media-scrim { - position: absolute; - inset: 0; - z-index: 1; - background: linear-gradient( - to top, - rgba($pyos-brand-dark-purple, 0.92) 0%, - rgba($pyos-brand-dark-purple, 0.5) 42%, - rgba($pyos-brand-dark-purple, 0.15) 100% - ); - } - - .blog-card__title--overlay { - position: absolute; - right: 0; - bottom: 0; - left: 0; - z-index: 2; - margin: 0; - padding: 0.85rem 1rem 1rem; - font-family: $header-font-family; - font-size: $type-size-3; - font-weight: $semibold-weight; - line-height: 1.2; - color: $pyos-white; - } - - .blog-card__body { - flex: 1 1 auto; - gap: 0.45rem; - } - - .blog-card__excerpt { - flex: 1 1 auto; - margin: 0; - display: -webkit-box; - -webkit-box-orient: vertical; - -webkit-line-clamp: 3; - overflow: hidden; - } - - .blog-card__meta { - margin-top: auto; - padding-top: 0.35rem; - } -} - -@media screen and (max-width: $mobile) { - .blog-card--featured { - .blog-card__title { - font-size: $type-size-blog-featured-title; - } - } -} - -/* From the archives — full-bleed band, centered content */ -.blog-index__archives { - width: 100vw; - max-width: 100vw; - margin-top: 3rem; - margin-left: calc(50% - 50vw); - margin-right: calc(50% - 50vw); - padding: 2.5rem clamp(1rem, 4vw, 2rem); - box-sizing: border-box; - background: rgba($pyos-softpurple, 0.35); -} - -.blog-index__archives-inner { - max-width: $max-width; - margin-left: auto; - margin-right: auto; - width: 100%; - box-sizing: border-box; -} - -.blog-index__archives-heading { - margin: 0 0 1.5rem; - font-family: $header-font-family; - font-size: $type-size-3; - font-weight: $semibold-weight; - text-align: left; - color: $pyos-brand-dark-purple; -} - -.blog-index__archives-grid { - display: grid; - grid-template-columns: 1fr; - gap: 1.25rem; - - @media (min-width: $small) { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } - - @media (min-width: $medium) { - grid-template-columns: repeat(3, minmax(0, 1fr)); - } -} - -.blog-archive-card { - display: flex; - flex-direction: column; - gap: 0.65rem; - height: 100%; - padding: 1.15rem 1.25rem 1.25rem; - border: 1px solid rgba($pyos-brand-dark-purple, 0.1); - border-radius: $border-radius; - background: $pyos-white; - box-shadow: 0 2px 6px rgba($pyos-brand-dark-purple, 0.06); -} - -.blog-archive-card__meta-row { - display: flex; - flex-wrap: wrap; - align-items: center; - justify-content: space-between; - gap: 0.5rem; -} - -.blog-archive-card__date { - font-size: $type-size-card-meta; - line-height: $line-height-card-meta; - color: $muted-text-color; -} - -.blog-archive-card__badge { - display: inline-block; - padding: 0.15rem 0.5rem; - border-radius: 999px; - font-size: $type-size-card-meta; - line-height: $line-height-card-meta; - font-weight: $semibold-weight; - letter-spacing: 0.04em; - text-transform: uppercase; - color: $pyos-brand-dark-purple; - background: rgba($pyos-softpurple, 0.5); -} - -.blog-archive-card__title { - margin: 0; - font-family: $header-font-family; - font-size: $type-size-card-title-lg; - line-height: 1.3; - - a { - color: $pyos-brand-dark-purple; - text-decoration: underline; - text-decoration-color: rgba($pyos-brand-dark-purple, 0.35); - text-underline-offset: 0.15em; - - &:hover, - &:focus-visible { - color: $pyos-darkpurple; - text-decoration-color: currentColor; - } - - &:focus-visible { - outline: 2px solid $pyos-teal; - outline-offset: 2px; - } - } -} - -.blog-archive-card__excerpt { - margin: 0; - flex: 1 1 auto; - font-size: $type-size-card-body; - line-height: $line-height-card-body; - color: $text-color; -} - -.blog-archive-card__read-more { - align-self: flex-start; - font-size: $type-size-card-meta; - line-height: $line-height-card-meta; - font-weight: $semibold-weight; - color: $pyos-brand-dark-purple; - text-decoration: underline; - text-decoration-color: rgba($pyos-brand-dark-purple, 0.35); - text-underline-offset: 0.15em; - - &:hover, - &:focus-visible { - color: $pyos-darkpurple; - text-decoration-color: currentColor; - } - - &:focus-visible { - outline: 2px solid $pyos-teal; - outline-offset: 2px; - } -} diff --git a/_sass/minimal-mistakes/_pyos-cards.scss b/_sass/minimal-mistakes/_pyos-cards.scss deleted file mode 100644 index 3cf99e5a..00000000 --- a/_sass/minimal-mistakes/_pyos-cards.scss +++ /dev/null @@ -1,1324 +0,0 @@ -/* ========================================================================== - BASE CARD STYLES - ========================================================================== */ - -// Base card styles - shared by both people and package cards -.cards { - top: 0px; - position: relative; - background-color: #fff;//#fcfbf5; - text-decoration: none; - z-index: 1; - overflow: hidden; - font-size: $type-size-card-title; - box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15); - margin: .5em; - - &__image { - max-height: calc($small / 3); - overflow: hidden; - border-radius: 10px; - } - - &:hover { - transition: all 0.2s ease-out; - transform: translateY(-3px); - box-shadow: 0 6px 14px rgba(0, 0, 0, 0.2); - top: -4px; - border: 1px solid #cccccc; - background-color: white; - cursor: pointer; - } - - &:hover:before { - transform: scale(2.15); - } - - p { - font-size: $type-size-card-body; - line-height: $line-height-card-body; - } - - h2 { - font-size: $type-size-4; - margin-bottom: 1rem!important; - - a { - text-decoration: none; - font-size: $type-size-card-title-lg; - } - - &.archive__item-title { - font-size: $type-size-card-title !important; - } - } - - h3.card__title.no_toc, - .card__title.no_toc { - margin-top: 1.2em; - font-size: $type-size-3; - color: $pyos-darkpurple; - } - - .page__meta.contributors { - font-size: $type-size-card-body; - line-height: $line-height-card-meta; - } - - .contributors a { - text-decoration: none; - } - - .package_description { - font-size: $type-size-card-body; - line-height: $line-height-card-body; - } - - ul { - list-style-type: none; - margin-left: 0; - padding: .2em; - - li { - font-size: $type-size-card-title; - a { - text-decoration: none; - //font-size: 1.2em - } - } - } -} - -.cards-image figure { - margin: 0; -} - - -.tutorial { - font-size: $type-size-card-title; - - & p.page__meta { - padding-top: 1.3em; - } -} - -.card .page__meta { - font-size: $type-size-card-meta !important; -} - -// Used on /learn.html and packaging-resources.md; changes apply to both. -.tutorial__container { - display: grid; - grid-gap: 20px; - grid-template-columns: 1fr; // Narrow: 1 column - - @media (min-width: $small) { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } - - @media (min-width: $medium) { - grid-template-columns: repeat(3, minmax(0, 1fr)); - } - - @media (min-width: $x-large) { - grid-template-columns: repeat(4, minmax(0, 1fr)); - } - - // Ensure all cards have the height of the tallest card - align-items: stretch; - - & a.btn.btn--success.btn--large { - background-color: $pyos-darkpurple!important; - color: $pyos-lightpurple!important; - border-bottom-left-radius: 10px; - border-bottom-right-radius: 10px; - border-top-left-radius: 0; - border-top-right-radius: 0; - margin-bottom: 0!important; - font-size: $type-size-card-title; - - } - - & .fa-solid .fa-magnifying-glass{ - color: #81c0aa; - } - - .card { - font-size: $type-size-card-title; - display: flex; - flex-direction: column; - background-color: #fff; - border: 1px solid #ddd; - border-radius: 8px; - box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1); - - .excerpt { - font-size: $type-size-card-body; - padding: 0.75em 1em 1em; - line-height: $line-height-card-body; - } - h4.title { - color: $pyos-deeppurple!important; - font-size: $type-size-card-title-lg; - margin: 0; - font-weight: $semibold-weight; - line-height: 1.3; - } - .title-block { - background-color: #ecbee3; - display: flex; - align-items: center; - min-height: 4.25em; - height: auto; - padding: 0.85em 1em; - border-bottom: $pyos-darkpurple 1.3px solid; - border-top-left-radius: 10px; - border-top-right-radius: 10px; - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - } - - img { - width: 100%; - height: auto; - } - - .content { - flex: 1; - } - ul li { - font-size: $type-size-card-meta; - margin: 0 0 .5em 0; - } - ul { - padding-right: .05em; - padding-bottom: .2em; - } - } - -} - - -// Theme uses .btn.btn--success + yiq (light text); we want dark text on green. -.btn.btn--success, -.btn.btn--success:visited { - color: #000 !important; -} - -.btn.btn--success:hover { - color: #000 !important; -} - - -.subsection { - padding: 0 2em; - - h3 { - padding-left: 0.5em; - } -} - - -// events -.event-card { - display: flex; - min-width: 0; - margin-bottom: 1.5rem; - border: 1px solid #ddd; - border-radius: 4px; - overflow: hidden; - transition: box-shadow 0.3s ease, transform 0.3s ease; - max-width: min(100%, 1000px); - - &:hover { - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); - transform: scale(1.03); - } - - a { - text-decoration: none; - color: inherit; - } - - /* Non-compact cards: link wraps the flex row; must shrink on narrow viewports. */ - & > a { - display: flex; - flex: 1 1 auto; - min-width: 0; - max-width: 100%; - } - &__title { - width: 100%; - } - - &__content { - display: flex; - flex: 1 1 auto; - flex-grow: 1; - min-width: 0; - } - - &__date { - position: relative; - width: 180px; // Increased width for the date box - min-width: 180px; - flex-shrink: 0; - display: flex; - align-items: center; - justify-content: center; - background-color: #f5f5f5; - padding: 1rem; - height: 100%; - - &-background { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-size: cover; - background-position: center; - opacity: 0.1; - } - - &-text { - position: relative; - z-index: 1; - text-align: center; - font-size: $type-size-4; - font-weight: $bold-weight; - line-height: 1.2; - - span { - display: block; - } - - span:last-of-type { - text-transform: uppercase; - font-size: $type-size-card-label; - margin-top: 0.2rem; - } - } - } - - &__details { - flex: 1 1 auto; - flex-grow: 1; - min-width: 0; - padding: 1rem; - padding-top: 2.8rem; - display: flex; - flex-direction: column; - justify-content: center; - overflow-wrap: break-word; - position: relative; - - h3 { - margin-bottom: 0.5rem; - } - - p { - margin-bottom: 1rem; - } - - &__button { - padding: 0.5rem 1rem; - background-color: #007bff; - color: #fff; - border-radius: 4px; - text-decoration: none; - transition: background-color 0.3s ease; - - &:hover { - background-color: #0056b3; - } - } - } - - &__type { - position: absolute; - top: 0.8rem; - right: 0.8rem; - display: inline-flex; - align-items: center; - border-radius: 9999px; - padding: 0.2rem 0.6rem; - font-size: $type-size-card-meta; - line-height: 1.2; - font-weight: $semibold-weight; - white-space: nowrap; - } - - &__type--training { - background-color: $pyos-teal; - color: $pyos-brand-dark-purple; - } - - &__type--sprint { - background-color: $pyos-lightpurple; - color: $pyos-brand-dark-purple; - } - - &__type--talk { - background-color: $pyos-softpurple; - color: $pyos-brand-dark-purple; - } - - &__type--community { - background-color: $pyos-brand-dark-purple; - color: $pyos-white; - } - } - - -// Modifications for upcoming events -.upcoming { - & .event-card__date-background { - opacity: 1; - } - .event-card__date-text { - color: #fff; - } - -} - -// Landing page upcoming events list: smaller card, less padding -.upcoming-events-list .event-card { - max-width: min(100%, 800px); - min-width: 0; - margin-bottom: 0; - - .event-card__date { - width: 140px; - min-width: 140px; - padding: 0.5rem; - } - - .event-card__date-text { - font-size: $type-size-card-title; - - span:last-of-type { - font-size: $type-size-7; - } - } - - .event-card__details { - min-width: 0; - overflow-wrap: break-word; - padding: 0.5rem 0.75rem; - - h3 { - margin-bottom: 0.35rem; - font-size: $type-size-card-title-lg; - } - - p { - margin-bottom: 0.5rem; - font-size: $type-size-card-meta; - } - - .event-card__button { - padding: 0.35rem 0.75rem; - font-size: $type-size-card-meta; - } - } -} - -// Compact home-page upcoming events variant -.upcoming-events-list .event-card--compact { - margin-bottom: 0; - border: 1px solid mix(#000, $pyos-brand-dark-purple, 20%); - border-radius: 14px; - background: $pyos-white; - box-sizing: border-box; - width: 100%; - max-width: 100%; - min-width: 0; - overflow-wrap: break-word; - color: $pyos-brand-dark-purple; - - &:hover { - transform: none; - box-shadow: 0 4px 14px rgba(#000, 0.2); - } - - .event-card__content { - display: block; - min-width: 0; - width: 100%; - max-width: 100%; - } - - .event-card__details { - min-width: 0; - max-width: 100%; - padding: 2.45rem 1.25rem 1.05rem; - } - - .event-card__date-line { - margin: 0 0 0.35rem; - font-size: $type-size-card-meta; - font-weight: $semibold-weight; - letter-spacing: 0.06em; - text-transform: uppercase; - color: $pyos-teal; - } - - .event-card__title { - margin: 0 0 0.2rem; - font-size: $type-size-3; - line-height: 1.25; - overflow-wrap: break-word; - } - - .event-card__location { - margin: 0 0 0.55rem; - font-size: $type-size-card-title; - color: mix(#000, $pyos-brand-dark-purple, 18%); - overflow-wrap: break-word; - } - - .event-card__excerpt { - margin: 0 0 0.65rem; - font-size: $type-size-card-body; - line-height: $line-height-card-body; - color: mix(#000, $pyos-brand-dark-purple, 22%); - overflow-wrap: break-word; - } - - .event-card__button { - display: inline-flex; - align-items: center; - gap: 0.25rem; - margin: 0; - padding: 0; - border: 0; - background: transparent; - color: $pyos-brand-dark-purple; - font-size: $type-size-card-title; - font-weight: $semibold-weight; - text-decoration: none; - } -} - - - // Responsive design adjustments - @media (max-width: 600px) { - .event-card { - flex-direction: column; - - & > a { - flex-direction: column; - width: 100%; - } - - &__date { - width: 100%; - min-width: auto; - height: auto; - padding: 0.5rem; - } - - &__details { - padding: 1rem 0.5rem; - } - - &__content { - flex-direction: column; - - } - } - } - - - - - - -.upcoming-event { - background-color: $pyos-lightpurple; - border-radius: 10px; - -webkit-border-radius: 10px; - -moz-border-radius: 10px; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); - -webkit-box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); - -moz-box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); - padding: .1em 2em 2em 2em; - width: 100%; - - &:hover { - background-color: darken-color($pyos-lightpurple, 10%); - } - &.btn { - width: 40%; - } -} - -/* ========================================================================== - EVENTS PAGE — upcoming section + past events grid - ========================================================================== */ - -// Upcoming events: prominent section with a tinted background -.events-page-upcoming-section { - background: $pyos-lightpurple; - border-radius: $border-radius; - padding: 2rem 2rem 1.5rem; - margin-bottom: 2.5rem; - - h2 { - margin-top: 0; - } - - .event-card { - max-width: 100%; - border-color: $pyos-softpurple; - box-shadow: 0 2px 12px rgba(0, 0, 0, 0.12); - } -} - -// Past events: CSS grid layout (3 cols desktop → 2 tablet → 1 mobile) -.event__grid { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 1rem; - margin-bottom: 1rem; - - @media (max-width: $medium-wide) { - grid-template-columns: repeat(2, 1fr); - } - - @media (max-width: $small) { - grid-template-columns: 1fr; - } -} - -// Compact cards inside the past-events grid -.event__grid .event-card--compact { - margin-bottom: 0; - border: 1px solid mix(#000, $pyos-brand-dark-purple, 20%); - border-radius: 12px; - background: $pyos-white; - box-sizing: border-box; - width: 100%; - max-width: 100%; - min-width: 0; - overflow-wrap: break-word; - color: $pyos-brand-dark-purple; - height: 100%; - - &:hover { - transform: none; - box-shadow: 0 4px 14px rgba(0, 0, 0, 0.15); - } - - .event-card__content { - display: block; - min-width: 0; - width: 100%; - height: 100%; - } - - .event-card__details { - min-width: 0; - max-width: 100%; - padding: 2.2rem 1.1rem 0.85rem; - display: flex; - flex-direction: column; - height: 100%; - } - - .event-card__date-line { - margin: 0 0 0.3rem; - font-size: $type-size-card-meta; - font-weight: $semibold-weight; - letter-spacing: 0.06em; - text-transform: uppercase; - color: $pyos-teal; - } - - .event-card__title { - margin: 0 0 0.2rem; - font-size: $type-size-card-title-lg; - line-height: 1.25; - overflow-wrap: break-word; - } - - .event-card__location { - margin: 0 0 0.4rem; - font-size: $type-size-card-meta; - color: mix(#000, $pyos-brand-dark-purple, 18%); - overflow-wrap: break-word; - } - - .event-card__excerpt { - margin: 0 0 0.6rem; - font-size: $type-size-card-meta; - line-height: $line-height-card-body; - color: mix(#000, $pyos-brand-dark-purple, 30%); - overflow-wrap: break-word; - flex: 1; - } - - .event-card__button { - display: inline-flex; - align-items: center; - gap: 0.25rem; - margin: 0; - padding: 0; - border: 0; - background: transparent; - color: $pyos-brand-dark-purple; - font-size: $type-size-card-meta; - font-weight: $semibold-weight; - text-decoration: none; - } -} - -/* ========================================================================== - PEOPLE CARDS - ========================================================================== */ - -// Base people card styles - semantic nesting approach -.people-card { - padding: 0; - .people-card__content { - // Ensure content wrapper has no padding (overrides div > div rule) - padding: 0; - overflow: hidden; // Ensures image doesn't overflow card boundaries - - // Image: full width, no padding - breaks out to card edges - .person_img { - filter: drop-shadow(3px 3px 3px #999); - margin: 0; - padding: 0; - width: 100%; - display: block; - overflow: hidden; - - img { - width: 100%; - aspect-ratio: 1; - object-fit: cover; - display: block; - margin: 0; - padding: 0; - } - } - - // Text elements: semantic selectors with padding - h4.person_name { - padding: 0; - margin-top: 0.6em; - margin-bottom: 0; - font-size: $type-size-card-title-xl; - - a { - text-decoration: none; - } - } - - p.page__meta { - padding: 0 1em; - // Inherits other styles from _page.scss - } - - p.contrib_org { - padding: 0 1em; - font-size: $type-size-card-meta; - font-weight: $weight-4; - margin-bottom: 0 !important; - } - - .ppl_social { - padding: 0 1em 1em 1em; // includes bottom padding - font-size: $type-size-card-meta; - font-weight: $weight-4; - margin-bottom: 0 !important; - - a { - margin-right: 10px; - text-decoration: none !important; - } - - // Font Awesome icons inherit font-size from parent - .fa-brands, - .fa-solid { - font-size: inherit; - } - } - } -} - -// Responsive adjustments (align with home page card scale: padding 0.75em, title 1rem, body 0.875rem) -@media screen and (max-width: $mobile) { - .people-card .people-card__content { - padding: 0.75em; - } - - .people-card { - h4.person_name { - font-size: $type-size-card-title; - } - - p.page__meta, - p.contrib_org, - .ppl_social { - font-size: $type-size-card-meta; - } - } - - .upcoming-events-list .event-card { - .event-card__date { - padding: 0.75em; - } - .event-card__date-text { - font-size: $type-size-card-title; - span:last-of-type { - font-size: $type-size-card-label; - } - } - .event-card__details { - padding: 0.75em; - h3 { - font-size: $type-size-card-title; - } - p { - font-size: $type-size-card-meta; - } - } - } -} - -/* ========================================================================== - EVENT INSTRUCTORS (front matter + include) - ========================================================================== */ - -.event-instructors { - margin-top: 1em; - margin-bottom: 1em; -} - -/* University partnerships: responsive tables for tier and pricing */ -.partnership-table-wrap { - width: 100%; - overflow-x: auto; - margin-bottom: 1.5rem; -} - -.partnership-table { - /* Override theme `table { display: block }` so columns lay out correctly. */ - display: table; - width: 100%; - min-width: 52rem; - border-collapse: collapse; - border: 1px solid rgba($pyos-brand-dark-purple, 0.25); - background-color: $pyos-white; - /* Theme tables use overflow-x: auto; with display:table it skews card layout. */ - overflow-x: visible; - - th, - td { - padding: 0.7rem 0.75rem; - border: 1px solid rgba($pyos-brand-dark-purple, 0.2); - text-align: left; - vertical-align: top; - line-height: 1.35; - font-size: $type-size-card-meta; - color: $dark-gray; - } - - th { - background-color: rgba($pyos-brand-dark-purple, 0.08); - color: $pyos-brand-dark-purple; - font-weight: $semibold-weight; - } - - td:first-child { - font-weight: $semibold-weight; - color: $pyos-brand-dark-purple; - } - - tbody tr:nth-child(even) { - background-color: rgba($pyos-brand-dark-purple, 0.03); - } -} - -.partnership-table--tiers { - min-width: 42rem; -} - -.partnership-check { - color: $pyos-teal; - font-weight: $bold-weight; -} - -@media (max-width: $large) { - .partnership-table { - display: block; - width: 100%; - min-width: 0; - border: 0; - overflow-x: visible; - - thead { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; - } - - tbody, - tr, - td { - display: block; - width: 100%; - } - - tr { - margin-bottom: 0.8rem; - border: 1px solid rgba($pyos-brand-dark-purple, 0.24); - border-radius: 0.5rem; - background-color: $pyos-white; - padding: 0; - overflow: hidden; - box-sizing: border-box; - } - - td { - border: 0; - border-bottom: 1px solid rgba($pyos-brand-dark-purple, 0.14); - padding: 0.4rem 0.75rem; - display: flex; - justify-content: space-between; - align-items: flex-start; - gap: 0.7rem; - box-sizing: border-box; - min-width: 0; - - &::before { - content: attr(data-label); - font-weight: $semibold-weight; - color: $pyos-brand-dark-purple; - flex-shrink: 0; - } - } - - td:first-child { - background-color: rgba($pyos-brand-dark-purple, 0.08); - color: $pyos-brand-dark-purple; - margin: 0; - padding: 0.65rem 0.75rem; - border-bottom: 1px solid rgba($pyos-brand-dark-purple, 0.18); - border-radius: 0.5rem 0.5rem 0 0; - font-size: $type-size-card-title; - font-weight: $semibold-weight; - - &::before { - color: $pyos-brand-dark-purple; - } - } - - td:last-child { - border-bottom: 0; - padding-bottom: 0.55rem; - } - } -} - -/* University partnerships: tier cards */ -.partnership-cards { - display: grid; - gap: 1.25rem; - grid-template-columns: 1fr; - margin: 0 0 1.5rem; - align-items: stretch; - - @media (min-width: $small) { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } - - @media (min-width: $medium) { - grid-template-columns: repeat(3, minmax(0, 1fr)); - } -} - -.partnership-card { - display: flex; - flex-direction: column; - min-height: 100%; - padding: 1.1rem 1rem; - border: 1px solid rgba($pyos-brand-dark-purple, 0.18); - border-radius: $border-radius; - background-color: $pyos-white; - box-shadow: 0 0.2rem 0.65rem rgba($pyos-brand-dark-purple, 0.09); - transition: transform 200ms ease, box-shadow 200ms ease; - - &:hover, - &:focus-within { - transform: translateY(-0.125rem); - box-shadow: 0 0.38rem 0.85rem rgba($pyos-brand-dark-purple, 0.12); - } - - &__stage { - display: inline-flex; - align-self: flex-start; - margin: 0 0 0.65rem; - padding: 0.2rem 0.6rem; - border-radius: 9999px; - background-color: $pyos-lightpurple; - color: $pyos-brand-dark-purple; - font-size: $type-size-card-label; - font-weight: $semibold-weight; - letter-spacing: 0.04em; - text-transform: uppercase; - line-height: $line-height-card-meta; - } - - &__title { - margin: 0.6rem 0 1.2rem 0; - color: $pyos-brand-dark-purple; - font-size: $type-size-card-title-xl; - line-height: 1.25; - border-left: 0; - padding-left: 0; - font-weight: $weight-5; - text-align: center; - - &::before, - &::after { - content: none; - } - } - - p.partnership-card__price { - margin: 0 0 0.85rem; - color: $pyos-brand-dark-purple; - font-size: $type-size-2; - font-weight: $bold-weight; - line-height: 1.2; - } - - &__body { - flex: 1 1 auto; - display: flex; - flex-direction: column; - min-height: 0; - } - - &__main { - flex: 1 1 auto; - min-height: 0; - display: flex; - flex-direction: column; - } - - .partnership-card__body > p.partnership-card__price { - flex-shrink: 0; - margin-top: 0; - margin-bottom: 0.35rem; - text-align: center; - } - - .partnership-card__body:has(> p.partnership-card__price) + a.partnership-card__cta { - margin-top: 0.5rem; - } - - a.partnership-card__cta { - flex-shrink: 0; - align-self: stretch; - margin: 1rem 0 0; - padding-top: 0.25rem; - text-align: center; - text-decoration: none; - } - - p.partnership-card__lede { - margin: 0 0 0.85rem; - color: rgba($pyos-brand-dark-purple, 0.9); - font-size: $type-size-card-body; - line-height: $line-height-card-body; - border-left: 0; - padding-left: 0; - - &::before, - &::after { - content: none; - } - } - - &__features { - list-style: none; - margin: 0; - padding: 0; - - li { - position: relative; - margin: 0 0 0.55rem; - padding-left: 1.3rem; - color: rgba($pyos-brand-dark-purple, 0.95); - font-size: $type-size-card-meta; - line-height: $line-height-card-body; - - &::before { - content: "✓"; - position: absolute; - left: 0; - top: 0.02rem; - color: $pyos-teal; - font-weight: $semibold-weight; - } - - &:last-child { - margin-bottom: 0; - } - } - } - - &__divider { - width: 100%; - border: 0; - border-top: 1px solid rgba($pyos-softpurple, 0.95); - margin: 1rem 0 0.7rem; - } - - p.partnership-card__subheader { - margin: .6rem 0 1.2rem 0; - color: mix($pyos-brand-dark-purple, $pyos-softpurple, 45%); - font-size: $type-size-card-label; - font-weight: $semibold-weight; - letter-spacing: 0.08em; - text-transform: uppercase; - line-height: $line-height-card-meta; - } -} - -.partnership-card--featured { - border-color: rgba($pyos-darkpurple, 0.45); - box-shadow: 0 0.32rem 0.8rem rgba($pyos-brand-dark-purple, 0.13); - - .partnership-card__stage { - background-color: rgba($pyos-teal, 0.28); - } -} - -/* Home page training feature card */ -.home-training-feature { - margin: 0 0 2rem; - - &__eyebrow { - margin: 0 0 0.8rem; - font-size: $type-size-card-title-lg; - font-weight: $semibold-weight; - letter-spacing: 0.08em; - text-transform: uppercase; - } - - &__card { - display: flex; - align-items: stretch; - justify-content: space-between; - gap: clamp(0.65rem, 0.5rem + 2vw, 1.25rem); - padding: clamp(0.65rem, 0.5rem + 2.5vw, 1.25rem) - clamp(0.75rem, 0.45rem + 3vw, 1.5rem); - border-radius: 14px; - background: $pyos-feature-card-bg; - border: 1px solid rgba($pyos-brand-dark-purple, 0.18); - } - - &__content { - min-width: 0; - flex: 1 1 auto; - display: flex; - flex-direction: column; - align-items: flex-start; - } - - &__badge { - display: inline-block; - margin: 0 0 0.65rem; - padding: 0.2rem 0.55rem; - border-radius: 999px; - font-size: $type-size-card-meta; - line-height: $line-height-card-meta; - color: $pyos-brand-dark-purple; - background: rgba($pyos-white, 0.55); - } - - &__title { - margin: 0 0 0.35rem; - font-size: $type-size-3; - line-height: 1.2; - color: $pyos-brand-dark-purple; - } - - &__excerpt { - margin: 0; - color: rgba($pyos-brand-dark-purple, 0.9); - font-size: $type-size-card-body; - line-height: $line-height-card-body; - max-width: 46ch; - } - - &__button { - margin-top: 0.9rem; - border-color: rgba($pyos-brand-dark-purple, 0.7); - color: $pyos-brand-dark-purple; - - &:hover { - background-color: $pyos-brand-dark-purple; - color: $pyos-white; - text-decoration: none; - } - } - - &__image-wrap { - flex: 0 0 36%; - min-height: 170px; - border-radius: 10px; - overflow: hidden; - background: rgba($pyos-brand-dark-purple, 0.06); - border: 1px solid rgba($pyos-brand-dark-purple, 0.16); - } - - &__image { - width: 100%; - height: 100%; - display: block; - object-fit: cover; - } - - @media (max-width: $medium) { - &__card { - flex-direction: column; - align-items: flex-start; - } - - &__image-wrap { - width: 100%; - min-height: 150px; - } - - &__button { - width: auto; - } - } - - @media (max-width: $home-training-feature-tight-max) { - margin-bottom: 1.25rem; - - &__eyebrow { - margin-bottom: 0.5rem; - } - - &__image-wrap { - min-height: 130px; - } - - &__button { - margin-top: 0.65rem; - } - } -} - -.event-instructors__grid { - grid-template-columns: 1fr !important; -} - -.event-instructor { - .people-card__content { - display: flex; - flex-direction: row; - align-items: center; - gap: 1.25rem; - text-align: left; - - .event-instructor__avatar { - flex-shrink: 0; - width: 160px; - min-width: 160px; - padding: 0 20px; - box-sizing: border-box; - display: flex; - align-items: center; - justify-content: center; - - img { - width: 120px; - height: 120px; - border-radius: 50%; - object-fit: cover; - } - } - - .event-instructor__text { - flex: 1; - min-width: 0; - display: flex; - flex-direction: column; - align-items: flex-start; - } - - .person_name { - width: 100%; - display: flex; - align-items: center; - justify-content: flex-start; - flex-wrap: wrap; - gap: 0.35em; - text-align: left; - margin-left: 0; - margin-right: 0; - - .event-instructor__github-link { - opacity: 0.7; - font-size: $type-size-card-meta; - - &:hover { - opacity: 1; - } - } - } - - .event-instructor__bio { - padding: 0; - margin-top: 0.5em; - margin-bottom: 0; - font-size: $type-size-card-meta; - line-height: 1.5; - text-align: left; - } - } -} - -@media screen and (max-width: $small) { - .event-instructor .people-card__content { - flex-direction: column; - align-items: center; - text-align: center; - - .event-instructor__avatar { - width: auto; - min-width: 0; - padding: 0; - } - - .event-instructor__text { - align-items: center; - text-align: center; - } - - .person_name { - justify-content: center; - text-align: center; - } - - .event-instructor__bio { - text-align: center; - } - } -} diff --git a/_sass/minimal-mistakes/_pyos-connect.scss b/_sass/minimal-mistakes/_pyos-connect.scss deleted file mode 100644 index fddced5c..00000000 --- a/_sass/minimal-mistakes/_pyos-connect.scss +++ /dev/null @@ -1,49 +0,0 @@ -/* ========================================================================== - Connect with pyOpenSci footer block (extends .notice) - ========================================================================== */ - -.notice.connect-with-pyos { - margin: $connect-notice-margin-y 0 !important; - padding: $connect-notice-padding; - line-height: $connect-notice-line-height !important; - - .header { - margin-bottom: $connect-notice-header-margin; - font-size: $connect-notice-header-size !important; - } - - > p { - margin-top: 0; - margin-bottom: $connect-notice-paragraph-spacing; - } - - ul { - margin-top: $connect-notice-list-margin-top; - margin-bottom: $connect-notice-list-margin-bottom; - - li { - margin-bottom: $connect-notice-list-item-spacing; - } - - &:last-child { - margin-bottom: 0; - } - } - - ul.connect-with-pyos__social { - display: flex; - flex-wrap: wrap; - gap: $connect-notice-social-gap-y $connect-notice-social-gap-x; - list-style: none; - padding-left: 0; - margin-bottom: $connect-notice-list-margin-bottom; - - li { - margin-bottom: 0; - } - } - - @include breakpoint-max-width($mobile) { - padding: $connect-notice-padding-mobile; - } -} diff --git a/_sass/minimal-mistakes/_pyos-dropdown.scss b/_sass/minimal-mistakes/_pyos-dropdown.scss deleted file mode 100644 index e39eae2f..00000000 --- a/_sass/minimal-mistakes/_pyos-dropdown.scss +++ /dev/null @@ -1,355 +0,0 @@ -/* ========================================================================== - pyos custom drop down menus - - Styles for the *contents* of the menus and the - menu buttons. For layout within the page, - see _masthead.scss - ========================================================================== */ - -.nav__links { - overflow: visible; - margin: 0 auto; - padding: 0; - display: flex; - align-items: stretch; - gap: 1em; - clear: both; - list-style-type: none; - - a { - float: left; - display: block; - color: $nav-font-color !important; - text-align: center; - padding: 14px 12px; - text-decoration: none; - transition: $global-transition; - font-size: 0.9em; - } - - a:hover { - color: $nav-hover-color !important; - } - - .icon { - display: none; - } - - @media (max-width: $medium-wide) { - gap: 0.4em; - } - - @include breakpoint($large) { - gap: 0.6em; - } -} - -/* NAVIGATION MOBILE vertical stacked menu with a soft drop shadow */ - .nav__links.vertical { - position: absolute; - display: flex; - flex-direction: column; - align-items: flex-start; - padding: 1em; - margin-top: 0.5em; - top: 100%; - left: 5%; - right: 5%; - gap: 0.25em; - font-size: 1em; - height: auto; - max-height: calc(100vh - 6rem); - overflow-y: auto; - background-color: $pyos-white; - z-index: 1; - border: 1px solid $border-color; - border-radius: 4px; - -webkit-box-shadow: - 0 2px 4px 0 rgba(0, 0, 0, 0.16), - 0 2px 10px 0 rgba(0, 0, 0, 0.12); - box-shadow: - 0 2px 4px 0 rgba(0, 0, 0, 0.16), - 0 2px 10px 0 rgba(0, 0, 0, 0.12); - - > li { - width: 100%; - padding: 0.5em 0; - display: block; - align-items: initial; - } - - a { - float: none; - display: block; - text-align: left; - padding: 0.5em 0; - font-size: 1em; - color: $nav-font-color; - } - - .dropdown { - width: 100%; - position: static; - overflow: visible; - - .dropbtn { - width: 100%; - text-align: left; - display: block; - padding: 0.75em 0; - cursor: pointer; - } - - .dropdown-content { - display: none; - position: relative; - width: 100%; - min-width: 100%; - box-shadow: none; - background-color: transparent; - padding-left: 1em; - margin-top: 0.5em; - top: auto; - left: 0; - - &.open { - display: block; - } - } - } - } - -/* down arrow works better with ascii vs font awesome */ -.downBtn { - font-size: 0.8em; -} - -/* Dropdown button with arrow on right */ -.dropdown .dropbtn { - display: flex; - align-items: center; - gap: 0.4em; - font-size: 0.9em !important; - - .dropbtn-text { - flex: 0 0 auto; - } - - .dropbtn-arrow { - flex: 0 0 auto; - font-size: 0.85em; - margin-left: 0.2em; - } -} - -/* Dropdown container for nav links */ -.dropdown { - float: left; - overflow: hidden; - - .dropbtn { - border: none; - outline: none; - color: $nav-font-color; - height: 100%; - background-color: inherit; - font-family: $header-font; - margin: 0; - line-height: 1em; - padding: 14px 12px; - } - - /* Style the dropdown content (hidden by default) */ - .dropdown-content { - display: none; - background-color: $pyos-white; - min-width: 160px; - max-width: 25%; - box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2); - z-index: 1; - width: 100%; - top: 100%; - transition: all 0.25s ease-in; - transform: translateY(0px); - position: absolute; - - &.open { - display: block; - } - - li { - list-style-type: none; - } - - a { - float: none; - padding: 12px 16px; - text-decoration: none; - display: block; - text-align: left; - white-space: nowrap; - - &:hover { - background-color: $pyos-white; - color: $nav-hover-color; - } - } - } -} - -/* End nav links bar styles */ - -/* Begin burger stylin */ - -.hamburger__btn-toggle { - display: none; - align-self: center; - height: 2rem; - border: 0; - outline: none; - -ms-flex-item-align: center; - background-color: transparent; - cursor: pointer; -} - -/* Setup the background with the white width and height 100% of screen */ -.hamburger__btn-toggle::before { - content: ""; - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - opacity: 0; - background-color: $pyos-white; - -webkit-transition: all 0.2s ease-in-out; - transition: all 0.2s ease-in-out; - pointer-events: none; -} - -/* Turn on white background when clicked - x for close */ -.hamburger__btn-toggle.close::before { - opacity: 0.9; - -webkit-transition: all 0.2s ease-in-out; - transition: all 0.2s ease-in-out; - pointer-events: auto; - z-index: -1 !important; -} - -/* Begin burger styling: actual hamburger element (the horiz lines)*/ -.burger__icon { - position: relative; - width: 1.5rem; - height: 0.25rem; - background: #6f777d; - margin: auto; - -webkit-transition: 0.3s; - transition: 0.3s; -} - -/* Hide the middle bar when in "closed" style state so it's an x */ -.close .burger__icon { - background: transparent; -} - -.burger__icon::before, -.burger__icon::after { - content: ""; - position: absolute; - left: 0; - width: 1.5rem; - height: 0.25rem; - background: #6f777d; - -webkit-transition: 0.3s; - transition: 0.3s; -} - -.burger__icon::after { - bottom: -0.5rem; -} -.burger__icon::before { - top: -0.5rem; -} - -// This creates the x by rotating the burger bars -.close .burger__icon::after { - -webkit-transform: rotate3d(0, 0, 1, -45deg); - transform: rotate3d(0, 0, 1, -45deg); -} -.close .burger__icon::before { - -webkit-transform: rotate3d(0, 0, 1, 45deg); - transform: rotate3d(0, 0, 1, 45deg); -} - -.close .burger__icon::before, -.close .burger__icon::after { - -webkit-transform-origin: 50% 50%; - -ms-transform-origin: 50% 50%; - transform-origin: 50% 50%; - top: 0; - width: 1.5rem; -} - -.visually-hidden { - position: absolute !important; - clip: rect(1px, 1px, 1px, 1px); - height: 1px !important; - width: 1px !important; - border: 0 !important; - overflow: hidden; -} -/* end burger */ - -/* Fancy underline effect for navigation links */ -.hover-underline { - display: inline-block; - position: relative; - width: 100%; - text-align: left !important; - color: $link-color; - - &:after { - content: ""; - position: absolute; - width: 100%; - height: 2px; - bottom: 0; - left: 0; - background-color: $primary-color; - transform: scaleX(0) translate3d(0, 0, 0); - transition: $global-transition; - } - - &:hover:after { - transform: scaleX(1); - } -} - -/* Add an active class to highlight the current page - might want to reinstate this */ -.active { - background-color: $pyos-teal; - color: $pyos-white; -} - -@media screen and (max-width: $large) { - /* Hide horizontal drop down links at $large / 1024px (closest to 1026px) */ - .nav__links { - display: none; - } - - /* Nav links goes vertical in responsive mode */ - .nav__links.vertical { - display: flex; - } - - .hamburger__btn-toggle { - display: block; - width: 2rem; - } - - .burger__icon, - .burger__icon::before, - .burger__icon::after { - display: block; - } -} diff --git a/_sass/minimal-mistakes/_pyos-flowing-page.scss b/_sass/minimal-mistakes/_pyos-flowing-page.scss deleted file mode 100644 index 31c884d9..00000000 --- a/_sass/minimal-mistakes/_pyos-flowing-page.scss +++ /dev/null @@ -1,300 +0,0 @@ -// Flowing splash pages: full-bleed bands (.pyos-section) with a centered -// content column. Markup pattern: one
-// (add .purple for tint) around each block. Optional: {% include -// pyos-flow-feature.html %} for feature rows. Use .pyos-bleed on a child that -// must span the viewport inside a section. - -.pyos-section { - padding: 0px; - margin-top: 0; // Remove default top margin - - // Reduce top margin when following feature wrapper - .feature__wrapper + & { - margin-top: 0; - } - - &.purple { - background-color: $pyos-lightpurple; - position: relative; - margin-top: 4rem; - } - - // Extra bottom padding when the learner / split quote sits in this band - &.purple:has(.highlight-quote-split) { - padding-bottom: 1.5rem; - } -} - -// Wave dividers (div_purple_top.html / div_purple_bottom.html): -// --out-of-purple: immediately after a purple band -// --into-purple: immediately before a purple band -// (Legacy misnamed .swoosh.bottom + svg.bottom referred to “into purple” only.) - -// Wave divider between bands: remove inline-SVG gap and extra margin. -.flowing .pyos-wave { - margin: 0; - line-height: 0; - - svg { - display: block; - width: 100%; - height: auto; - vertical-align: top; - } -} - -// Space between “out of purple” wave and the next “into purple” wave (light -// content or a gap between two purple strips when an out wave is used). -.flowing .pyos-wave--out-of-purple + .pyos-wave--into-purple { - margin-top: clamp(2rem, 5vw, 5rem); -} - -// Purple band followed directly by “into purple” wave (no out wave in between). -.flowing .pyos-section.purple + .pyos-wave--into-purple { - margin-top: 0; -} - -// Flush the wave to the purple band below (no double gap with .purple margin). -.flowing .pyos-wave--into-purple + .pyos-section.purple { - margin-top: 0; -} - -// Flush purple feature bands to the wave below (no white strip). -.flowing .pyos-section.purple { - padding-bottom: 0; -} - -// Kill feature_row margins inside purple bands; top margin otherwise collapses -// through the section and reads as a white gap below the into-purple wave. -.flowing .pyos-section.purple .feature__wrapper { - margin-top: 0; - margin-bottom: 0; -} - -.flowing .pyos-section.purple .feature__item, -.flowing .pyos-section.purple .feature__item--left, -.flowing .pyos-section.purple .feature__item--center { - margin-bottom: 0; -} - -.flowing .pyos-section.purple + .pyos-wave { - margin-top: 0; -} - - -.flowing div#main { - max-width: 100%; - margin-right: 0!important; - margin-left: 0!important; - padding-left: 0!important; - padding-right: 0!important; -} - -// Horizontal gutter on the full-bleed band; direct children share one max-width. -.flowing .pyos-section { - padding-left: clamp(1rem, 4vw, 2rem); - padding-right: clamp(1rem, 4vw, 2rem); - box-sizing: border-box; - - > *:not(.pyos-bleed) { - max-width: $max-width; - margin-left: auto; - margin-right: auto; - width: 100%; - box-sizing: border-box; - } - - // Prose uses the full inner column (theme defaults cap p/li much narrower). - h1, - h2, - h3, - h4, - h5, - h6, - p, - li { - max-width: 100%; - } - - .feature__wrapper p { - max-width: 100%; - } - - // Primary buttons must not span the full content column. - > a.btn { - width: auto; - } - - // Block IAL can put .btn on

, producing a full-width bar; cap it. - > p.btn { - width: fit-content; - max-width: $medium; - margin-left: auto; - margin-right: auto; - box-sizing: border-box; - } -} - -// Events CTA block: same readable width for headings, copy, figure, and button. -.flowing .pyos-section.learn-events { - > h2, - > p, - > figure { - max-width: $medium; - margin-left: auto; - margin-right: auto; - } - - > figure picture, - > figure img { - display: block; - width: 100%; - height: auto; - } - - // Inline .btn on the link only (content-sized, aligned with text column). - p > a.btn { - width: auto; - display: inline-block; - } -} - -// Narrow notice to readable measure ($medium). Use with .notice, e.g. -// {: .notice .notice--measure } -.flowing .notice.notice--measure { - max-width: $medium; - margin-left: auto !important; - margin-right: auto !important; - width: 100%; - box-sizing: border-box; -} - -// Learn page hero: heading + intro + notice share the same line length. -.flowing .pyos-section.learn-hero { - > h2:first-of-type { - margin: 2rem auto; - max-width: $medium; - font-size: $type-size-3; - line-height: 1.25; - } - - > p { - margin-left: auto; - margin-right: auto; - max-width: $medium; - font-size: $type-size-5; - line-height: 1.55; - } - - .notice.notice--measure { - margin-top: 1.25rem !important; - margin-bottom: 0 !important; - padding: 1rem 1.25rem !important; - font-size: $type-size-5 !important; - - p, - li { - font-size: inherit !important; - line-height: 1.55; - } - - p:last-child { - margin-bottom: 0 !important; - } - } -} - -// Tutorial card grid: breathing room so cards don’t crowd the adjacent waves. -.flowing .pyos-section.learn-packaging-tutorials { - padding-top: clamp(1.5rem, 4vw, 2.75rem); - padding-bottom: clamp(1.5rem, 4vw, 2.75rem); -} - -// Feature rows that sit directly under .page__content (not inside .pyos-section). -.flowing .page__content > .feature__wrapper { - max-width: $max-width; - margin-left: auto; - margin-right: auto; - padding-left: clamp(1rem, 4vw, 2rem); - padding-right: clamp(1rem, 4vw, 2rem); - box-sizing: border-box; -} - -.content { - max-width: $max-width; - margin-right: auto; - margin-left: auto; - - &.noimage { - max-width: 900px; - } -} - -.content.padding { - max-width: 900px; - margin: auto; -} - -// Add padding to non feature wrapper sections (legacy pages). -.feature__wrapper.padded { - padding: 0 clamp(1rem, 4vw, 2rem); -} - -.page__content .feature__wrapper.padded { - & p { - max-width: fit-content; - } - & h2 { - font-size: 2rem; - } -} - - -// Tighter padding on small screens (never flush to viewport edge). -@media (max-width: 480px) { - .feature__wrapper.padded { - padding-left: 1rem; - padding-right: 1rem; - } - -} - -// Opt-in layout: wide blockquote + photo (add .highlight-quote--split on blockquote) -.highlight-quote-split { - display: flex; - flex-direction: column; - align-items: center; - gap: 1.5rem; - // Clearance below "A learner perspective" so the opening quote mark does - // not overlap the heading - margin-top: 2.25rem; - margin-bottom: 0.5rem; - - @include breakpoint($medium) { - flex-direction: row; - align-items: flex-start; - gap: 2rem; - } -} - -.highlight-quote-split__photo { - margin: 0; - flex-shrink: 0; - text-align: center; - - img { - display: block; - width: 180px; - height: 180px; - object-fit: cover; - border-radius: 50%; - } -} - -// Class name uses BEM-style --; attribute selector avoids Sass minus ambiguity -blockquote.highlight-quote.wide[class~="highlight-quote--split"] { - flex: 1; - min-width: 0; - margin-top: 0; - margin-bottom: 0; -} diff --git a/_sass/minimal-mistakes/_pyos-functions.scss b/_sass/minimal-mistakes/_pyos-functions.scss deleted file mode 100644 index e6397890..00000000 --- a/_sass/minimal-mistakes/_pyos-functions.scss +++ /dev/null @@ -1,3 +0,0 @@ -@function darken-color($color, $percentage) { - @return mix(black, $color, $percentage); - } diff --git a/_sass/minimal-mistakes/_pyos-grid.scss b/_sass/minimal-mistakes/_pyos-grid.scss deleted file mode 100644 index 952ff67b..00000000 --- a/_sass/minimal-mistakes/_pyos-grid.scss +++ /dev/null @@ -1,742 +0,0 @@ -// Create colormap for cards -$colors: ( - color-0: #bb82b0, - color-1: #6b5b95, - color-2: #bab3d4, - color-3: #81c0aa, - color-4: #000000 -); - -@mixin generate-color-styles($color-map) { - @each $name, $color in $color-map { - .card__header-#{$name} { - background-color: $color; - - &::before { - background: rgba(0, 0, 0, 0.4)!important; - } - } - .cards:hover .card__header-#{$name} { - background-color: lighten($color, 15%) !important; // Lighten the color by 40% - } - } -} - -// Include the mixin -@include generate-color-styles($colors); - -// Shared mixin removed - using fixed breakpoints throughout for consistent behavior - -.clearfix::after { - content: ""; - display: table; - clear: both; -} - -/* Create & style a 3x3 grid wrapper */ -/* Adjust grid side for the flowing layout - fixed breakpoints */ -.flowing .grid { - display: grid; - grid-gap: 10px; - grid-template-columns: 1fr; // Mobile: 1 column - - @media (min-width: $small) { - grid-template-columns: repeat(2, 1fr); // Small: 2 columns - } - - @media (min-width: $medium) { - grid-template-columns: repeat(3, 1fr); // Medium: 3 columns - } - - @media (min-width: $large) { - grid-template-columns: repeat(4, 1fr); // Large: 4 columns - } -} - -.grid { - display: grid; - grid-gap: 10px; - grid-template-columns: 1fr; // Mobile: 1 column - - @media (min-width: $small) { - grid-template-columns: repeat(2, 1fr); // Small: 2 columns - } - - @media (min-width: $medium) { - grid-template-columns: repeat(3, 1fr); // Medium: 3 columns - } - - @media (min-width: $large) { - grid-template-columns: repeat(4, 1fr); // Large: 4 columns - } - - // Executive council section - fixed breakpoints - &.executive-council-grid { - grid-template-columns: repeat(4, 1fr); - grid-gap: 8px; - max-width: 1000px; - margin: 0 auto; - - @media screen and (max-width: $x-large) { - grid-template-columns: repeat(3, 1fr); - } - - @media screen and (max-width: $medium) { - grid-template-columns: repeat(2, 1fr); - } - - @media screen and (max-width: $mobile) { - grid-template-columns: 1fr; - } - } - - // Advisory council and editorial board sections - fixed breakpoints - &.advisory-council-grid { - grid-template-columns: repeat(4, 1fr); - grid-gap: 8px; - - @media screen and (max-width: $x-large) { - grid-template-columns: repeat(3, 1fr); - } - - @media screen and (max-width: $medium) { - grid-template-columns: repeat(2, 1fr); - } - - @media screen and (max-width: $mobile) { - grid-template-columns: 1fr; - } - } - - // Emeritus council section - fixed breakpoints - &.emeritus-council-grid { - grid-template-columns: repeat(4, 1fr); - grid-gap: 8px; - max-width: 1000px; - margin: 0 auto; - - @media screen and (max-width: $x-large) { - grid-template-columns: repeat(3, 1fr); - } - - @media screen and (max-width: $medium) { - grid-template-columns: repeat(2, 1fr); - } - - @media screen and (max-width: $mobile) { - grid-template-columns: 1fr; - } - } - - h4.grid_title { - font-size: 1.6em; - margin-top: 1em; - } - .grid_title { - margin-bottom: 0; - } - - h4.grid_title a, h3 a { - text-decoration: none; - } - - .page__meta.title { - font-size: 1em !important; - text-align: center; - font-size: 1.7em; - padding-top: 0; - margin-top: 0; - } - .page__meta .bio p { - font-size: 0.7em; - line-height: 0.2em !important; - } - } - -// Home page and volunteer page new contributors section - auto-responsive -.entries-grid { - display: grid; - gap: 8px; - grid-template-columns: 1fr; // Mobile: 1 column - - > * { - max-width: 280px; - width: 100%; - } - - @media (min-width: $small) { - grid-template-columns: repeat(2, 1fr); // Small: 2 columns - } - - @media (min-width: $medium) { - grid-template-columns: repeat(3, 1fr); // Medium: 3 columns - } - - @media (min-width: $large) { - grid-template-columns: repeat(4, 1fr); // Large: 4 columns - } - - // Home page only (home.md): not used on /our-community/ or volunteer entries-grid - &.entries-grid--contributors { - grid-template-columns: 1fr; // Mobile: 1 column - gap: $home-contributors-stripe-gap; - /* Below $large: cap card width + center in cell so 1–3 columns are not full-bleed slabs */ - justify-items: center; - - > * { - min-width: 0; - width: 100%; - max-width: min($home-contributors-card-max-below-large, 100%); - justify-self: center; - margin: 0; - box-sizing: border-box; - } - - @media (min-width: $small) { - grid-template-columns: repeat(2, 1fr); // Small: 2 columns - } - - @media (min-width: $medium) { - grid-template-columns: repeat(3, 1fr); // Medium: 3 columns - } - - /* Wide desktop: five columns, cards fill each track (no oversized cells) */ - @media (min-width: $large) { - grid-template-columns: repeat(5, 1fr); // Large: 5 columns - justify-items: stretch; - - > * { - max-width: 100%; - justify-self: stretch; - } - } - .people-card { - h4.person_name { - font-size: 0.95rem; - padding-left: 0.5em; - padding-right: 0.5em; - } - p.page__meta, - p.contrib_org { - padding-left: 0.5em; - padding-right: 0.5em; - } - p.page__meta, - p.contrib_org, - .ppl_social { - font-size: 0.75rem; - } - .ppl_social { - padding-left: 0.5em; - padding-right: 0.5em; - padding-bottom: 0.4em; - } - } - } - } - -// Home page upcoming events: compact cards, 2-up on wide screens -.upcoming-events-section { - margin-bottom: 2rem; - min-width: 0; - max-width: 100%; - - .upcoming-events-section__title { - margin: 0 0 0.8rem 0; - font-size: $type-size-card-title-lg; - font-weight: $semibold-weight; - letter-spacing: 0.08em; - text-transform: uppercase; - } -} - -.upcoming-events-list { - display: grid; - grid-template-columns: 1fr; - gap: 1rem; - min-width: 0; - max-width: 100%; - - @media (min-width: $medium) { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } - - > * { - max-width: none; - width: 100%; - min-width: 0; - } - - .upcoming { - min-width: 0; - max-width: 100%; - } -} - -// Home page packages section - fixed breakpoints (max 3 columns for better spacing) -.packages-grid { - display: grid; - grid-gap: 10px; - grid-template-columns: minmax(0, 1fr); // Mobile: 1 column, allow shrink - - @media (min-width: $small) { - grid-template-columns: repeat(2, minmax(0, 1fr)); // Small: 2 columns - } - - @media (min-width: $medium) { - grid-template-columns: repeat(3, minmax(0, 1fr)); // Medium and above: 3 columns (max) - } - - // Feature package cards - compact display - .package-card { - .package-card__content { - padding: 1.25em; - } - - .package-card__title { - font-size: $type-size-card-title-lg; - line-height: 1.3; - } - - .package-card__meta { - font-size: $type-size-card-meta; - line-height: $line-height-card-meta; - } - - .package-card__description p { - font-size: $type-size-card-body; - line-height: $line-height-card-body; - } - } -} - -.grid .package-card { - .package-card__content { - padding: 1.25em; - } - - .package-card__title { - font-size: 1.0375em; - } - - .package-card__meta { - font-size: 0.75em; - } - - .package-card__description { - font-size: 0.8125em; - } -} - -.grid-item { - background: #f9f9f9; - border: 1px solid #ddd; - border-radius: 8px; - overflow: hidden; - display: flex; - flex-direction: column; - } - -/* Blog grid styles */ -.blog__grid { - display: grid; - /* Uses $home-autofit-card-min so tracks never exceed the viewport on narrow screens */ - grid-template-columns: repeat( - auto-fill, - minmax(min(100%, #{$home-autofit-card-min}), 1fr) - ); - grid-auto-rows: 1fr; - grid-column-gap: 7px; - grid-row-gap: 30px; - margin-bottom: 20px; - min-width: 0; - - h3.card-title { - font-size: $type-size-card-title-lg !important; - text-align: left; - margin-top: 0; - vertical-align: middle; - padding: 0 .4em; - - } - - .card-excerpt p { - font-size: $type-size-card-body; - line-height: $line-height-card-body; - } - - .card__bkg { - padding-top: 30px; - - .card-title { - font-size: $type-size-card-title-lg !important; - letter-spacing: normal; - } - } - - .card__bkg.card__bkg--brand { - background-color: $pyos-darkpurple; - color: #fff; - - .card__title-container, - h3.card-title { - color: #fff; - } - } - - p.card-stage { - padding: 1em; - } - - article { - display: contents; - } - - a { - text-decoration: none; - } - - p { - font-size: $type-size-card-body; - font-weight: 400; - line-height: $line-height-card-body; - } -} - - - -.bubble:before { - content: ""; - position: absolute; - z-index: -1; - top: -16px; - right: -16px; - background: #967eb6; - height: 32px; - width: 32px; - border-radius: 32px; - transform: scale(2); - transform-origin: 50% 50%; - transition: transform 0.15s ease-out; - } - -// Cards are also used to style the isotope layout -// do not adjust the display or structure of the cards element. -/* Package cards splash */ - -// Base card styles moved to _pyos-cards.scss - -.grid h3.card-title a { - margin-top: .3em; - font-size: $type-size-card-title-lg; - color: $pyos-darkpurple; - margin-top: 0; - margin-bottom: 0; - border-bottom: none; -} - -.feature__grid { - display: grid; - grid-template-columns: repeat( - auto-fit, - minmax(min(100%, #{$home-autofit-card-min}), 1fr) - ); - gap: 20px; - width: 100%; - min-width: 0; - box-sizing: border-box; - - .grid-item { - display: flex; - flex-direction: column; - min-width: 0; - max-width: 100%; - background: transparent; - border: none; - border-radius: 8px; - overflow: visible; - padding: 0; - } - - .cards.highlight { - margin: 0 !important; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08) !important; - } - - div > div { - padding: 0; - } -} - -// Highlight is used for the home page highlight feature cards -.cards.highlight { - display: flex; - flex-direction: column; - height: 100%; - min-width: 0; - max-width: 100%; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08); - - .cards__image, - .cards-image { - max-height: 200px; - overflow: hidden; - - img { - max-width: 100%; - width: 100%; - height: auto; - object-fit: cover; - } - } - - &:before { - background-color: #fff; - } - - &:hover { - cursor: default; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08); // Maintain shadow on hover (override base .cards hover shadow) - } - - .card-body { - display: flex; - flex-direction: column; - flex-grow: 1; - min-width: 0; - padding: 15px; - overflow-wrap: break-word; - - h2.card-title { - margin:0; - font-size: $type-size-card-title-lg; - line-height: 1.3; - overflow-wrap: break-word; - } - - .card-content { - flex-grow: 1; - min-width: 0; - - p { - font-size: $type-size-card-body; - line-height: $line-height-card-body; - } - } - - .card-footer { - padding: 15px; - text-align: center; - - a.btn { - margin-top: auto - } - } - - /* Ensure content is spaced properly and button stays at the bottom */ - > div { - display: flex; - flex-direction: column; - flex-grow: 1; - justify-content: space-between; /* Distribute content vertically */ - } - - /* Style for the p containing the button */ - p { - font-size: $type-size-card-body; - margin-bottom: 0.5em; - line-height: $line-height-card-body; - } - - /* Ensure the button is pushed to the bottom */ - p a.btn { - margin-top: auto; /* This ensures the button stays at the bottom */ - align-self: flex-start; /* Optional, to align the button to the left */ - } - } -} -/* Blog & event grid styles*/ - -// Because notice text is smaller, rescale card for that specific case. -.notice .blog__grid { - font-size: $type-size-card-title; -} - -.blog__grid .cards { - padding: 0 0 1.5rem 0 !important; - display: flex; - flex-direction: column; - height: 100%; - box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2); - transition: transform 0.3s ease, box-shadow 0.3s ease; - - &:hover { - transform: scale(1.05); // Slightly enlarge the card - box-shadow: 0 8px 12px rgba(0, 0, 0, 0.4); // Enhanced shadow on hover - } - - .card__header { - position: relative; - background-size: cover; - background-position: center; - overflow: hidden; - - &::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: rgba(0, 0, 0, 0.6); // Initial overlay color (faded) - transition: background 0.3s ease; // Smooth transition effect - } - } - // Changed hover selector to target .cards instead of .card__header - &:hover .card__header::before { - background: rgba(0, 0, 0, 0.2); // Darker overlay on hover - - } - - .card__title-container { - min-height: 6rem; - display: flex; - align-items: center; /* Vertically center the title */ - padding: 0; - } - - .card-excerpt{ - font-size: $type-size-card-body; - padding: 0 1.3em; - - & p { - margin-top: 0; - font-size: $type-size-card-body; - line-height: $line-height-card-body; - } - } -} - -.grid__wrapper { - display: flex; -} - -// Base card styles moved to _pyos-cards.scss - -// Adjust cards within a notice block -.notice .cards p { - font-size: $type-size-card-body !important; - line-height: $line-height-card-body; -} - -/* author list */ - -.cards .page__meta-readtime { - font-size: 1.1em; -} - - -/* Adjust font size on cards */ -.narrow { - font-size: .8em!important; - } - -/* This creates a grid where each box is the same height */ -.grid-highlight { - display: grid; - // Essentially switch the default axis - grid-auto-flow: column; - - a { - text-decoration: none; - } - - // Ensure content elements fill up the .column - .element { - height: 100%; - } - - &.col-3 { - grid-gap: 1rem; - grid-template-columns: repeat(3, 1fr); - grid-auto-flow: unset; - } - } - - - - - /* max 480 px - unified home page card scale */ - @media screen and (max-width: $mobile) { - // Shared scale: card padding 0.75em, title 1rem, body 0.875rem - .cards.highlight .card-body { - padding: 0.75em; - p { - font-size: 0.875rem; - } - h2.card-title { - font-size: 1rem; - } - } - - .blog__grid { - h3.card__title-container { - padding: 0; - } - .card-excerpt p { - line-height: $line-height-card-body; - font-size: $type-size-card-body; - } - .card__bkg .card__title-container { - padding: 0; - } - - h3.card-title { - font-size: $type-size-card-title-lg; - } - .card-bkg { - padding:0; - margin:0; - } - } - - // the home page has this grid in a notice block - .notice { - - .blog__grid { - font-size: 1rem; - grid-template-columns: repeat(auto-fill, minmax(95%, 1fr)) !important; - grid-auto-rows: 0fr!important; - grid-column-gap: 7px; - grid-row-gap: 1rem!important; - margin-bottom: 5rem; - padding: 0; - - .card-body { - padding: 0.75em; - font-size: $type-size-card-body; - } - .card-body p, - .card-body h2 { - line-height: $line-height-card-body; - padding: 0; - font-size: $type-size-card-body; - } - h3.card-title { - font-size: $type-size-card-title-lg; - } - .cards { - margin: 0 0 !important; - } - - a { - font-size: 1em; - } - } - - } -} - - -// People cards in isotope grid - styles are now in _pyos-cards.scss -// Package cards in isotope grid - styles are now in _pyos-isotope.scss diff --git a/_sass/minimal-mistakes/_pyos-isotope.scss b/_sass/minimal-mistakes/_pyos-isotope.scss deleted file mode 100644 index bd46cf72..00000000 --- a/_sass/minimal-mistakes/_pyos-isotope.scss +++ /dev/null @@ -1,322 +0,0 @@ - -/* ---- isotope styles ---- */ -* { box-sizing: border-box; } - -// Isotope grid column widths - separate for people and packages -// People cards: 25% = 4 columns -$isotope-column-width: 25%; -// Package cards: 33.33% = 3 columns -$package-column-width: 33.33%; - -// body { -// font-family: $body-font!important; -// } - -/* ---- input ---- */ - -input[type="text"] { - font-size: 20px; -} - - - -/* ---- button ---- */ - -.button { - display: inline-block; - padding: 0.5em 1.0em; - margin-bottom: 10px; - background: #EEE; - border: none; - border-radius: 7px; - transition: background-color 0.2s cubic-bezier(0.3, 0, 0.5, 1); - box-shadow: rgba(27, 31, 35, 0.04) 0 10px 0, rgba(255, 255, 255, 0.25) 0 1px 0 inset; - //background-image: linear-gradient( to bottom, hsla(0, 0%, 0%, 0), hsla(0, 0%, 0%, 0.2) ); - color: #222; - font-family: sans-serif; - font-size: 16px; - text-shadow: 0 1px white; - cursor: pointer; - font-family: $body-font; - - &.community { - background-color: rgb(193, 173, 219); - } -} - -.button:hover { - background-color: #CCC7D6; - text-shadow: 0 1px hsla(0, 0%, 100%, 0.5); - color: #222; -} - -.button:active, -.button.is-checked { - background-color: #5b2773; -} - -.button.is-checked { - color: white; - text-shadow: 0 -1px hsla(0, 0%, 0%, 0.8); -} - -.button:active { - box-shadow: inset 0 1px 10px hsla(0, 0%, 0%, 0.8); -} - -/* ---- button-group ---- */ - -.button-group:after { - content: ''; - display: block; - clear: both; -} - -.button-group .button { - float: left; - border-radius: 0; - margin-left: 0; - margin-right: 1px; -} - -.button-group .button:first-child { border-radius: 0.5em 0 0 0.5em; } -.button-group .button:last-child { border-radius: 0 0.5em 0.5em 0; } - -.button-group { - display: flex; - flex-wrap: wrap; - - .button { - flex-grow: 1; - - @media (max-width: $small){ - padding: 0.25em 0.5em; - margin-bottom: 0.25em; - box-shadow: unset; - - &:first-child, &:last-child { - border-radius: 0; - } - } - } -} - - -/* ---- grid ---- */ - -/* Grid isotope container - must contain floats and establish proper layout */ -.grid-isotope { - position: relative; - width: 100%; - margin: 1em 0; - padding: 0; -} - -/* Layout */ -.grid-sizer { - width: $isotope-column-width; - /* Isotope needs to measure this element */ - visibility: hidden; - height: 0; - overflow: hidden; -} - -/* Package grid uses different column width */ -.package-grid-isotope .grid-sizer { - width: $package-column-width; -} - - -/* ---- .element-item ---- */ - -.grid .element-item { - width: auto; -} - - -.element-item { - position: relative; - float: left; - padding: 0; - box-sizing: border-box; - display: block; - visibility: visible; - opacity: 1; - - // People cards - override isotope defaults - // Base styles are in _pyos-cards.scss - &.people-card { - padding: 0 !important; - margin: 20px; - // Account for 20px margins on each side (40px total) - width: calc(#{$isotope-column-width} - 40px); - } -} - -.element-item > * { - margin: 0; - padding: 0; -} - -/* ---- Package Cards - Separate concerns ---- */ - -// Base package card styles - apply to all package cards (isotope and grid) -.package-card { - .package-card__content { - background: #fff; - border: 1px solid #e0e0e0; - border-top: 10px solid $pyos-darkpurple; - border-radius: 8px; - padding: 1.5em; - height: 100%; - display: flex; - flex-direction: column; - transition: box-shadow 0.2s ease, transform 0.2s ease; - position: relative; - overflow: visible; - - &:hover { - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); - transform: translateY(-2px); - } - } - - .package-card__title { - font-size: $type-size-card-title-lg; - font-weight: $semibold-weight; - margin: 0 0 0.75em 0; - color: #333; - line-height: 1.3; - } - - .package-card__meta { - font-size: $type-size-card-meta; - line-height: $line-height-card-meta; - color: #666; - margin-bottom: 1em; - display: flex; - align-items: center; - gap: 0.5em; - - i { - color: #999; - } - - a { - color: #666; - text-decoration: none; - - &:hover { - color: $pyos-darkpurple; - text-decoration: underline; - } - } - } - - .package-card__description { - font-size: $type-size-card-body; - line-height: $line-height-card-body; - color: #555; - margin-bottom: 1.25em; - flex-grow: 1; - - p { - margin: 0; - } - } - - .package-card__links { - list-style-type: none; - margin-left: 0; - margin-top: auto; - padding: 0.2em; - padding-top: 1em; - border-top: 1px solid #f0f0f0; - - li { - font-size: 1rem; - margin-bottom: 0.3em; - - a { - text-decoration: none; - color: $pyos-darkpurple; - display: inline-flex; - align-items: center; - gap: 0.4em; - transition: color 0.2s ease; - - &:hover { - color: darken($pyos-darkpurple, 10%); - } - - i { - font-size: 0.9em; - } - - img { - height: 18px; - vertical-align: middle; - } - } - - // Archived badge styling - &.package-card__archived { - color: #999; - font-size: 0.875em; - display: inline-flex; - align-items: center; - gap: 0.4em; - } - } - } -} - -// Isotope-specific styles - width and margin for isotope layout -.element-item.package-card { - margin: 8px; - padding: 0; - overflow: visible; - // Account for 8px margins on each side (16px total) - width: calc(#{$package-column-width} - 16px); -} - -// Responsive adjustments for isotope package cards (width only) -@media screen and (max-width: $x-large) { - .element-item.package-card { - width: calc(#{$package-column-width} - 16px); - } -} - -@media screen and (max-width: $medium) { - .element-item.package-card { - width: calc(50% - 16px); - } - - // Responsive padding for all package cards - .package-card .package-card__content { - padding: 1.25em; - } -} - -@media screen and (max-width: $mobile) { - .element-item.package-card { - width: 100%; - margin-left: 0; - margin-right: 0; - } - - // Align with blog cards: padding 0.75em, title 0.9rem, body 0.875rem - .package-card .package-card__content { - padding: 0.75em !important; - } - - .package-card .package-card__title { - font-size: 0.9rem; - word-break: break-word; - } - - .package-card .package-card__meta, - .package-card .package-card__description { - font-size: 0.875rem; - } -} diff --git a/_sass/minimal-mistakes/_pyos-main.scss b/_sass/minimal-mistakes/_pyos-main.scss deleted file mode 100644 index ab6a22aa..00000000 --- a/_sass/minimal-mistakes/_pyos-main.scss +++ /dev/null @@ -1,162 +0,0 @@ -img { - border-radius: 10px; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); -} - -// Grid customized styles - - -/* styles for the people grid */ -.contrib_avatar { - border-radius: 50%; -} - -.ppl_img { - padding-left: 2em; - padding-right: 2em; -} - -.grid.people { - grid-gap: 60px; -} -.person__item { - //margin: 2.2em; - text-align: center; -} - - -div > div:not(.package-card__content) { - padding: 10px; -} - -// Override div > div padding for person_img with higher specificity -// Person image styles are now in _pyos-cards.scss -.cards .person_img, -.archive__item .person_img { - padding: 0; -} - - - - -/* Fix people page - allow for tall images */ -.archive__item-teaser.tall { - max-height: 320px!important; -} - -h2.clearall { - clear: both; -} - -/* Sidebar author with circle font and header font adjustment */ -.sidebar .author__name { - font-family: $header-font!important; - font-size: .9em; -} - -@include breakpoint($large) { - .wide .archive { - width: calc(100% - 100px); - } -} - -.wide__p_text { - -webkit-box-flex: 0; - -webkit-flex: 0 0 66.66667%; - -ms-flex: 0 0 66.66667%; - flex: 0 0 66.66667%; - max-width: 66.66667%; -} - - -/* max 480 px */ -@media screen and (max-width: 480px) { - - .archive__item { - padding: 0!important; - - & .archive__item-excerpt { - font-size: .8em!important; - padding: 0!important; - } - } - - - .taxonomy__index { - font-size: .9em!important; - } - - .back-to-top { - font-size: 1.2em!important; - } - - .card-body { - padding: 0!important; - } - - .grid { - font-size: 1.2em; - - &.element-item { - padding:0; - } - } - - // .grid-isotope { - // font-size: 1.3em; - // } - - .card-body { - - & .btn { - font-size: 1em; - } - - & .archive__item-excerpt { - font-size: 1em; - } - - } - - .feature__item--center { - font-size: 1.1em; - - & .archive__item { - padding:0; - line-height: 1.3em; - } - - & .archive__item-excerpt { - padding:0; - font-size: .7rem; - - } - } - .feature__item { - width: 100%!important; - } - - h1, - h2, - h3:not(.package-card__title), - .archive__item-title { - font-size: 1.6em!important; - } - .btn--large { - font-size: 1.3em!important; - } - - .wide #main { - padding-left: 1em; - padding-right: 1em; - } - - .wide__p_text { - max-width: 90%!important; - padding-left: 1em; - } - - ol { - padding-left: 1em; - } -} diff --git a/_sass/minimal-mistakes/_pyos-testimonial-showcase.scss b/_sass/minimal-mistakes/_pyos-testimonial-showcase.scss deleted file mode 100644 index d42563c3..00000000 --- a/_sass/minimal-mistakes/_pyos-testimonial-showcase.scss +++ /dev/null @@ -1,122 +0,0 @@ -/* ========================================================================== - TESTIMONIAL SHOWCASE (universities / labs + similar landing sections) - Featured row + 2-up cards on light purple bands - ========================================================================== */ - -.flowing .pyos-section.purple.pyos-university-testimonials { - padding-top: 1.5rem; - padding-bottom: 2rem; -} - -.testimonial-showcase { - margin: 0; - padding: 0; - display: flex; - flex-direction: column; - gap: 1.25rem; - max-width: 100%; -} - -.testimonial-showcase__featured { - display: flex; - flex-direction: column; - align-items: center; - gap: 1rem; - margin: 0; - padding: 1.25rem 1.25rem 1.35rem; - border-radius: $border-radius; - background-color: $pyos-white; - box-shadow: 0 0.2rem 0.65rem rgba($pyos-brand-dark-purple, 0.1); - border: 1px solid rgba($pyos-brand-dark-purple, 0.12); - text-align: left; - - @media (min-width: $small) { - flex-direction: row; - align-items: flex-start; - gap: 1.5rem; - padding: 1.5rem 1.5rem 1.6rem; - } -} - -.testimonial-showcase__avatar-wrap { - flex-shrink: 0; -} - -.testimonial-showcase__avatar { - width: 7.5rem; - height: 7.5rem; - border-radius: 50%; - object-fit: cover; - display: block; - border: 3px solid rgba($pyos-brand-dark-purple, 0.18); - box-sizing: border-box; -} - -.testimonial-showcase__featured-body { - flex: 1 1 auto; - min-width: 0; -} - -.testimonial-showcase__mark { - margin: 0 0 0.35rem; - line-height: 1; - color: $pyos-brand-dark-purple; - - .fa-quote-left { - font-size: $type-size-3; - } - - &--compact { - color: $pyos-teal; - - .fa-quote-left { - font-size: $type-size-card-title-lg; - } - } -} - -.testimonial-showcase__blockquote { - margin: 0; - padding: 0; - border: 0; - font-family: $caption-font-family; - - p { - margin: 0 0 0.75rem; - font-size: $type-size-card-body; - line-height: $line-height-card-body; - font-style: italic; - color: rgba($pyos-brand-dark-purple, 0.95); - } -} - -.testimonial-showcase__cite { - margin: 0; - font-family: $caption-font-family; - font-size: $type-size-card-meta; - line-height: $line-height-card-meta; - font-style: normal; - font-weight: $semibold-weight; - color: rgba($pyos-brand-dark-purple, 0.72); -} - -.testimonial-showcase__grid { - display: grid; - gap: 1.25rem; - grid-template-columns: 1fr; - - @media (min-width: $small) { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } -} - -.testimonial-showcase__card { - margin: 0; - padding: 1.1rem 1rem 1.15rem; - border-radius: $border-radius; - background-color: $pyos-white; - box-shadow: 0 0.2rem 0.65rem rgba($pyos-brand-dark-purple, 0.08); - border: 1px solid rgba($pyos-brand-dark-purple, 0.12); - border-top: 4px solid $pyos-teal; - text-align: left; -} diff --git a/_sass/minimal-mistakes/_pyos-twitter.scss b/_sass/minimal-mistakes/_pyos-twitter.scss deleted file mode 100644 index 0c8e9fd8..00000000 --- a/_sass/minimal-mistakes/_pyos-twitter.scss +++ /dev/null @@ -1,16 +0,0 @@ - -/* TWITTER cards */ -.twitter-tweet { - border: 1px solid #ccc; - border-radius: .5em; - padding: 1.2em; - width: 90%; - margin-left: auto; - margin-right: auto; - background-color: #e2ecf3; - font-weight: 300; - font-style: normal; - font-size: .85em; - color: #555; - filter: drop-shadow(5px 5px 4px #ccc); -} diff --git a/_sass/minimal-mistakes/_reset.scss b/_sass/minimal-mistakes/_reset.scss deleted file mode 100644 index c76bfaaf..00000000 --- a/_sass/minimal-mistakes/_reset.scss +++ /dev/null @@ -1,187 +0,0 @@ -/* ========================================================================== - Media queries for responsive queries - ========================================================================== */ - -* { box-sizing: border-box; } - -html { - /* apply a natural box layout model to all elements */ - box-sizing: border-box; - background-color: $background-color; - font-size: 1rem; - - @include breakpoint($medium) { - font-size: 1rem; - } - - @include breakpoint($large) { - font-size: 1.0625rem; // 17px / 16px - } - - @include breakpoint($x-large) { - font-size: 1.0625rem; // keep scale consistent on wide screens - } - - -webkit-text-size-adjust: 100%; - -ms-text-size-adjust: 100%; -} - -/* Remove margin */ - -body { margin: 0; } - -/* Selected elements */ - -::-moz-selection { - color: #fff; - background: #000; -} - -::selection { - color: #fff; - background: #000; -} - -/* Display HTML5 elements in IE6-9 and FF3 */ - -article, -aside, -details, -figcaption, -figure, -footer, -header, -hgroup, -main, -nav, -section { - display: block; -} - -/* Display block in IE6-9 and FF3 */ - -audio, -canvas, -video { - display: inline-block; - *display: inline; - *zoom: 1; -} - -/* Prevents modern browsers from displaying 'audio' without controls */ - -audio:not([controls]) { - display: none; -} - -a { - color: $link-color; -} - -/* Apply focus state */ - -a:focus { - @extend %tab-focus; -} - -/* Remove outline from links */ - -a:hover, -a:active { - outline: 0; -} - -/* Prevent sub and sup affecting line-height in all browsers */ - -sub, -sup { - position: relative; - font-size: 75%; - line-height: 0; - vertical-align: baseline; -} - -sup { - top: -0.5em; -} - -sub { - bottom: -0.25em; -} - -/* img border in anchor's and image quality */ - -img { - /* Responsive images (ensure images don't scale beyond their parents) */ - max-width: 100%; /* part 1: Set a maximum relative to the parent*/ - width: auto\9; /* IE7-8 need help adjusting responsive images*/ - height: auto; /* part 2: Scale the height according to the width, otherwise you get stretching*/ - - vertical-align: middle; - border: 0; - -ms-interpolation-mode: bicubic; -} - -/* Prevent max-width from affecting Google Maps */ - -#map_canvas img, -.google-maps img { - max-width: none; -} - -/* Consistent form font size in all browsers, margin changes, misc */ - -button, -input, -select, -textarea { - margin: 0; - font-size: 100%; - vertical-align: middle; -} - -button, -input { - *overflow: visible; /* inner spacing ie IE6/7*/ - line-height: normal; /* FF3/4 have !important on line-height in UA stylesheet*/ -} - -button::-moz-focus-inner, -input::-moz-focus-inner { /* inner padding and border oddities in FF3/4*/ - padding: 0; - border: 0; -} - -button, -html input[type="button"], // avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` and `video` controls -input[type="reset"], -input[type="submit"] { - -webkit-appearance: button; /* corrects inability to style clickable `input` types in iOS*/ - cursor: pointer; /* improves usability and consistency of cursor style between image-type `input` and others*/ -} - -label, -select, -button, -input[type="button"], -input[type="reset"], -input[type="submit"], -input[type="radio"], -input[type="checkbox"] { - cursor: pointer; /* improves usability and consistency of cursor style between image-type `input` and others*/ -} - -input[type="search"] { /* Appearance in Safari/Chrome*/ - box-sizing: border-box; - -webkit-appearance: textfield; -} - -input[type="search"]::-webkit-search-decoration, -input[type="search"]::-webkit-search-cancel-button { - -webkit-appearance: none; /* inner-padding issues in Chrome OSX, Safari 5*/ -} - -textarea { - overflow: auto; /* remove vertical scrollbar in IE6-9*/ - vertical-align: top; /* readability and alignment cross-browser*/ -} diff --git a/_sass/minimal-mistakes/_search.scss b/_sass/minimal-mistakes/_search.scss deleted file mode 100644 index fa7ee832..00000000 --- a/_sass/minimal-mistakes/_search.scss +++ /dev/null @@ -1,132 +0,0 @@ -/* ========================================================================== - SEARCH - ========================================================================== */ - -.layout--search { - .archive__item-teaser { - margin-bottom: 0.25em; - } -} - -.search__toggle { - margin-left: 1rem; - margin-right: 1rem; - height: $nav-toggle-height; - border: 0; - outline: none; - color: $primary-color; - background-color: transparent; - cursor: pointer; - -webkit-transition: 0.2s; - transition: 0.2s; - - &:hover { - color: mix(#000, $primary-color, 25%); - } -} - -.search-icon { - width: 100%; - height: 100%; -} - -.search-content { - display: none; - visibility: hidden; - padding-top: 1em; - padding-bottom: 1em; - - &__inner-wrap { - width: 100%; - margin-left: auto; - margin-right: auto; - padding-left: 1em; - padding-right: 1em; - -webkit-animation: $intro-transition; - animation: $intro-transition; - -webkit-animation-delay: 0.15s; - animation-delay: 0.15s; - - @include breakpoint($x-large) { - max-width: $max-width; - } - - } - - &__form { - background-color: transparent; - } - - .search-input { - display: block; - margin-bottom: 0; - padding: 0; - border: none; - outline: none; - box-shadow: none; - background-color: transparent; - font-size: $type-size-3; - - @include breakpoint($large) { - font-size: $type-size-2; - } - - @include breakpoint($x-large) { - font-size: $type-size-1; - } - } - - &.is--visible { - display: block; - visibility: visible; - - &::after { - content: ""; - display: block; - } - } - - .results__found { - margin-top: 0.5em; - font-size: $type-size-6; - } - - .archive__item { - margin-bottom: 2em; - - @include breakpoint($large) { - width: 75%; - } - - @include breakpoint($x-large) { - width: 50%; - } - } - - .archive__item-title { - margin-top: 0; - } - - .archive__item-excerpt { - margin-bottom: 0; - } -} - -/* Algolia search */ - -.ais-search-box { - max-width: 100% !important; - margin-bottom: 2em; -} - -.archive__item-title .ais-Highlight { - color: $primary-color; - font-style: normal; - text-decoration: underline; -} - -.archive__item-excerpt .ais-Highlight { - color: $primary-color; - font-style: normal; - font-weight: bold; -} diff --git a/_sass/minimal-mistakes/_sidebar.scss b/_sass/minimal-mistakes/_sidebar.scss deleted file mode 100644 index 91655611..00000000 --- a/_sass/minimal-mistakes/_sidebar.scss +++ /dev/null @@ -1,352 +0,0 @@ -/* ========================================================================== - SIDEBAR - ========================================================================== */ - -/* - Default - ========================================================================== */ - -.sidebar { - @include clearfix(); - // @include breakpoint(max-width $large) { - // /* fix z-index order of follow links */ - // position: relative; - // z-index: 10; - // -webkit-transform: translate3d(0, 0, 0); - // transform: translate3d(0, 0, 0); - // } - - @include breakpoint($large) { - float: left; - width: calc(#{$right-sidebar-width-narrow} - 1em); - opacity: 0.75; - -webkit-transition: opacity 0.2s ease-in-out; - transition: opacity 0.2s ease-in-out; - - &:hover { - opacity: 1; - } - - &.sticky { - overflow-y: auto; - /* calculate height of nav list - viewport height - nav height - masthead x-padding - */ - max-height: calc(100vh - #{$nav-height} - 2em); - } - } - - @include breakpoint($x-large) { - width: calc(#{$right-sidebar-width} - 1em); - } - - > * { - margin-top: 1em; - margin-bottom: 1em; - } - - h2, - h3, - h4, - h5, - h6 { - margin-bottom: 0; - font-family: $sans-serif-narrow; - } - - p, - li { - font-family: $sans-serif; - font-size: $type-size-6; - line-height: 1.5; - } - - img { - width: 100%; - - &.emoji { - width: 20px; - height: 20px; - } - } -} - -.sidebar__right { - margin-bottom: 1em; - - @include breakpoint($large) { - position: absolute; - top: 0; - right: 0; - width: $right-sidebar-width-narrow; - margin-right: -1 * $right-sidebar-width-narrow; - padding-left: 1em; - z-index: 10; - - &.sticky { - @include clearfix(); - position: -webkit-sticky; - position: sticky; - top: 2em; - float: right; - - .toc { - .toc__menu { - overflow-y: auto; - max-height: calc(100vh - 7em); - } - } - } - } - - @include breakpoint($x-large) { - width: $right-sidebar-width; - margin-right: -1 * $right-sidebar-width; - } -} - -.splash .sidebar__right { - @include breakpoint($large) { - position: relative; - float: right; - margin-right: 0; - } - - @include breakpoint($x-large) { - margin-right: 0; - } -} - -/* - Author profile and links - ========================================================================== */ - -.author__avatar { - display: table-cell; - vertical-align: top; - width: 36px; - height: 36px; - - @include breakpoint($large) { - display: block; - width: auto; - height: auto; - } - - img { - max-width: 110px; - border-radius: 50%; - - @include breakpoint($large) { - padding: 5px; - border: 1px solid $border-color; - } - } -} - -.author__content { - display: table-cell; - vertical-align: top; - padding-left: 15px; - padding-right: 25px; - line-height: 1; - - @include breakpoint($large) { - display: block; - width: 100%; - padding-left: 0; - padding-right: 0; - } - - a { - color: inherit; - text-decoration: none; - } -} - -.author__name { - margin: 0; - - @include breakpoint($large) { - margin-top: 10px; - margin-bottom: 10px; - } -} -.sidebar .author__name { - font-family: $sans-serif; - font-size: $type-size-5; -} - -.author__bio { - margin: 0; - - @include breakpoint($large) { - margin-top: 10px; - margin-bottom: 20px; - } -} - -.author__urls-wrapper { - position: relative; - display: table-cell; - vertical-align: middle; - font-family: $sans-serif; - z-index: 20; - cursor: pointer; - - li:last-child { - a { - margin-bottom: 0; - } - } - - .author__urls { - span.label { - padding-left: 5px; - } - } - - @include breakpoint($large) { - display: block; - } - - button { - position: relative; - margin-bottom: 0; - - &:before { - @supports (pointer-events: none) { - content: ''; - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - pointer-events: none; - } - } - - &.open { - &:before { - pointer-events: auto; - } - } - - @include breakpoint($large) { - display: none; - } - } -} - -.author__urls { - display: none; - position: absolute; - right: 0; - margin-top: 15px; - padding: 10px; - list-style-type: none; - border: 1px solid $border-color; - border-radius: $border-radius; - background: $background-color; - box-shadow: 0 2px 4px 0 rgba(#000, 0.16), 0 2px 10px 0 rgba(#000, 0.12); - cursor: default; - - &.is--visible { - display: block; - } - - @include breakpoint($large) { - display: block; - position: relative; - margin: 0; - padding: 0; - border: 0; - background: transparent; - box-shadow: none; - } - - &:before { - display: block; - content: ""; - position: absolute; - top: -11px; - left: calc(50% - 10px); - width: 0; - border-style: solid; - border-width: 0 10px 10px; - border-color: $border-color transparent; - z-index: 0; - - @include breakpoint($large) { - display: none; - } - } - - &:after { - display: block; - content: ""; - position: absolute; - top: -10px; - left: calc(50% - 10px); - width: 0; - border-style: solid; - border-width: 0 10px 10px; - border-color: $background-color transparent; - z-index: 1; - - @include breakpoint($large) { - display: none; - } - } - - ul { - padding: 10px; - list-style-type: none; - } - - li { - white-space: nowrap; - } - - a { - display: block; - margin-bottom: 5px; - padding-right: 5px; - padding-top: 2px; - padding-bottom: 2px; - color: inherit; - font-size: $type-size-5; - text-decoration: none; - - &:hover { - text-decoration: underline; - } - } -} - -/* - Wide Pages - ========================================================================== */ - -.wide .sidebar__right { - margin-bottom: 1em; - - @include breakpoint($large) { - position: initial; - top: initial; - right: initial; - width: initial; - margin-right: initial; - padding-left: initial; - z-index: initial; - - &.sticky { - float: none; - } - } - - @include breakpoint($x-large) { - width: initial; - margin-right: initial; - } -} diff --git a/_sass/minimal-mistakes/_syntax.scss b/_sass/minimal-mistakes/_syntax.scss deleted file mode 100644 index 64395ef7..00000000 --- a/_sass/minimal-mistakes/_syntax.scss +++ /dev/null @@ -1,325 +0,0 @@ -/* ========================================================================== - Syntax highlighting - ========================================================================== */ - -div.highlighter-rouge, -figure.highlight { - position: relative; - margin-bottom: 1em; - background: $base00; - color: $base05; - font-family: $monospace; - font-size: $type-size-6; - line-height: 1.8; - border-radius: $border-radius; - max-width: $medium; - - > pre, - pre.highlight { - margin: 0; - padding: 1em; - } -} - -.highlight table { - margin-bottom: 0; - font-size: 1em; - border: 0; - - td { - padding: 0; - width: calc(100% - 1em); - border: 0; - - /* line numbers*/ - &.gutter, - &.rouge-gutter { - padding-right: 1em; - width: 1em; - color: $base04; - border-right: 1px solid $base04; - text-align: right; - } - - /* code */ - &.code, - &.rouge-code { - padding-left: 1em; - } - } - - pre { - margin: 0; - } -} - -.highlight pre { - width: 100%; -} - -.highlight .hll { - background-color: $base06; -} -.highlight { - .c { - /* Comment */ - color: $base04; - } - .err { - /* Error */ - color: $base08; - } - .k { - /* Keyword */ - color: $base0e; - } - .l { - /* Literal */ - color: $base09; - } - .n { - /* Name */ - color: $base05; - } - .o { - /* Operator */ - color: $base0c; - } - .p { - /* Punctuation */ - color: $base05; - } - .cm { - /* Comment.Multiline */ - color: $base04; - } - .cp { - /* Comment.Preproc */ - color: $base04; - } - .c1 { - /* Comment.Single */ - color: $base04; - } - .cs { - /* Comment.Special */ - color: $base04; - } - .gd { - /* Generic.Deleted */ - color: $base08; - } - .ge { - /* Generic.Emph */ - font-style: italic; - } - .gh { - /* Generic.Heading */ - color: $base05; - font-weight: bold; - } - .gi { - /* Generic.Inserted */ - color: $base0b; - } - .gp { - /* Generic.Prompt */ - color: $base04; - font-weight: bold; - } - .gs { - /* Generic.Strong */ - font-weight: bold; - } - .gu { - /* Generic.Subheading */ - color: $base0c; - font-weight: bold; - } - .kc { - /* Keyword.Constant */ - color: $base0e; - } - .kd { - /* Keyword.Declaration */ - color: $base0e; - } - .kn { - /* Keyword.Namespace */ - color: $base0c; - } - .kp { - /* Keyword.Pseudo */ - color: $base0e; - } - .kr { - /* Keyword.Reserved */ - color: $base0e; - } - .kt { - /* Keyword.Type */ - color: $base0a; - } - .ld { - /* Literal.Date */ - color: $base0b; - } - .m { - /* Literal.Number */ - color: $base09; - } - .s { - /* Literal.String */ - color: $base0b; - } - .na { - /* Name.Attribute */ - color: $base0d; - } - .nb { - /* Name.Builtin */ - color: $base05; - } - .nc { - /* Name.Class */ - color: $base0a; - } - .no { - /* Name.Constant */ - color: $base08; - } - .nd { - /* Name.Decorator */ - color: $base0c; - } - .ni { - /* Name.Entity */ - color: $base05; - } - .ne { - /* Name.Exception */ - color: $base08; - } - .nf { - /* Name.Function */ - color: $base0d; - } - .nl { - /* Name.Label */ - color: $base05; - } - .nn { - /* Name.Namespace */ - color: $base0a; - } - .nx { - /* Name.Other */ - color: $base0d; - } - .py { - /* Name.Property */ - color: $base05; - } - .nt { - /* Name.Tag */ - color: $base0c; - } - .nv { - /* Name.Variable */ - color: $base08; - } - .ow { - /* Operator.Word */ - color: $base0c; - } - .w { - /* Text.Whitespace */ - color: $base05; - } - .mf { - /* Literal.Number.Float */ - color: $base09; - } - .mh { - /* Literal.Number.Hex */ - color: $base09; - } - .mi { - /* Literal.Number.Integer */ - color: $base09; - } - .mo { - /* Literal.Number.Oct */ - color: $base09; - } - .sb { - /* Literal.String.Backtick */ - color: $base0b; - } - .sc { - /* Literal.String.Char */ - color: $base05; - } - .sd { - /* Literal.String.Doc */ - color: $base04; - } - .s2 { - /* Literal.String.Double */ - color: $base0b; - } - .se { - /* Literal.String.Escape */ - color: $base09; - } - .sh { - /* Literal.String.Heredoc */ - color: $base0b; - } - .si { - /* Literal.String.Interpol */ - color: $base09; - } - .sx { - /* Literal.String.Other */ - color: $base0b; - } - .sr { - /* Literal.String.Regex */ - color: $base0b; - } - .s1 { - /* Literal.String.Single */ - color: $base0b; - } - .ss { - /* Literal.String.Symbol */ - color: $base0b; - } - .bp { - /* Name.Builtin.Pseudo */ - color: $base05; - } - .vc { - /* Name.Variable.Class */ - color: $base08; - } - .vg { - /* Name.Variable.Global */ - color: $base08; - } - .vi { - /* Name.Variable.Instance */ - color: $base08; - } - .il { - /* Literal.Number.Integer.Long */ - color: $base09; - } -} - -.gist { - th, td { - border-bottom: 0; - } -} diff --git a/_sass/minimal-mistakes/_tables.scss b/_sass/minimal-mistakes/_tables.scss deleted file mode 100644 index b7311c2d..00000000 --- a/_sass/minimal-mistakes/_tables.scss +++ /dev/null @@ -1,39 +0,0 @@ -/* ========================================================================== - TABLES - ========================================================================== */ - -table { - display: block; - margin-bottom: 1em; - width: 100%; - font-family: $global-font-family; - font-size: $type-size-5; - border-collapse: collapse; - overflow-x: auto; - - & + table { - margin-top: 1em; - } -} - -thead { - background-color: $border-color; - border-bottom: 2px solid mix(#000, $border-color, 25%); -} - -th { - padding: 0.5em; - font-weight: bold; - text-align: left; -} - -td { - padding: 0.5em; - border-bottom: 1px solid mix(#000, $border-color, 25%); -} - -tr, -td, -th { - vertical-align: middle; -} diff --git a/_sass/minimal-mistakes/_utilities.scss b/_sass/minimal-mistakes/_utilities.scss deleted file mode 100644 index 1c675be4..00000000 --- a/_sass/minimal-mistakes/_utilities.scss +++ /dev/null @@ -1,558 +0,0 @@ -/* ========================================================================== - UTILITY CLASSES - ========================================================================== */ - -/* - Visibility - ========================================================================== */ - -/* http://www.456bereastreet.com/archive/200711/screen_readers_sometimes_ignore_displaynone/ */ - -.hidden, -.is--hidden { - display: none; - visibility: hidden; -} - -/* for preloading images */ - -.load { - display: none; -} - -.transparent { - opacity: 0; -} - -/* https://developer.yahoo.com/blogs/ydn/clip-hidden-content-better-accessibility-53456.html */ - -.visually-hidden, -.screen-reader-text, -.screen-reader-text span, -.screen-reader-shortcut { - position: absolute !important; - clip: rect(1px, 1px, 1px, 1px); - height: 1px !important; - width: 1px !important; - border: 0 !important; - overflow: hidden; -} - -body:hover .visually-hidden a, -body:hover .visually-hidden input, -body:hover .visually-hidden button { - display: none !important; -} - -/* screen readers */ - -.screen-reader-text:focus, -.screen-reader-shortcut:focus { - clip: auto !important; - height: auto !important; - width: auto !important; - display: block; - font-size: 1em; - font-weight: bold; - padding: 15px 23px 14px; - background: #fff; - z-index: 100000; - text-decoration: none; - box-shadow: 0 0 2px 2px rgba(0, 0, 0, 0.6); -} - -/* - Skip links - ========================================================================== */ - -.skip-link { - position: fixed; - z-index: 20; - margin: 0; - font-family: $sans-serif; - white-space: nowrap; -} - -.skip-link li { - height: 0; - width: 0; - list-style: none; -} - -/* - Type - ========================================================================== */ - -.text-left { - text-align: left; -} - -.text-center { - text-align: center; -} - -.text-right { - text-align: right; -} - -.text-justify { - text-align: justify; -} - -.text-nowrap { - white-space: nowrap; -} - -/* - Narrow text utility - ========================================================================== */ - -.narrow { - font-size: 0.9em !important; -} - -.narrow p { - font-size: 0.9em !important; -} - -/* - Task lists - ========================================================================== */ - -.task-list { - padding: 0; - - li { - list-style-type: none; - } - - .task-list-item-checkbox { - margin-right: 0.5em; - opacity: 1; - } -} - -.task-list .task-list { - margin-left: 1em; -} - -/* - Alignment - ========================================================================== */ - -/* clearfix */ - -.cf { - clear: both; -} - -.wrapper { - margin-left: auto; - margin-right: auto; - width: 100%; -} - -/* - Images - ========================================================================== */ - -/* image align left */ - -.align-left { - display: block; - margin-left: auto; - margin-right: auto; - - @include breakpoint($small) { - float: left; - margin-right: 1em; - } -} - -/* image align right */ - -.align-right { - display: block; - margin-left: auto; - margin-right: auto; - - @include breakpoint($small) { - float: right; - margin-left: 1em; - } -} - -/* image align center */ - -.align-center { - display: block; - //margin-left: auto; //align-center is implemented on the img tag below - //margin-right: auto; - text-align: center; // Ensures the caption is centered - width: 100%; // Ensures the figure takes up the full width of its container - - img { - display: block; - margin-left: auto; - margin-right: auto; - } - - figcaption { - text-align: center; - font-style: italic; - margin-top: 5px; - } -} - -/* file page content container */ - -.full { - @include breakpoint($large) { - margin-right: -1 * span(2.5 of 12) !important; - } -} - -/* wider padding option for landing-page content blocks */ - -.padding-wide { - @include breakpoint($medium) { - padding-left: 2.5rem !important; - padding-right: 2.5rem !important; - } - - @include breakpoint($large) { - padding-left: 3rem !important; - padding-right: 3rem !important; - } -} - -/* - Icons - ========================================================================== */ - -.icon { - display: inline-block; - fill: currentColor; - width: 1em; - height: 1.1em; - line-height: 1; - position: relative; - top: -0.1em; - vertical-align: middle; -} - -/* social icons*/ -// General social icons - for author profiles and other non-footer contexts -// Footer styles are handled separately in _footer.scss with higher specificity -.author__urls.social-icons { - .fas, - .fab, - .far, - .fal { - color: $text-color; - } - - .fa-github, - .fa-github-alt, - .fa-github-square { - color: $github-color; - } - - .fa-gitlab { - color: $gitlab-color; - } - - .fa-linkedin, - .fa-linkedin-in { - color: $linkedin-color; - } - - .fa-mastodon, - .fa-mastodon-square { - color: $mastodon-color; - } - - .fa-reddit { - color: $reddit-color; - } - - .fa-rss, - .fa-rss-square { - color: $rss-color; - } - - .fa-stack-exchange, - .fa-stack-overflow { - color: $stackoverflow-color; - } - - .fa-youtube { - color: $youtube-color; - } -} - -/* - Navicons - ========================================================================== */ - -.navicon { - position: relative; - width: $navicon-width; - height: $navicon-height; - background: $primary-color; - margin: auto; - -webkit-transition: 0.3s; - transition: 0.3s; - - &:before, - &:after { - content: ""; - position: absolute; - left: 0; - width: $navicon-width; - height: $navicon-height; - background: $primary-color; - -webkit-transition: 0.3s; - transition: 0.3s; - } - - &:before { - top: (-2 * $navicon-height); - } - - &:after { - bottom: (-2 * $navicon-height); - } -} - -.close .navicon { - /* hide the middle line*/ - background: transparent; - - /* overlay the lines by setting both their top values to 0*/ - &:before, - &:after { - -webkit-transform-origin: 50% 50%; - -ms-transform-origin: 50% 50%; - transform-origin: 50% 50%; - top: 0; - width: $navicon-width; - } - - /* rotate the lines to form the x shape*/ - &:before { - -webkit-transform: rotate3d(0, 0, 1, 45deg); - transform: rotate3d(0, 0, 1, 45deg); - } - &:after { - -webkit-transform: rotate3d(0, 0, 1, -45deg); - transform: rotate3d(0, 0, 1, -45deg); - } -} - -.greedy-nav__toggle { - &:before { - @supports (pointer-events: none) { - content: ""; - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - opacity: 0; - background-color: $background-color; - -webkit-transition: $global-transition; - transition: $global-transition; - pointer-events: none; - } - } - - &.close { - &:before { - opacity: 0.9; - -webkit-transition: $global-transition; - transition: $global-transition; - pointer-events: auto; - } - } -} - -.greedy-nav__toggle:hover { - .navicon, - .navicon:before, - .navicon:after { - background: mix(#000, $primary-color, 25%); - } - - &.close { - .navicon { - background: transparent; - } - } -} - -/* - Sticky, fixed to top content - ========================================================================== */ - -.sticky { - @include breakpoint($large) { - @include clearfix(); - position: -webkit-sticky; - position: sticky; - top: 2em; - - > * { - display: block; - } - } -} - -/* - Wells - ========================================================================== */ - -.well { - min-height: 20px; - padding: 19px; - margin-bottom: 20px; - background-color: #f5f5f5; - border: 1px solid #e3e3e3; - border-radius: $border-radius; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); -} - -/* - Modals - ========================================================================== */ - -.show-modal { - overflow: hidden; - position: relative; - - &:before { - position: absolute; - content: ""; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 999; - background-color: rgba(255, 255, 255, 0.85); - } - - .modal { - display: block; - } -} - -.modal { - display: none; - position: fixed; - width: 300px; - top: 50%; - left: 50%; - margin-left: -150px; - margin-top: -150px; - min-height: 0; - z-index: 9999; - background: #fff; - border: 1px solid $border-color; - border-radius: $border-radius; - box-shadow: $box-shadow; - - &__title { - margin: 0; - padding: 0.5em 1em; - } - - &__supporting-text { - padding: 0 1em 0.5em 1em; - } - - &__actions { - padding: 0.5em 1em; - border-top: 1px solid $border-color; - } -} - -/* - Footnotes - ========================================================================== */ - -.footnote { - color: mix(#fff, $gray, 25%); - text-decoration: none; -} - -.footnotes { - color: mix(#fff, $gray, 25%); - - ol, - li, - p { - margin-bottom: 0; - font-size: $type-size-6; - } -} - -a.reversefootnote { - color: $gray; - text-decoration: none; - - &:hover { - text-decoration: underline; - } -} - -/* - Required - ========================================================================== */ - -.required { - color: $danger-color; - font-weight: bold; -} - -/* - Google Custom Search Engine - ========================================================================== */ - -.gsc-control-cse { - table, - tr, - td { - border: 0; /* remove table borders widget */ - } -} - -/* - Responsive Video Embed - ========================================================================== */ - -.responsive-video-container { - position: relative; - margin-bottom: 1em; - padding-bottom: 56.25%; - height: 0; - overflow: hidden; - max-width: 100%; - - iframe, - object, - embed { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - } -} - -// full screen video fixes -:-webkit-full-screen-ancestor { - .masthead, - .page__footer { - position: static; - } -} diff --git a/_sass/minimal-mistakes/_variables.scss b/_sass/minimal-mistakes/_variables.scss deleted file mode 100644 index 41f66b6d..00000000 --- a/_sass/minimal-mistakes/_variables.scss +++ /dev/null @@ -1,257 +0,0 @@ -/* ========================================================================== - pyos Variables - breaking away from mm theme - ========================================================================== */ - -/* - Typography - ========================================================================== */ - -/* font weights */ -$weight-1: 100; -$thin-weight: 200; -$weight-3: 300; -$weight-4: 400; -$weight-5: 500; -$semibold-weight: 600; -$bold-weight: 700; -$xbold-weight: 800; -$black-weight: 900; - -$doc-font-size: 15 !default; - -/* paragraph indention */ -$paragraph-indent: false !default; // true, false (default) -$indent-var: 2em !default; - -/* system typefaces */ -$serif: Georgia, Times, serif !default; -$sans-serif: - "Nunito Sans", - -apple-system, - BlinkMacSystemFont, - "Helvetica Neue", - "Lucida Grande", - Arial, - sans-serif !default; -$monospace: Monaco, Consolas, "Courier New", monospace !default; - -/* sans serif typefaces */ -$sans-serif-narrow: $sans-serif !default; -$helvetica: Helvetica, "Helvetica Neue", Arial, sans-serif !default; - -/* serif typefaces */ -$georgia: Georgia, serif !default; -$times: Times, serif !default; -$bodoni: "Bodoni MT", serif !default; -$calisto: "Calisto MT", serif !default; -$garamond: Garamond, serif !default; - -$global-font-family: $sans-serif !default; -$title-font-family: "Poppins", $sans-serif !default; -$header-font-family: "Poppins", $sans-serif; -$caption-font-family: "Nunito Sans", sans-serif !default; -$header-font: "Poppins", san-serif !default; -$body-font: "Nunito Sans", sans-serif !default; - -/* type scale */ -$type-size-1: 2.441rem !default; // ~39.056px -$type-size-2: 1.7rem !default; // ~31.248px -$type-size-3: 1.4rem !default; // ~25.008px -$type-size-4: 1.25rem !default; // ~20px -$type-size-5: 1.1rem !default; // ~13.6px -$type-size-6: 0.75rem !default; // ~12px main body font -$type-size-7: 0.64rem !default; // ~10.24px -$type-size-8: 0.635rem !default; // ~10.16px -$type-size-card-title: 1rem !default; -$type-size-card-body: 1rem !default; -$type-size-card-meta: 0.875rem !default; -$type-size-card-label: 0.85rem !default; -$type-size-card-title-lg: 1.1rem !default; -$type-size-card-title-xl: 1.5rem !default; -$type-size-blog-featured-title: 1.7rem !default; -$type-size-blog-featured-excerpt: 1.1rem !default; -$line-height-card-body: 1.45 !default; -$line-height-blog-featured-excerpt: 1.9 !default; -$line-height-card-meta: 1.35 !default; - -/* headline scale */ -$h-size-1: 1.563rem !default; // ~25.008px -$h-size-2: 2.0rem !default; // section headers -$h-size-3: 1.8rem !default; // ~18px -$h-size-4: 1.5rem !default; // ~17px -$h-size-5: 1.43125rem !default; // ~16.5px -$h-size-6: 1.2rem !default; // ~16px -$hero-title-min-size: 2rem !default; -$hero-title-fluid-size: 4.2vw !default; -$hero-title-max-size: 3rem !default; -$hero-title-max-width: 24ch !default; -$hero-side-image-width: 480px !default; -$hero-side-image-height: 320px !default; -/* Row layout: image width is the smallest of px cap, % of wrapper, and vw (vw pulls size down as the viewport narrows). */ -$hero-side-image-max-width-percent: 45% !default; -$hero-side-image-max-width-vw: 40vw !default; - -/* kbd colors */ -$kbd-color-background: #bbbdc3; -$kbd-color-border: #999ba5; -$kbd-color-text: #222325; - -/* - Colors - ========================================================================== */ -$nav-font-color: #3d2a46; -$nav-hover-color: #8d0065; -$background-block: #d6cfde; - -/* pyos colors */ - -/* Brand color */ -$pyos-mediumpurple: #735fab; //brand color - -/* Design/theme colors */ -$pyos-deeppurple: #33205c; -$pyos-darkpurple: #542668; -$pyos-lightpurple: #e1dfed; -$pyos-softpurple: #bab3d4; -$pyos-teal: #81c0aa; -$pyos-darkteal: #4e7064; -$pyos-magenta: #bb82b0; -$pyos-yellow: #feffe7; -$pyos-white: #ffffff; - -/* semantic brand aliases for reusable UI tokens */ -$pyos-brand-dark-purple: $pyos-deeppurple !default; // #33205c -$pyos-feature-card-bg: $pyos-softpurple !default; // #bab3d4 - -$gray: #24052f !default; -$dark-gray: #4c454e !default; -$darker-gray: mix(#000, $gray, 60%) !default; -$light-gray: mix(#fff, $gray, 50%) !default; -$lighter-gray: mix(#fff, $gray, 90%) !default; - -$background-color: #fff !default; -$code-background-color: #fafafa !default; -$code-background-color-dark: $light-gray !default; -$text-color: $dark-gray !default; -$muted-text-color: mix(#fff, $text-color, 18%) !default; -$border-color: $lighter-gray !default; -$form-background-color: $lighter-gray !default; -$footer-background-color: $lighter-gray !default; - -$primary-color: $pyos-softpurple !default; -$success-color: $pyos-teal !default; -$warning-color: #e6cb72 !default; -$danger-color: #f9937c !default; -$info-color: $pyos-deeppurple !default; -$focus-color: $primary-color !default; -$active-color: mix(#fff, $primary-color, 80%) !default; - -/* YIQ color contrast */ -$yiq-contrasted-dark-default: $dark-gray !default; -$yiq-contrasted-light-default: #fff !default; -$yiq-contrasted-threshold: 175 !default; -$yiq-debug: false !default; - -/* brands */ -$facebook-color: #3b5998 !default; -$flickr-color: #ff0084 !default; -$github-color: #171516 !default; -$gitlab-color: #e24329 !default; -$instagram-color: #517fa4 !default; -$lastfm-color: #d51007 !default; -$linkedin-color: #007bb6 !default; -$mastodon-color: #2b90d9 !default; -$pinterest-color: #cb2027 !default; -$reddit-color: #ff4500 !default; -$rss-color: #fa9b39 !default; -$soundcloud-color: #ff3300 !default; -$stackoverflow-color: #fe7a15 !default; -$tumblr-color: #32506d !default; -$twitter-color: #55acee !default; -$vimeo-color: #1ab7ea !default; -$vine-color: #00bf8f !default; -$youtube-color: #bb0000 !default; - -/* links */ -// Note that the link color is mixed with info-color here -// we could just make it a solid color -$link-color: mix(#7b7979, $info-color, 20%) !default; -$link-color-hover: mix(#000, $link-color, 25%) !default; -$link-color-visited: mix(#fff, $link-color, 15%) !default; -$masthead-link-color: $primary-color !default; -$masthead-link-color-hover: mix(#000, $primary-color, 25%) !default; -$navicon-link-color-hover: mix(#fff, $primary-color, 75%) !default; - -/* notices */ -$notice-background-mix: 80% !default; -$code-notice-background-mix: 90% !default; - -/* connect-with-pyos footer (extends .notice) */ -$connect-notice-padding: 1.5rem !default; -$connect-notice-padding-mobile: 1rem !default; -$connect-notice-margin-y: 1rem !default; -$connect-notice-header-size: 1.35rem !default; -$connect-notice-header-margin: 0.5rem !default; -$connect-notice-line-height: 1.5rem !default; -$connect-notice-paragraph-spacing: 0.5rem !default; -$connect-notice-list-margin-top: 0.25rem !default; -$connect-notice-list-margin-bottom: 0.5rem !default; -$connect-notice-list-item-spacing: 0.25rem !default; -$connect-notice-social-gap-y: 0.25rem !default; -$connect-notice-social-gap-x: 1.25rem !default; - -/* syntax highlighting (base16) */ -$base00: #263238 !default; -$base01: #2e3c43 !default; -$base02: #314549 !default; -$base03: #546e7a !default; -$base04: #b2ccd6 !default; -$base05: #eeffff !default; -$base06: #eeffff !default; -$base07: #ffffff !default; -$base08: #f07178 !default; -$base09: #f78c6c !default; -$base0a: #ffcb6b !default; -$base0b: #c3e88d !default; -$base0c: #89ddff !default; -$base0d: #82aaff !default; -$base0e: #c792ea !default; -$base0f: #ff5370 !default; - -/* - Breakpoints - ========================================================================== */ -$mobile: 480px !default; -$small: 600px !default; -$medium: 768px !default; -$medium-wide: 900px !default; -$large: 1024px !default; -$x-large: 1280px !default; -$max-width: $x-large !default; - -/* Home / landing layout (splash) — shared by _pyos-grid, _pyos-cards */ -$home-autofit-card-min: 300px !default; -$home-contributors-stripe-gap: 0.35rem !default; -$home-contributors-card-max-below-large: 200px !default; -$home-training-feature-tight-max: 420px !default; - -/* - Grid - ========================================================================== */ - -$right-sidebar-width-narrow: 200px !default; -$right-sidebar-width: 300px !default; -$right-sidebar-width-wide: 400px !default; - -/* - Other - ========================================================================== */ - -$border-radius: 15px !default; -$box-shadow: 0 1px 1px rgba(0, 0, 0, 0.125) !default; -$nav-height: 2em !default; -$nav-toggle-height: 2rem !default; -$navicon-width: 1.5rem !default; -$navicon-height: 0.25rem !default; -$global-transition: all 0.2s ease-in-out !default; -$intro-transition: intro 0.3s both !default; diff --git a/_sass/minimal-mistakes/skins/_default.scss b/_sass/minimal-mistakes/skins/_default.scss deleted file mode 100644 index 7489b584..00000000 --- a/_sass/minimal-mistakes/skins/_default.scss +++ /dev/null @@ -1,5 +0,0 @@ -/* ========================================================================== - Default skin - ========================================================================== */ - -// Intentionally left blank diff --git a/_sass/minimal-mistakes/vendor/breakpoint/_breakpoint.scss b/_sass/minimal-mistakes/vendor/breakpoint/_breakpoint.scss deleted file mode 100644 index a0528eb8..00000000 --- a/_sass/minimal-mistakes/vendor/breakpoint/_breakpoint.scss +++ /dev/null @@ -1,114 +0,0 @@ -////////////////////////////// -// Default Variables -////////////////////////////// -$Breakpoint-Settings: ( - 'default media': all, - 'default feature': min-width, - 'default pair': width, - - 'force all media type': false, - 'to ems': false, - 'transform resolutions': true, - - 'no queries': false, - 'no query fallbacks': false, - - 'base font size': 16px, - - 'legacy syntax': false -); - -$breakpoint: () !default; - -////////////////////////////// -// Imports -////////////////////////////// -@import "settings"; -@import "context"; -@import "helpers"; -@import "parsers"; -@import "no-query"; - -@import "respond-to"; - -@import "legacy-settings"; - -////////////////////////////// -// Breakpoint Mixin -////////////////////////////// - -@mixin breakpoint($query, $no-query: false) { - @include legacy-settings-warning; - - // Reset contexts - @include private-breakpoint-reset-contexts(); - - $breakpoint: breakpoint($query, false); - - $query-string: map-get($breakpoint, 'query'); - $query-fallback: map-get($breakpoint, 'fallback'); - - $private-breakpoint-context-holder: map-get($breakpoint, 'context holder') !global; - $private-breakpoint-query-count: map-get($breakpoint, 'query count') !global; - - // Allow for an as-needed override or usage of no query fallback. - @if $no-query != false { - $query-fallback: $no-query; - } - - @if $query-fallback != false { - $context-setter: private-breakpoint-set-context('no-query', $query-fallback); - } - - // Print Out Query String - @if not breakpoint-get('no queries') { - @media #{$query-string} { - @content; - } - } - - @if breakpoint-get('no query fallbacks') != false or breakpoint-get('no queries') == true { - - $type: type-of(breakpoint-get('no query fallbacks')); - $print: false; - - @if ($type == 'bool') { - $print: true; - } - @else if ($type == 'string') { - @if $query-fallback == breakpoint-get('no query fallbacks') { - $print: true; - } - } - @else if ($type == 'list') { - @each $wrapper in breakpoint-get('no query fallbacks') { - @if $query-fallback == $wrapper { - $print: true; - } - } - } - - // Write Fallback - @if ($query-fallback != false) and ($print == true) { - $type-fallback: type-of($query-fallback); - - @if ($type-fallback != 'bool') { - #{$query-fallback} & { - @content; - } - } - @else { - @content; - } - } - } - - @include private-breakpoint-reset-contexts(); -} - - -@mixin mq($query, $no-query: false) { - @include breakpoint($query, $no-query) { - @content; - } -} diff --git a/_sass/minimal-mistakes/vendor/breakpoint/_context.scss b/_sass/minimal-mistakes/vendor/breakpoint/_context.scss deleted file mode 100644 index 982ae691..00000000 --- a/_sass/minimal-mistakes/vendor/breakpoint/_context.scss +++ /dev/null @@ -1,95 +0,0 @@ -////////////////////////////// -// Private Breakpoint Variables -////////////////////////////// -$private-breakpoint-context-holder: (); -$private-breakpoint-query-count: 0 !default; - -////////////////////////////// -// Breakpoint Has Context -// Returns whether or not you are inside a Breakpoint query -////////////////////////////// -@function breakpoint-has-context() { - @if length($private-breakpoint-query-count) { - @return true; - } - @else { - @return false; - } -} - -////////////////////////////// -// Breakpoint Get Context -// $feature: Input feature to get it's current MQ context. Returns false if no context -////////////////////////////// -@function breakpoint-get-context($feature) { - @if map-has-key($private-breakpoint-context-holder, $feature) { - $get: map-get($private-breakpoint-context-holder, $feature); - // Special handling of no-query from get side so /false/ prepends aren't returned - @if $feature == 'no-query' { - @if type-of($get) == 'list' and length($get) > 1 and nth($get, 1) == false { - $get: nth($get, length($get)); - } - } - @return $get; - } - @else { - @if breakpoint-has-context() and $feature == 'media' { - @return breakpoint-get('default media'); - } - @else { - @return false; - } - } -} - -////////////////////////////// -// Private function to set context -////////////////////////////// -@function private-breakpoint-set-context($feature, $value) { - @if $value == 'monochrome' { - $feature: 'monochrome'; - } - - $current: map-get($private-breakpoint-context-holder, $feature); - @if $current and length($current) == $private-breakpoint-query-count { - @warn "You have already queried against `#{$feature}`. Unexpected things may happen if you query against the same feature more than once in the same `and` query. Breakpoint is overwriting the current context with `#{$value}`"; - } - - @if not map-has-key($private-breakpoint-context-holder, $feature) { - $v-holder: (); - @for $i from 1 to $private-breakpoint-query-count { - @if $feature == 'media' { - $v-holder: append($v-holder, breakpoint-get('default media')); - } - @else { - $v-holder: append($v-holder, false); - } - } - $v-holder: append($v-holder, $value); - $private-breakpoint-context-holder: map-merge($private-breakpoint-context-holder, ($feature: $v-holder)) !global; - } - @else { - $v-holder: map-get($private-breakpoint-context-holder, $feature); - $length: length($v-holder); - @for $i from $length to $private-breakpoint-query-count - 1 { - @if $feature == 'media' { - $v-holder: append($v-holder, breakpoint-get('default media')); - } - @else { - $v-holder: append($v-holder, false); - } - } - $v-holder: append($v-holder, $value); - $private-breakpoint-context-holder: map-merge($private-breakpoint-context-holder, ($feature: $v-holder)) !global; - } - - @return true; -} - -////////////////////////////// -// Private function to reset context -////////////////////////////// -@mixin private-breakpoint-reset-contexts { - $private-breakpoint-context-holder: () !global; - $private-breakpoint-query-count: 0 !global; -} diff --git a/_sass/minimal-mistakes/vendor/breakpoint/_helpers.scss b/_sass/minimal-mistakes/vendor/breakpoint/_helpers.scss deleted file mode 100644 index 97e522d1..00000000 --- a/_sass/minimal-mistakes/vendor/breakpoint/_helpers.scss +++ /dev/null @@ -1,151 +0,0 @@ -////////////////////////////// -// Converts the input value to Base EMs -////////////////////////////// -@function breakpoint-to-base-em($value) { - $value-unit: unit($value); - - // Will convert relative EMs into root EMs. - @if breakpoint-get('base font size') and type-of(breakpoint-get('base font size')) == 'number' and $value-unit == 'em' { - $base-unit: unit(breakpoint-get('base font size')); - - @if $base-unit == 'px' or $base-unit == '%' or $base-unit == 'em' or $base-unit == 'pt' { - @return base-conversion($value) / base-conversion(breakpoint-get('base font size')) * 1em; - } - @else { - @warn '#{breakpoint-get(\'base font size\')} is not set in valid units for font size!'; - @return false; - } - } - @else { - @return base-conversion($value); - } -} - -@function base-conversion($value) { - $unit: unit($value); - - @if $unit == 'px' { - @return $value / 16px * 1em; - } - @else if $unit == '%' { - @return $value / 100% * 1em; - } - @else if $unit == 'em' { - @return $value; - } - @else if $unit == 'pt' { - @return $value / 12pt * 1em; - } - @else { - @return $value; -// @warn 'Everything is terrible! What have you done?!'; - } -} - -////////////////////////////// -// Returns whether the feature can have a min/max pair -////////////////////////////// -$breakpoint-min-max-features: 'color', - 'color-index', - 'aspect-ratio', - 'device-aspect-ratio', - 'device-height', - 'device-width', - 'height', - 'monochrome', - 'resolution', - 'width'; - -@function breakpoint-min-max($feature) { - @each $item in $breakpoint-min-max-features { - @if $feature == $item { - @return true; - } - } - @return false; -} - -////////////////////////////// -// Returns whether the feature can have a string value -////////////////////////////// -$breakpoint-string-features: 'orientation', - 'scan', - 'color', - 'aspect-ratio', - 'device-aspect-ratio', - 'pointer', - 'luminosity'; - -@function breakpoint-string-value($feature) { - @each $item in $breakpoint-string-features { - @if breakpoint-min-max($item) { - @if $feature == 'min-#{$item}' or $feature == 'max-#{$item}' { - @return true; - } - } - @else if $feature == $item { - @return true; - } - } - @return false; -} - -////////////////////////////// -// Returns whether the feature is a media type -////////////////////////////// -$breakpoint-media-types: 'all', - 'braille', - 'embossed', - 'handheld', - 'print', - 'projection', - 'screen', - 'speech', - 'tty', - 'tv'; - -@function breakpoint-is-media($feature) { - @each $media in $breakpoint-media-types { - @if ($feature == $media) or ($feature == 'not #{$media}') or ($feature == 'only #{$media}') { - @return true; - } - } - - @return false; -} - -////////////////////////////// -// Returns whether the feature can stand alone -////////////////////////////// -$breakpoint-single-string-features: 'color', - 'color-index', - 'grid', - 'monochrome'; - -@function breakpoint-single-string($feature) { - @each $item in $breakpoint-single-string-features { - @if $feature == $item { - @return true; - } - } - @return false; -} - -////////////////////////////// -// Returns whether the feature -////////////////////////////// -@function breakpoint-is-resolution($feature) { - $resolutions: 'device-pixel-ratio', 'dpr'; - - @if breakpoint-get('transform resolutions') { - $resolutions: append($resolutions, 'resolution'); - } - - @each $reso in $resolutions { - @if index($feature, $reso) or index($feature, 'min-#{$reso}') or index($feature, 'max-#{$reso}') { - @return true; - } - } - - @return false; -} diff --git a/_sass/minimal-mistakes/vendor/breakpoint/_legacy-settings.scss b/_sass/minimal-mistakes/vendor/breakpoint/_legacy-settings.scss deleted file mode 100644 index 4242cba0..00000000 --- a/_sass/minimal-mistakes/vendor/breakpoint/_legacy-settings.scss +++ /dev/null @@ -1,50 +0,0 @@ -@mixin legacy-settings-warning { - $legacyVars: ( - 'default-media': 'default media', - 'default-feature': 'default feature', - 'force-media-all': 'force all media type', - 'to-ems': 'to ems', - 'resolutions': 'transform resolutions', - 'no-queries': 'no queries', - 'no-query-fallbacks': 'no query fallbacks', - 'base-font-size': 'base font size', - 'legacy-syntax': 'legacy syntax' - ); - - @each $legacy, $new in $legacyVars { - @if global-variable-exists('breakpoint-' + $legacy) { - @warn "In order to avoid variable namspace collisions, we have updated the way to change settings for Breakpoint. Please change all instances of `$breakpoint-#{$legacy}: {{setting}}` to `@include breakpoint-set('#{$new}', {{setting}})`. Variable settings, as well as this warning will be deprecated in a future release." - } - }; - - ////////////////////////////// - // Hand correct each setting - ////////////////////////////// - @if global-variable-exists('breakpoint-default-media') and $breakpoint-default-media != breakpoint-get('default media') { - @include breakpoint-set('default media', $breakpoint-default-media); - } - @if global-variable-exists('breakpoint-default-feature') and $breakpoint-default-feature != breakpoint-get('default feature') { - @include breakpoint-set('default feature', $breakpoint-default-feature); - } - @if global-variable-exists('breakpoint-force-media-all') and $breakpoint-force-media-all != breakpoint-get('force all media type') { - @include breakpoint-set('force all media type', $breakpoint-force-media-all); - } - @if global-variable-exists('breakpoint-to-ems') and $breakpoint-to-ems != breakpoint-get('to ems') { - @include breakpoint-set('to ems', $breakpoint-to-ems); - } - @if global-variable-exists('breakpoint-resolutions') and $breakpoint-resolutions != breakpoint-get('transform resolutions') { - @include breakpoint-set('transform resolutions', $breakpoint-resolutions); - } - @if global-variable-exists('breakpoint-no-queries') and $breakpoint-no-queries != breakpoint-get('no queries') { - @include breakpoint-set('no queries', $breakpoint-no-queries); - } - @if global-variable-exists('breakpoint-no-query-fallbacks') and $breakpoint-no-query-fallbacks != breakpoint-get('no query fallbacks') { - @include breakpoint-set('no query fallbacks', $breakpoint-no-query-fallbacks); - } - @if global-variable-exists('breakpoint-base-font-size') and $breakpoint-base-font-size != breakpoint-get('base font size') { - @include breakpoint-set('base font size', $breakpoint-base-font-size); - } - @if global-variable-exists('breakpoint-legacy-syntax') and $breakpoint-legacy-syntax != breakpoint-get('legacy syntax') { - @include breakpoint-set('legacy syntax', $breakpoint-legacy-syntax); - } -} diff --git a/_sass/minimal-mistakes/vendor/breakpoint/_no-query.scss b/_sass/minimal-mistakes/vendor/breakpoint/_no-query.scss deleted file mode 100644 index 0b5a81f6..00000000 --- a/_sass/minimal-mistakes/vendor/breakpoint/_no-query.scss +++ /dev/null @@ -1,15 +0,0 @@ -@function breakpoint-no-query($query) { - @if type-of($query) == 'list' { - $keyword: nth($query, 1); - - @if type-of($keyword) == 'string' and ($keyword == 'no-query' or $keyword == 'no query' or $keyword == 'fallback') { - @return nth($query, 2); - } - @else { - @return false; - } - } - @else { - @return false; - } -} diff --git a/_sass/minimal-mistakes/vendor/breakpoint/_parsers.scss b/_sass/minimal-mistakes/vendor/breakpoint/_parsers.scss deleted file mode 100644 index f0b053fe..00000000 --- a/_sass/minimal-mistakes/vendor/breakpoint/_parsers.scss +++ /dev/null @@ -1,215 +0,0 @@ -////////////////////////////// -// Import Parser Pieces -////////////////////////////// -@import "parsers/query"; -@import "parsers/single"; -@import "parsers/double"; -@import "parsers/triple"; -@import "parsers/resolution"; - -$Memo-Exists: function-exists(memo-get) and function-exists(memo-set); - -////////////////////////////// -// Breakpoint Function -////////////////////////////// -@function breakpoint($query, $contexts...) { - $run: true; - $return: (); - - // Grab the Memo Output if Memoization can be a thing - @if $Memo-Exists { - $return: memo-get(breakpoint, breakpoint $query $contexts); - - @if $return != null { - $run: false; - } - } - - @if not $Memo-Exists or $run { - // Internal Variables - $query-string: ''; - $query-fallback: false; - $return: (); - - // Reserve Global Private Breakpoint Context - $holder-context: $private-breakpoint-context-holder; - $holder-query-count: $private-breakpoint-query-count; - - // Reset Global Private Breakpoint Context - $private-breakpoint-context-holder: () !global; - $private-breakpoint-query-count: 0 !global; - - - // Test to see if it's a comma-separated list - $or-list: if(list-separator($query) == 'comma', true, false); - - - @if ($or-list == false and breakpoint-get('legacy syntax') == false) { - $query-string: breakpoint-parse($query); - } - @else { - $length: length($query); - - $last: nth($query, $length); - $query-fallback: breakpoint-no-query($last); - - @if ($query-fallback != false) { - $length: $length - 1; - } - - @if (breakpoint-get('legacy syntax') == true) { - $mq: (); - - @for $i from 1 through $length { - $mq: append($mq, nth($query, $i), comma); - } - - $query-string: breakpoint-parse($mq); - } - @else { - $query-string: ''; - @for $i from 1 through $length { - $query-string: $query-string + if($i == 1, '', ', ') + breakpoint-parse(nth($query, $i)); - } - } - } - - $return: ('query': $query-string, - 'fallback': $query-fallback, - 'context holder': $private-breakpoint-context-holder, - 'query count': $private-breakpoint-query-count - ); - @if length($contexts) > 0 and nth($contexts, 1) != false { - @if $query-fallback != false { - $context-setter: private-breakpoint-set-context('no-query', $query-fallback); - } - $context-map: (); - @each $context in $contexts { - $context-map: map-merge($context-map, ($context: breakpoint-get-context($context))); - } - $return: map-merge($return, (context: $context-map)); - } - - // Reset Global Private Breakpoint Context - $private-breakpoint-context-holder: () !global; - $private-breakpoint-query-count: 0 !global; - - @if $Memo-Exists { - $holder: memo-set(breakpoint, breakpoint $query $contexts, $return); - } - } - - @return $return; -} - -////////////////////////////// -// General Breakpoint Parser -////////////////////////////// -@function breakpoint-parse($query) { - // Increase number of 'and' queries - $private-breakpoint-query-count: $private-breakpoint-query-count + 1 !global; - - // Set up Media Type - $query-print: ''; - - $force-all: ((breakpoint-get('force all media type') == true) and (breakpoint-get('default media') == 'all')); - $empty-media: true; - @if ($force-all == true) or (breakpoint-get('default media') != 'all') { - // Force the print of the default media type if (force all is true and default media type is all) or (default media type is not all) - $query-print: breakpoint-get('default media'); - $empty-media: false; - } - - - $query-resolution: false; - - $query-holder: breakpoint-parse-query($query); - - - - // Loop over each parsed out query and write it to $query-print - $first: true; - - @each $feature in $query-holder { - $length: length($feature); - - // Parse a single feature - @if ($length == 1) { - // Feature is currently a list, grab the actual value - $feature: nth($feature, 1); - - // Media Type must by convention be the first item, so it's safe to flat override $query-print, which right now should only be the default media type - @if (breakpoint-is-media($feature)) { - @if ($force-all == true) or ($feature != 'all') { - // Force the print of the default media type if (force all is true and default media type is all) or (default media type is not all) - $query-print: $feature; - $empty-media: false; - - // Set Context - $context-setter: private-breakpoint-set-context(media, $query-print); - } - } - @else { - $parsed: breakpoint-parse-single($feature, $empty-media, $first); - $query-print: '#{$query-print} #{$parsed}'; - $first: false; - } - } - // Parse a double feature - @else if ($length == 2) { - @if (breakpoint-is-resolution($feature) != false) { - $query-resolution: $feature; - } - @else { - $parsed: null; - // If it's a string/number pair, - // we check to see if one is a single-string value, - // then we parse it as a normal double - $alpha: nth($feature, 1); - $beta: nth($feature, 2); - @if breakpoint-single-string($alpha) or breakpoint-single-string($beta) { - $parsed: breakpoint-parse-single($alpha, $empty-media, $first); - $query-print: '#{$query-print} #{$parsed}'; - $first: false; - $parsed: breakpoint-parse-single($beta, $empty-media, $first); - $query-print: '#{$query-print} #{$parsed}'; - } - @else { - $parsed: breakpoint-parse-double($feature, $empty-media, $first); - $query-print: '#{$query-print} #{$parsed}'; - $first: false; - } - } - } - // Parse a triple feature - @else if ($length == 3) { - $parsed: breakpoint-parse-triple($feature, $empty-media, $first); - $query-print: '#{$query-print} #{$parsed}'; - $first: false; - } - - } - - @if ($query-resolution != false) { - $query-print: breakpoint-build-resolution($query-print, $query-resolution, $empty-media, $first); - } - - // Loop through each feature that's been detected so far and append 'false' to the the value list to increment their counters - @each $f, $v in $private-breakpoint-context-holder { - $v-holder: $v; - $length: length($v-holder); - @if length($v-holder) < $private-breakpoint-query-count { - @for $i from $length to $private-breakpoint-query-count { - @if $f == 'media' { - $v-holder: append($v-holder, breakpoint-get('default media')); - } - @else { - $v-holder: append($v-holder, false); - } - } - } - $private-breakpoint-context-holder: map-merge($private-breakpoint-context-holder, ($f: $v-holder)) !global; - } - - @return $query-print; -} diff --git a/_sass/minimal-mistakes/vendor/breakpoint/_respond-to.scss b/_sass/minimal-mistakes/vendor/breakpoint/_respond-to.scss deleted file mode 100644 index e2462c5f..00000000 --- a/_sass/minimal-mistakes/vendor/breakpoint/_respond-to.scss +++ /dev/null @@ -1,82 +0,0 @@ -//////////////////////// -// Default the Breakpoints variable -//////////////////////// -$breakpoints: () !default; -$BREAKPOINTS: () !default; - -//////////////////////// -// Respond-to API Mixin -//////////////////////// -@mixin respond-to($context, $no-query: false) { - @if length($breakpoints) > 0 and length($BREAKPOINTS) == 0 { - @warn "In order to avoid variable namespace collisions, we have updated the way to add breakpoints for respond-to. Please change all instances of `$breakpoints: add-breakpoint()` to `@include add-breakpoint()`. The `add-breakpoint()` function will be deprecated in a future release."; - $BREAKPOINTS: $breakpoints !global; - $breakpoints: () !global; - } - - @if type-of($BREAKPOINTS) != 'map' { - // Just in case someone writes gibberish to the $breakpoints variable. - @warn "Your breakpoints aren't a map! `respond-to` expects a map. Please check the value of $BREAKPOINTS variable."; - @content; - } - @else if map-has-key($BREAKPOINTS, $context) { - @include breakpoint(map-get($BREAKPOINTS, $context), $no-query) { - @content; - } - } - @else if not map-has-key($BREAKPOINTS, $context) { - @warn "`#{$context}` isn't a defined breakpoint! Please add it using `$breakpoints: add-breakpoint(`#{$context}`, $value);`"; - @content; - } - @else { - @warn "You haven't created any breakpoints yet! Make some already! `@include add-breakpoint($name, $bkpt)`"; - @content; - } -} - -////////////////////////////// -// Add Breakpoint to Breakpoints -// TODO: Remove function in next release -////////////////////////////// -@function add-breakpoint($name, $bkpt, $overwrite: false) { - $output: ($name: $bkpt); - - @if length($breakpoints) == 0 { - @return $output; - } - @else { - @if map-has-key($breakpoints, $name) and $overwrite != true { - @warn "You already have a breakpoint named `#{$name}`, please choose another breakpoint name, or pass in `$overwrite: true` to overwrite the previous breakpoint."; - @return $breakpoints; - } - @else if not map-has-key($breakpoints, $name) or $overwrite == true { - @return map-merge($breakpoints, $output); - } - } -} - -@mixin add-breakpoint($name, $bkpt, $overwrite: false) { - $output: ($name: $bkpt); - - @if length($BREAKPOINTS) == 0 { - $BREAKPOINTS: $output !global; - } - @else { - @if map-has-key($BREAKPOINTS, $name) and $overwrite != true { - @warn "You already have a breakpoint named `#{$name}`, please choose another breakpoint name, or pass in `$overwrite: true` to overwrite the previous breakpoint."; - $BREAKPOINTS: $BREAKPOINTS !global; - } - @else if not map-has-key($BREAKPOINTS, $name) or $overwrite == true { - $BREAKPOINTS: map-merge($BREAKPOINTS, $output) !global; - } - } -} - -@function get-breakpoint($name: false) { - @if $name == false { - @return $BREAKPOINTS; - } - @else { - @return map-get($BREAKPOINTS, $name); - } -} diff --git a/_sass/minimal-mistakes/vendor/breakpoint/_settings.scss b/_sass/minimal-mistakes/vendor/breakpoint/_settings.scss deleted file mode 100644 index 08631a3c..00000000 --- a/_sass/minimal-mistakes/vendor/breakpoint/_settings.scss +++ /dev/null @@ -1,71 +0,0 @@ -////////////////////////////// -// Has Setting -////////////////////////////// -@function breakpoint-has($setting) { - @if map-has-key($breakpoint, $setting) { - @return true; - } - @else { - @return false; - } -} - -////////////////////////////// -// Get Settings -////////////////////////////// -@function breakpoint-get($setting) { - @if breakpoint-has($setting) { - @return map-get($breakpoint, $setting); - } - @else { - @return map-get($Breakpoint-Settings, $setting); - } -} - -////////////////////////////// -// Set Settings -////////////////////////////// -@function breakpoint-set($setting, $value) { - @if (str-index($setting, '-') or str-index($setting, '_')) and str-index($setting, ' ') == null { - @warn "Words in Breakpoint settings should be separated by spaces, not dashes or underscores. Please replace dashes and underscores between words with spaces. Settings will not work as expected until changed."; - } - $breakpoint: map-merge($breakpoint, ($setting: $value)) !global; - @return true; -} - -@mixin breakpoint-change($setting, $value) { - $breakpoint-change: breakpoint-set($setting, $value); -} - -@mixin breakpoint-set($setting, $value) { - @include breakpoint-change($setting, $value); -} - -@mixin bkpt-change($setting, $value) { - @include breakpoint-change($setting, $value); -} -@mixin bkpt-set($setting, $value) { - @include breakpoint-change($setting, $value); -} - -////////////////////////////// -// Remove Setting -////////////////////////////// -@function breakpoint-reset($settings...) { - @if length($settings) == 1 { - $settings: nth($settings, 1); - } - - @each $setting in $settings { - $breakpoint: map-remove($breakpoint, $setting) !global; - } - @return true; -} - -@mixin breakpoint-reset($settings...) { - $breakpoint-reset: breakpoint-reset($settings); -} - -@mixin bkpt-reset($settings...) { - $breakpoint-reset: breakpoint-reset($settings); -} diff --git a/_sass/minimal-mistakes/vendor/breakpoint/parsers/_double.scss b/_sass/minimal-mistakes/vendor/breakpoint/parsers/_double.scss deleted file mode 100644 index 24580c15..00000000 --- a/_sass/minimal-mistakes/vendor/breakpoint/parsers/_double.scss +++ /dev/null @@ -1,33 +0,0 @@ -////////////////////////////// -// Import Pieces -////////////////////////////// -@import "double/default-pair"; -@import "double/double-string"; -@import "double/default"; - -@function breakpoint-parse-double($feature, $empty-media, $first) { - $parsed: ''; - $leader: ''; - // If we're forcing - @if not ($empty-media) or not ($first) { - $leader: 'and '; - } - - $first: nth($feature, 1); - $second: nth($feature, 2); - - // If we've got two numbers, we know we need to use the default pair because there are no media queries that has a media feature that is a number - @if type-of($first) == 'number' and type-of($second) == 'number' { - $parsed: breakpoint-parse-default-pair($first, $second); - } - // If they are both strings, we send it through the string parser - @else if type-of($first) == 'string' and type-of($second) == 'string' { - $parsed: breakpoint-parse-double-string($first, $second); - } - // If it's a string/number pair, we parse it as a normal double - @else { - $parsed: breakpoint-parse-double-default($first, $second); - } - - @return $leader + $parsed; -} diff --git a/_sass/minimal-mistakes/vendor/breakpoint/parsers/_query.scss b/_sass/minimal-mistakes/vendor/breakpoint/parsers/_query.scss deleted file mode 100644 index b138b393..00000000 --- a/_sass/minimal-mistakes/vendor/breakpoint/parsers/_query.scss +++ /dev/null @@ -1,82 +0,0 @@ -@function breakpoint-parse-query($query) { - // Parse features out of an individual query - $feature-holder: (); - $query-holder: (); - $length: length($query); - - @if $length == 2 { - // If we've got a string/number, number/string, check to see if it's a valid string/number pair or two singles - @if (type-of(nth($query, 1)) == 'string' and type-of(nth($query, 2)) == 'number') or (type-of(nth($query, 1)) == 'number' and type-of(nth($query, 2)) == 'string') { - - $number: ''; - $value: ''; - - @if type-of(nth($query, 1)) == 'string' { - $number: nth($query, 2); - $value: nth($query, 1); - } - @else { - $number: nth($query, 1); - $value: nth($query, 2); - } - - // If the string value can be a single value, check to see if the number passed in is a valid input for said single value. Fortunately, all current single-value options only accept unitless numbers, so this check is easy. - @if breakpoint-single-string($value) { - @if unitless($number) { - $feature-holder: append($value, $number, space); - $query-holder: append($query-holder, $feature-holder, comma); - @return $query-holder; - } - } - // If the string is a media type, split the query - @if breakpoint-is-media($value) { - $query-holder: append($query-holder, nth($query, 1)); - $query-holder: append($query-holder, nth($query, 2)); - @return $query-holder; - } - // If it's not a single feature, we're just going to assume it's a proper string/value pair, and roll with it. - @else { - $feature-holder: append($value, $number, space); - $query-holder: append($query-holder, $feature-holder, comma); - @return $query-holder; - } - - } - // If they're both numbers, we assume it's a double and roll with that - @else if (type-of(nth($query, 1)) == 'number' and type-of(nth($query, 2)) == 'number') { - $feature-holder: append(nth($query, 1), nth($query, 2), space); - $query-holder: append($query-holder, $feature-holder, comma); - @return $query-holder; - } - // If they're both strings and neither are singles, we roll with that. - @else if (type-of(nth($query, 1)) == 'string' and type-of(nth($query, 2)) == 'string') { - @if not breakpoint-single-string(nth($query, 1)) and not breakpoint-single-string(nth($query, 2)) { - $feature-holder: append(nth($query, 1), nth($query, 2), space); - $query-holder: append($query-holder, $feature-holder, comma); - @return $query-holder; - } - } - } - @else if $length == 3 { - // If we've got three items and none is a list, we check to see - @if type-of(nth($query, 1)) != 'list' and type-of(nth($query, 2)) != 'list' and type-of(nth($query, 3)) != 'list' { - // If none of the items are single string values and none of the values are media values, we're good. - @if (not breakpoint-single-string(nth($query, 1)) and not breakpoint-single-string(nth($query, 2)) and not breakpoint-single-string(nth($query, 3))) and ((not breakpoint-is-media(nth($query, 1)) and not breakpoint-is-media(nth($query, 2)) and not breakpoint-is-media(nth($query, 3)))) { - $feature-holder: append(nth($query, 1), nth($query, 2), space); - $feature-holder: append($feature-holder, nth($query, 3), space); - $query-holder: append($query-holder, $feature-holder, comma); - @return $query-holder; - } - // let's check to see if the first item is a media type - @else if breakpoint-is-media(nth($query, 1)) { - $query-holder: append($query-holder, nth($query, 1)); - $feature-holder: append(nth($query, 2), nth($query, 3), space); - $query-holder: append($query-holder, $feature-holder); - @return $query-holder; - } - } - } - - // If it's a single item, or if it's not a special case double or triple, we can simply return the query. - @return $query; -} diff --git a/_sass/minimal-mistakes/vendor/breakpoint/parsers/_resolution.scss b/_sass/minimal-mistakes/vendor/breakpoint/parsers/_resolution.scss deleted file mode 100644 index 19769adf..00000000 --- a/_sass/minimal-mistakes/vendor/breakpoint/parsers/_resolution.scss +++ /dev/null @@ -1,31 +0,0 @@ -@import "resolution/resolution"; - -@function breakpoint-build-resolution($query-print, $query-resolution, $empty-media, $first) { - $leader: ''; - // If we're forcing - @if not ($empty-media) or not ($first) { - $leader: 'and '; - } - - @if breakpoint-get('transform resolutions') and $query-resolution { - $resolutions: breakpoint-make-resolutions($query-resolution); - $length: length($resolutions); - $query-holder: ''; - - @for $i from 1 through $length { - $query: '#{$query-print} #{$leader}#{nth($resolutions, $i)}'; - @if $i == 1 { - $query-holder: $query; - } - @else { - $query-holder: '#{$query-holder}, #{$query}'; - } - } - - @return $query-holder; - } - @else { - // Return with attached resolution - @return $query-print; - } -} diff --git a/_sass/minimal-mistakes/vendor/breakpoint/parsers/_single.scss b/_sass/minimal-mistakes/vendor/breakpoint/parsers/_single.scss deleted file mode 100644 index d9fd764a..00000000 --- a/_sass/minimal-mistakes/vendor/breakpoint/parsers/_single.scss +++ /dev/null @@ -1,26 +0,0 @@ -////////////////////////////// -// Import Pieces -////////////////////////////// -@import "single/default"; - -@function breakpoint-parse-single($feature, $empty-media, $first) { - $parsed: ''; - $leader: ''; - // If we're forcing - @if not ($empty-media) or not ($first) { - $leader: 'and '; - } - - // If it's a single feature that can stand alone, we let it - @if (breakpoint-single-string($feature)) { - $parsed: $feature; - // Set Context - $context-setter: private-breakpoint-set-context($feature, $feature); - } - // If it's not a stand alone feature, we pass it off to the default handler. - @else { - $parsed: breakpoint-parse-default($feature); - } - - @return $leader + '(' + $parsed + ')'; -} diff --git a/_sass/minimal-mistakes/vendor/breakpoint/parsers/_triple.scss b/_sass/minimal-mistakes/vendor/breakpoint/parsers/_triple.scss deleted file mode 100644 index e2732067..00000000 --- a/_sass/minimal-mistakes/vendor/breakpoint/parsers/_triple.scss +++ /dev/null @@ -1,36 +0,0 @@ -////////////////////////////// -// Import Pieces -////////////////////////////// -@import "triple/default"; - -@function breakpoint-parse-triple($feature, $empty-media, $first) { - $parsed: ''; - $leader: ''; - - // If we're forcing - @if not ($empty-media) or not ($first) { - $leader: 'and '; - } - - // separate the string features from the value numbers - $string: null; - $numbers: null; - @each $val in $feature { - @if type-of($val) == string { - $string: $val; - } - @else { - @if type-of($numbers) == 'null' { - $numbers: $val; - } - @else { - $numbers: append($numbers, $val); - } - } - } - - $parsed: breakpoint-parse-triple-default($string, nth($numbers, 1), nth($numbers, 2)); - - @return $leader + $parsed; - -} diff --git a/_sass/minimal-mistakes/vendor/breakpoint/parsers/double/_default-pair.scss b/_sass/minimal-mistakes/vendor/breakpoint/parsers/double/_default-pair.scss deleted file mode 100644 index f88432cc..00000000 --- a/_sass/minimal-mistakes/vendor/breakpoint/parsers/double/_default-pair.scss +++ /dev/null @@ -1,21 +0,0 @@ -@function breakpoint-parse-default-pair($first, $second) { - $default: breakpoint-get('default pair'); - $min: ''; - $max: ''; - - // Sort into min and max - $min: min($first, $second); - $max: max($first, $second); - - // Set Context - $context-setter: private-breakpoint-set-context(min-#{$default}, $min); - $context-setter: private-breakpoint-set-context(max-#{$default}, $max); - - // Make them EMs if need be - @if (breakpoint-get('to ems') == true) { - $min: breakpoint-to-base-em($min); - $max: breakpoint-to-base-em($max); - } - - @return '(min-#{$default}: #{$min}) and (max-#{$default}: #{$max})'; -} diff --git a/_sass/minimal-mistakes/vendor/breakpoint/parsers/double/_default.scss b/_sass/minimal-mistakes/vendor/breakpoint/parsers/double/_default.scss deleted file mode 100644 index 73190ed5..00000000 --- a/_sass/minimal-mistakes/vendor/breakpoint/parsers/double/_default.scss +++ /dev/null @@ -1,22 +0,0 @@ -@function breakpoint-parse-double-default($first, $second) { - $feature: ''; - $value: ''; - - @if type-of($first) == 'string' { - $feature: $first; - $value: $second; - } - @else { - $feature: $second; - $value: $first; - } - - // Set Context - $context-setter: private-breakpoint-set-context($feature, $value); - - @if (breakpoint-get('to ems') == true) { - $value: breakpoint-to-base-em($value); - } - - @return '(#{$feature}: #{$value})' -} diff --git a/_sass/minimal-mistakes/vendor/breakpoint/parsers/double/_double-string.scss b/_sass/minimal-mistakes/vendor/breakpoint/parsers/double/_double-string.scss deleted file mode 100644 index 3462fab7..00000000 --- a/_sass/minimal-mistakes/vendor/breakpoint/parsers/double/_double-string.scss +++ /dev/null @@ -1,22 +0,0 @@ -@function breakpoint-parse-double-string($first, $second) { - $feature: ''; - $value: ''; - - // Test to see which is the feature and which is the value - @if (breakpoint-string-value($first) == true) { - $feature: $first; - $value: $second; - } - @else if (breakpoint-string-value($second) == true) { - $feature: $second; - $value: $first; - } - @else { - @warn "Neither #{$first} nor #{$second} is a valid media query name."; - } - - // Set Context - $context-setter: private-breakpoint-set-context($feature, $value); - - @return '(#{$feature}: #{$value})'; -} diff --git a/_sass/minimal-mistakes/vendor/breakpoint/parsers/resolution/_resolution.scss b/_sass/minimal-mistakes/vendor/breakpoint/parsers/resolution/_resolution.scss deleted file mode 100644 index 36804212..00000000 --- a/_sass/minimal-mistakes/vendor/breakpoint/parsers/resolution/_resolution.scss +++ /dev/null @@ -1,60 +0,0 @@ -@function breakpoint-make-resolutions($resolution) { - $length: length($resolution); - - $output: (); - - @if $length == 2 { - $feature: ''; - $value: ''; - - // Find which is number - @if type-of(nth($resolution, 1)) == 'number' { - $value: nth($resolution, 1); - } - @else { - $value: nth($resolution, 2); - } - - // Determine min/max/standard - @if index($resolution, 'min-resolution') { - $feature: 'min-'; - } - @else if index($resolution, 'max-resolution') { - $feature: 'max-'; - } - - $standard: '(#{$feature}resolution: #{$value})'; - - // If we're not dealing with dppx, - @if unit($value) != 'dppx' { - $base: 96dpi; - @if unit($value) == 'dpcm' { - $base: 243.84dpcm; - } - // Write out feature tests - $webkit: ''; - $moz: ''; - $webkit: '(-webkit-#{$feature}device-pixel-ratio: #{$value / $base})'; - $moz: '(#{$feature}-moz-device-pixel-ratio: #{$value / $base})'; - // Append to output - $output: append($output, $standard, space); - $output: append($output, $webkit, space); - $output: append($output, $moz, space); - } - @else { - $webkit: ''; - $moz: ''; - $webkit: '(-webkit-#{$feature}device-pixel-ratio: #{$value / 1dppx})'; - $moz: '(#{$feature}-moz-device-pixel-ratio: #{$value / 1dppx})'; - $fallback: '(#{$feature}resolution: #{$value / 1dppx * 96dpi})'; - // Append to output - $output: append($output, $standard, space); - $output: append($output, $webkit, space); - $output: append($output, $moz, space); - $output: append($output, $fallback, space); - } - - } - - @return $output; -} diff --git a/_sass/minimal-mistakes/vendor/breakpoint/parsers/single/_default.scss b/_sass/minimal-mistakes/vendor/breakpoint/parsers/single/_default.scss deleted file mode 100644 index 503ef427..00000000 --- a/_sass/minimal-mistakes/vendor/breakpoint/parsers/single/_default.scss +++ /dev/null @@ -1,13 +0,0 @@ -@function breakpoint-parse-default($feature) { - $default: breakpoint-get('default feature'); - - // Set Context - $context-setter: private-breakpoint-set-context($default, $feature); - - @if (breakpoint-get('to ems') == true) and (type-of($feature) == 'number') { - @return '#{$default}: #{breakpoint-to-base-em($feature)}'; - } - @else { - @return '#{$default}: #{$feature}'; - } -} diff --git a/_sass/minimal-mistakes/vendor/breakpoint/parsers/triple/_default.scss b/_sass/minimal-mistakes/vendor/breakpoint/parsers/triple/_default.scss deleted file mode 100644 index 7fa418dd..00000000 --- a/_sass/minimal-mistakes/vendor/breakpoint/parsers/triple/_default.scss +++ /dev/null @@ -1,18 +0,0 @@ -@function breakpoint-parse-triple-default($feature, $first, $second) { - - // Sort into min and max - $min: min($first, $second); - $max: max($first, $second); - - // Set Context - $context-setter: private-breakpoint-set-context(min-#{$feature}, $min); - $context-setter: private-breakpoint-set-context(max-#{$feature}, $max); - - // Make them EMs if need be - @if (breakpoint-get('to ems') == true) { - $min: breakpoint-to-base-em($min); - $max: breakpoint-to-base-em($max); - } - - @return '(min-#{$feature}: #{$min}) and (max-#{$feature}: #{$max})'; -} diff --git a/_sass/minimal-mistakes/vendor/magnific-popup/_magnific-popup.scss b/_sass/minimal-mistakes/vendor/magnific-popup/_magnific-popup.scss deleted file mode 100644 index 27b27bcc..00000000 --- a/_sass/minimal-mistakes/vendor/magnific-popup/_magnific-popup.scss +++ /dev/null @@ -1,649 +0,0 @@ -/* Magnific Popup CSS */ - -@import "settings"; - -//////////////////////// -// -// Contents: -// -// 1. Default Settings -// 2. General styles -// - Transluscent overlay -// - Containers, wrappers -// - Cursors -// - Helper classes -// 3. Appearance -// - Preloader & text that displays error messages -// - CSS reset for buttons -// - Close icon -// - "1 of X" counter -// - Navigation (left/right) arrows -// - Iframe content type styles -// - Image content type styles -// - Media query where size of arrows is reduced -// - IE7 support -// -//////////////////////// - - - -//////////////////////// -// 1. Default Settings -//////////////////////// - -$mfp-overlay-color: #0b0b0b !default; -$mfp-overlay-opacity: 0.8 !default; -$mfp-shadow: 0 0 8px rgba(0, 0, 0, 0.6) !default; // shadow on image or iframe -$mfp-popup-padding-left: 8px !default; // Padding from left and from right side -$mfp-popup-padding-left-mobile: 6px !default; // Same as above, but is applied when width of window is less than 800px - -$mfp-z-index-base: 1040 !default; // Base z-index of popup -$mfp-include-arrows: true !default; // include styles for nav arrows -$mfp-controls-opacity: 0.65 !default; -$mfp-controls-color: #FFF !default; -$mfp-controls-border-color: #3F3F3F !default; -$mfp-inner-close-icon-color: #333 !default; -$mfp-controls-text-color: #CCC !default; // Color of preloader and "1 of X" indicator -$mfp-controls-text-color-hover: #FFF !default; -$mfp-IE7support: true !default; // Very basic IE7 support - -// Iframe-type options -$mfp-include-iframe-type: true !default; -$mfp-iframe-padding-top: 40px !default; -$mfp-iframe-background: #000 !default; -$mfp-iframe-max-width: 900px !default; -$mfp-iframe-ratio: 9/16 !default; - -// Image-type options -$mfp-include-image-type: true !default; -$mfp-image-background: #444 !default; -$mfp-image-padding-top: 40px !default; -$mfp-image-padding-bottom: 40px !default; -$mfp-include-mobile-layout-for-image: true !default; // Removes paddings from top and bottom - -// Image caption options -$mfp-caption-title-color: #F3F3F3 !default; -$mfp-caption-subtitle-color: #BDBDBD !default; - -// A11y -$mfp-use-visuallyhidden: false !default; // Hide content from browsers, but make it available for screen readers - - - -//////////////////////// -// 2. General styles -//////////////////////// - -// Transluscent overlay -.mfp-bg { - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: $mfp-z-index-base + 2; - overflow: hidden; - position: fixed; - - background: $mfp-overlay-color; - opacity: $mfp-overlay-opacity; - @if $mfp-IE7support { - filter: unquote("alpha(opacity=#{$mfp-overlay-opacity*100})"); - } -} - -// Wrapper for popup -.mfp-wrap { - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: $mfp-z-index-base + 3; - position: fixed; - outline: none !important; - -webkit-backface-visibility: hidden; // fixes webkit bug that can cause "false" scrollbar -} - -// Root container -.mfp-container { - text-align: center; - position: absolute; - width: 100%; - height: 100%; - left: 0; - top: 0; - padding: 0 $mfp-popup-padding-left; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} - -// Vertical centerer helper -.mfp-container { - &:before { - content: ''; - display: inline-block; - height: 100%; - vertical-align: middle; - } -} - -// Remove vertical centering when popup has class `mfp-align-top` -.mfp-align-top { - .mfp-container { - &:before { - display: none; - } - } -} - -// Popup content holder -.mfp-content { - position: relative; - display: inline-block; - vertical-align: middle; - margin: 0 auto; - text-align: left; - z-index: $mfp-z-index-base + 5; -} -.mfp-inline-holder, -.mfp-ajax-holder { - .mfp-content { - width: 100%; - cursor: auto; - } -} - -// Cursors -.mfp-ajax-cur { - cursor: progress; -} -.mfp-zoom-out-cur { - &, .mfp-image-holder .mfp-close { - cursor: -moz-zoom-out; - cursor: -webkit-zoom-out; - cursor: zoom-out; - } -} -.mfp-zoom { - cursor: pointer; - cursor: -webkit-zoom-in; - cursor: -moz-zoom-in; - cursor: zoom-in; -} -.mfp-auto-cursor { - .mfp-content { - cursor: auto; - } -} - -.mfp-close, -.mfp-arrow, -.mfp-preloader, -.mfp-counter { - -webkit-user-select:none; - -moz-user-select: none; - user-select: none; -} - -// Hide the image during the loading -.mfp-loading { - &.mfp-figure { - display: none; - } -} - -// Helper class that hides stuff -@if $mfp-use-visuallyhidden { - // From HTML5 Boilerplate https://github.com/h5bp/html5-boilerplate/blob/v4.2.0/doc/css.md#visuallyhidden - .mfp-hide { - border: 0 !important; - clip: rect(0 0 0 0) !important; - height: 1px !important; - margin: -1px !important; - overflow: hidden !important; - padding: 0 !important; - position: absolute !important; - width: 1px !important; - } -} @else { - .mfp-hide { - display: none !important; - } -} - - -//////////////////////// -// 3. Appearance -//////////////////////// - -// Preloader and text that displays error messages -.mfp-preloader { - color: $mfp-controls-text-color; - position: absolute; - top: 50%; - width: auto; - text-align: center; - margin-top: -0.8em; - left: 8px; - right: 8px; - z-index: $mfp-z-index-base + 4; - a { - color: $mfp-controls-text-color; - &:hover { - color: $mfp-controls-text-color-hover; - } - } -} - -// Hide preloader when content successfully loaded -.mfp-s-ready { - .mfp-preloader { - display: none; - } -} - -// Hide content when it was not loaded -.mfp-s-error { - .mfp-content { - display: none; - } -} - -// CSS-reset for buttons -button { - &.mfp-close, - &.mfp-arrow { - overflow: visible; - cursor: pointer; - background: transparent; - border: 0; - -webkit-appearance: none; - display: block; - outline: none; - padding: 0; - z-index: $mfp-z-index-base + 6; - -webkit-box-shadow: none; - box-shadow: none; - } - &::-moz-focus-inner { - padding: 0; - border: 0 - } -} - - -// Close icon -.mfp-close { - width: 44px; - height: 44px; - line-height: 44px; - - position: absolute; - right: 0; - top: 0; - text-decoration: none; - text-align: center; - opacity: $mfp-controls-opacity; - @if $mfp-IE7support { - filter: unquote("alpha(opacity=#{$mfp-controls-opacity*100})"); - } - padding: 0 0 18px 10px; - color: $mfp-controls-color; - - font-style: normal; - font-size: 28px; - font-family: $serif; - - &:hover, - &:focus { - opacity: 1; - @if $mfp-IE7support { - filter: unquote("alpha(opacity=#{1*100})"); - } - } - - &:active { - top: 1px; - } -} -.mfp-close-btn-in { - .mfp-close { - color: $mfp-inner-close-icon-color; - } -} -.mfp-image-holder, -.mfp-iframe-holder { - .mfp-close { - color: $mfp-controls-color; - right: -6px; - text-align: right; - padding-right: 6px; - width: 100%; - } -} - -// "1 of X" counter -.mfp-counter { - position: absolute; - top: 0; - right: 0; - color: $mfp-controls-text-color; - font-size: 12px; - line-height: 18px; -} - -// Navigation arrows -@if $mfp-include-arrows { - .mfp-arrow { - position: absolute; - opacity: $mfp-controls-opacity; - @if $mfp-IE7support { - filter: unquote("alpha(opacity=#{$mfp-controls-opacity*100})"); - } - margin: 0; - top: 50%; - margin-top: -55px; - padding: 0; - width: 90px; - height: 110px; - -webkit-tap-highlight-color: rgba(0,0,0,0); - &:active { - margin-top: -54px; - } - &:hover, - &:focus { - opacity: 1; - @if $mfp-IE7support { - filter: unquote("alpha(opacity=#{1*100})"); - } - } - &:before, - &:after, - .mfp-b, - .mfp-a { - content: ''; - display: block; - width: 0; - height: 0; - position: absolute; - left: 0; - top: 0; - margin-top: 35px; - margin-left: 35px; - border: medium inset transparent; - } - - &:after, - .mfp-a { - - border-top-width: 13px; - border-bottom-width: 13px; - top:8px; - } - - &:before, - .mfp-b { - border-top-width: 21px; - border-bottom-width: 21px; - opacity: 0.7; - } - - } - - .mfp-arrow-left { - left: 0; - - &:after, - .mfp-a { - border-right: 17px solid $mfp-controls-color; - margin-left: 31px; - } - &:before, - .mfp-b { - margin-left: 25px; - border-right: 27px solid $mfp-controls-border-color; - } - } - - .mfp-arrow-right { - right: 0; - &:after, - .mfp-a { - border-left: 17px solid $mfp-controls-color; - margin-left: 39px - } - &:before, - .mfp-b { - border-left: 27px solid $mfp-controls-border-color; - } - } -} - - - -// Iframe content type -@if $mfp-include-iframe-type { - .mfp-iframe-holder { - padding-top: $mfp-iframe-padding-top; - padding-bottom: $mfp-iframe-padding-top; - .mfp-content { - line-height: 0; - width: 100%; - max-width: $mfp-iframe-max-width; - } - .mfp-close { - top: -40px; - } - } - .mfp-iframe-scaler { - width: 100%; - height: 0; - overflow: hidden; - padding-top: $mfp-iframe-ratio * 100%; - iframe { - position: absolute; - display: block; - top: 0; - left: 0; - width: 100%; - height: 100%; - box-shadow: $mfp-shadow; - background: $mfp-iframe-background; - } - } -} - - - -// Image content type -@if $mfp-include-image-type { - - /* Main image in popup */ - img { - &.mfp-img { - width: auto; - max-width: 100%; - height: auto; - display: block; - line-height: 0; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - padding: $mfp-image-padding-top 0 $mfp-image-padding-bottom; - margin: 0 auto; - } - } - - /* The shadow behind the image */ - .mfp-figure { - line-height: 0; - &:after { - content: ''; - position: absolute; - left: 0; - top: $mfp-image-padding-top; - bottom: $mfp-image-padding-bottom; - display: block; - right: 0; - width: auto; - height: auto; - z-index: -1; - box-shadow: $mfp-shadow; - background: $mfp-image-background; - } - small { - color: $mfp-caption-subtitle-color; - display: block; - font-size: 12px; - line-height: 14px; - } - figure { - margin: 0; - } - figcaption { - margin-top: 0; - margin-bottom: 0; // reset for bottom spacing - } - } - .mfp-bottom-bar { - margin-top: -$mfp-image-padding-bottom + 4; - position: absolute; - top: 100%; - left: 0; - width: 100%; - cursor: auto; - } - .mfp-title { - text-align: left; - line-height: 18px; - color: $mfp-caption-title-color; - word-wrap: break-word; - padding-right: 36px; // leave some space for counter at right side - } - - .mfp-image-holder { - .mfp-content { - max-width: 100%; - } - } - - .mfp-gallery { - .mfp-image-holder { - .mfp-figure { - cursor: pointer; - } - } - } - - - @if $mfp-include-mobile-layout-for-image { - @media screen and (max-width: 800px) and (orientation:landscape), screen and (max-height: 300px) { - /** - * Remove all paddings around the image on small screen - */ - .mfp-img-mobile { - .mfp-image-holder { - padding-left: 0; - padding-right: 0; - } - img { - &.mfp-img { - padding: 0; - } - } - .mfp-figure { - // The shadow behind the image - &:after { - top: 0; - bottom: 0; - } - small { - display: inline; - margin-left: 5px; - } - } - .mfp-bottom-bar { - background: rgba(0,0,0,0.6); - bottom: 0; - margin: 0; - top: auto; - padding: 3px 5px; - position: fixed; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - &:empty { - padding: 0; - } - } - .mfp-counter { - right: 5px; - top: 3px; - } - .mfp-close { - top: 0; - right: 0; - width: 35px; - height: 35px; - line-height: 35px; - background: rgba(0, 0, 0, 0.6); - position: fixed; - text-align: center; - padding: 0; - } - } - } - } -} - - - -// Scale navigation arrows and reduce padding from sides -@media all and (max-width: 900px) { - .mfp-arrow { - -webkit-transform: scale(0.75); - transform: scale(0.75); - } - .mfp-arrow-left { - -webkit-transform-origin: 0; - transform-origin: 0; - } - .mfp-arrow-right { - -webkit-transform-origin: 100%; - transform-origin: 100%; - } - .mfp-container { - padding-left: $mfp-popup-padding-left-mobile; - padding-right: $mfp-popup-padding-left-mobile; - } -} - - - -// IE7 support -// Styles that make popup look nicier in old IE -@if $mfp-IE7support { - .mfp-ie7 { - .mfp-img { - padding: 0; - } - .mfp-bottom-bar { - width: 600px; - left: 50%; - margin-left: -300px; - margin-top: 5px; - padding-bottom: 5px; - } - .mfp-container { - padding: 0; - } - .mfp-content { - padding-top: 44px; - } - .mfp-close { - top: 0; - right: 0; - padding-top: 0; - } - } -} diff --git a/_sass/minimal-mistakes/vendor/magnific-popup/_settings.scss b/_sass/minimal-mistakes/vendor/magnific-popup/_settings.scss deleted file mode 100644 index caaca026..00000000 --- a/_sass/minimal-mistakes/vendor/magnific-popup/_settings.scss +++ /dev/null @@ -1,46 +0,0 @@ -//////////////////////// -// Settings // -//////////////////////// - -// overlay -$mfp-overlay-color: #000; // Color of overlay screen -$mfp-overlay-opacity: 0.8; // Opacity of overlay screen -$mfp-shadow: 0 0 8px rgba(0, 0, 0, 0.6); // Shadow on image or iframe - -// spacing -$mfp-popup-padding-left: 8px; // Padding from left and from right side -$mfp-popup-padding-left-mobile: 6px; // Same as above, but is applied when width of window is less than 800px - -$mfp-z-index-base: 1040; // Base z-index of popup - -// controls -$mfp-include-arrows: true; // Include styles for nav arrows -$mfp-controls-opacity: 1; // Opacity of controls -$mfp-controls-color: #fff; // Color of controls -$mfp-controls-border-color: #fff; // Border color of controls -$mfp-inner-close-icon-color: #fff; // Color of close button when inside -$mfp-controls-text-color: #ccc; // Color of preloader and "1 of X" indicator -$mfp-controls-text-color-hover: #fff; // Hover color of preloader and "1 of X" indicator -$mfp-IE7support: true; // Very basic IE7 support - -// Iframe-type options -$mfp-include-iframe-type: true; // Enable Iframe-type popups -$mfp-iframe-padding-top: 40px; // Iframe padding top -$mfp-iframe-background: #000; // Background color of iframes -$mfp-iframe-max-width: 900px; // Maximum width of iframes -$mfp-iframe-ratio: 9/16; // Ratio of iframe (9/16 = widescreen, 3/4 = standard, etc.) - -// Image-type options -$mfp-include-image-type: true; // Enable Image-type popups -$mfp-image-background: #444 !default; -$mfp-image-padding-top: 40px; // Image padding top -$mfp-image-padding-bottom: 40px; // Image padding bottom -$mfp-include-mobile-layout-for-image: true; // Removes paddings from top and bottom - -// Image caption options -$mfp-caption-title-color: #f3f3f3; // Caption title color -$mfp-caption-subtitle-color: #bdbdbd; // Caption subtitle color -.mfp-counter { font-family: $serif; } // Caption font family - -// A11y -$mfp-use-visuallyhidden: false; diff --git a/_sass/minimal-mistakes/vendor/susy/_su.scss b/_sass/minimal-mistakes/vendor/susy/_su.scss deleted file mode 100644 index 83386adb..00000000 --- a/_sass/minimal-mistakes/vendor/susy/_su.scss +++ /dev/null @@ -1,4 +0,0 @@ -// Su -// == - -@import 'susy/su'; diff --git a/_sass/minimal-mistakes/vendor/susy/_susy-prefix.scss b/_sass/minimal-mistakes/vendor/susy/_susy-prefix.scss deleted file mode 100644 index 185b3561..00000000 --- a/_sass/minimal-mistakes/vendor/susy/_susy-prefix.scss +++ /dev/null @@ -1,13 +0,0 @@ -// Susy (Prefixed) -// =============== - -$susy-version: 3; - -@import 'susy/utilities'; -@import 'susy/su-validate'; -@import 'susy/su-math'; -@import 'susy/settings'; -@import 'susy/normalize'; -@import 'susy/parse'; -@import 'susy/syntax-helpers'; -@import 'susy/api'; diff --git a/_sass/minimal-mistakes/vendor/susy/_susy.scss b/_sass/minimal-mistakes/vendor/susy/_susy.scss deleted file mode 100644 index bfda3d08..00000000 --- a/_sass/minimal-mistakes/vendor/susy/_susy.scss +++ /dev/null @@ -1,5 +0,0 @@ -// Susy (Un-Prefixed) -// ================== - -@import 'susy-prefix'; -@import 'susy/unprefix'; diff --git a/_sass/minimal-mistakes/vendor/susy/plugins/_svg-grid.scss b/_sass/minimal-mistakes/vendor/susy/plugins/_svg-grid.scss deleted file mode 100644 index 99db8d1e..00000000 --- a/_sass/minimal-mistakes/vendor/susy/plugins/_svg-grid.scss +++ /dev/null @@ -1,5 +0,0 @@ -// SVG Grid Background -// =================== - -@import 'svg-grid/prefix'; -@import 'svg-grid/svg-unprefix'; diff --git a/_sass/minimal-mistakes/vendor/susy/plugins/svg-grid/_prefix.scss b/_sass/minimal-mistakes/vendor/susy/plugins/svg-grid/_prefix.scss deleted file mode 100644 index 21fb45fa..00000000 --- a/_sass/minimal-mistakes/vendor/susy/plugins/svg-grid/_prefix.scss +++ /dev/null @@ -1,7 +0,0 @@ -// Prefixed SVG Plugin -// =================== - -@import 'svg-settings'; -@import 'svg-utilities'; -@import 'svg-grid-math'; -@import 'svg-api'; diff --git a/_sass/minimal-mistakes/vendor/susy/plugins/svg-grid/_svg-api.scss b/_sass/minimal-mistakes/vendor/susy/plugins/svg-grid/_svg-api.scss deleted file mode 100644 index 7d880e3e..00000000 --- a/_sass/minimal-mistakes/vendor/susy/plugins/svg-grid/_svg-api.scss +++ /dev/null @@ -1,114 +0,0 @@ -/// Plugin: SVG Grid Image -/// ====================== -/// @group plugin_svg-grid -/// @see susy-svg-grid - - - -/// ## Overview -/// If you want to generate svg-backgrounds -/// for help visualizing and debugging your grids, -/// import the SVG Grid Plugin. -/// -/// The plugin adds `svg-grid-colors` setting -/// to your global defaults, -/// which you can override in `$susy`. -/// It also provides you with a new function, -/// `susy-svg-grid()`, -/// which will return inline svg for use in -/// backgrounds or generated content. -/// -/// This function come with an unprefixed alias by default, -/// using the `svg-grid` import. -/// If you only only want prefixed versions of the API, -/// import the `svg-grid/prefix` partial instead. -/// -/// @group plugin_svg-grid -/// -/// @example scss - importing the plugin -/// // The full path to import Susy will depend on your setup… -/// -/// // unprefixed -/// @import 'plugins/svg-grid'; -/// -/// // prefixed -/// @import 'plugins/svg-grid/prefix'; -/// -/// @example scss - generating background grids -/// .grid { -/// background: susy-svg-grid() no-repeat scroll; -/// } - - - -// SVG Grid -// -------- -/// Return inline svg-data in to display the grid. -/// -/// @group plugin_svg-grid -/// -/// @param {Map | List} $grid [$susy] - -/// Map or shorthand defining the current grid -/// @param {Color | List | null} $colors [null] - -/// Column color, or list of colors for column-gradient, -/// used to override the global `svg-grid-colors` setting -/// @param {Length | null} $offset [null] - -/// Manually override the default grid-image offset, -/// to account for grid edges -/// -/// @return {String} - -/// CSS inline-data SVG string, in `url()` format, -/// for use in image or content properties -/// @example scss -/// .grid { -/// background: susy-svg-grid() no-repeat scroll; -/// } -@function susy-svg-grid( - $grid: $susy, - $colors: null, - $offset: null -) { - // Grid parsing & normalizing - $grid: susy-compile($grid, $context-only: true); - - // Color and gradient handling - $gradient: ''; - - @if (not $colors) { - $colors: susy-get('svg-grid-colors'); - } - - @if length($colors) > 1 { - $gradient: _susy-svg-gradient($colors); - $colors: 'url(%23susy-svg-gradient)'; - } @else { - $colors: _susy-svg-color($colors); - } - - // Get a default image-width - $span: ( - 'span': map-get($grid, 'columns'), - 'spread': map-get($grid, 'container-spread'), - ); - $span: map-merge($grid, $span); - $image-width: su-call('su-span', $span); - $image-width: if((type-of($image-width) == 'number'), $image-width, 100%); - - // SVG construction - $columns: map-get($grid, 'columns'); - $offset: $offset or _susy-svg-offset($grid); - - $attrs: 'fill="#{$colors}" width="#{$image-width}"'; - $svg: 'data:image/svg+xml,'; - $svg: $svg + '%3Csvg xmlns="http://www.w3.org/2000/svg" #{$attrs} %3E'; - $svg: $svg + $gradient; - - @for $column from 1 through length($columns) { - $width: susy-span(1 narrow at $column, $grid); - $x: _susy-svg-column-position($column, $grid); - - $svg: $svg + _susy-svg-rect($x, $width, $offset); - } - - @return url('#{$svg}%3C/svg%3E'); -} diff --git a/_sass/minimal-mistakes/vendor/susy/plugins/svg-grid/_svg-grid-math.scss b/_sass/minimal-mistakes/vendor/susy/plugins/svg-grid/_svg-grid-math.scss deleted file mode 100644 index 044801ab..00000000 --- a/_sass/minimal-mistakes/vendor/susy/plugins/svg-grid/_svg-grid-math.scss +++ /dev/null @@ -1,67 +0,0 @@ -// SVG Grid Math -// ============= - - - -// SVG Column Position -// ------------------- -/// Determine the proper horizontal position -/// for a column rectangle -/// -/// @access private -/// -/// @param {Integer} $column - -/// 1-indexed column location on the grid -/// @param {Map} $grid - -/// Normalized settings map representing the current grid -/// -/// @return {Length} - -/// Horizontal position of svg column rectangle, -/// as distance from the grid edge -@function _susy-svg-column-position( - $column, - $grid -) { - $x: $column - 1; - - @if ($x > 0) { - $x: susy-span(first $x wide, $grid); - } - - @return $x; -} - - - -// SVG Offset -// ---------- -/// Determine if a grid image needs to be offset, -/// to account for edge gutters. -/// -/// @access private -/// -/// @param {Map} $grid - -/// Normalized settings map representing the current grid -/// -/// @return {Length | null} - -/// Expected distance from container edge to first column, -/// based on spread values and gutter-widths -@function _susy-svg-offset( - $grid -) { - $columns: su-valid-columns(map-get($grid, 'columns')); - $gutters: su-valid-gutters(map-get($grid, 'gutters')); - $container: su-valid-spread(map-get($grid, 'container-spread')) + 1; - - @if ($container == 0) { - @return null; - } - - $gutter: su-call('su-gutter', $grid); - - @if (type-of($gutter) == 'string') { - @return 'calc(#{$container} * #{$gutter} / 2)'; - } - - @return $container * $gutter / 2; -} diff --git a/_sass/minimal-mistakes/vendor/susy/plugins/svg-grid/_svg-settings.scss b/_sass/minimal-mistakes/vendor/susy/plugins/svg-grid/_svg-settings.scss deleted file mode 100644 index 3fcc91fb..00000000 --- a/_sass/minimal-mistakes/vendor/susy/plugins/svg-grid/_svg-settings.scss +++ /dev/null @@ -1,14 +0,0 @@ -// SVG Settings -// ============ - - -// Susy SVG Defaults -// ================= -/// This plugin adds the `svg-grid-colors` property -/// and default value to `$_susy-defaults` — -/// you can override that value in `$susy` -/// or any other grid settings map. -/// @group plugin_svg-grid -$_susy-defaults: map-merge(( - 'svg-grid-colors': hsla(120, 50%, 50%, 0.5) hsla(120, 50%, 75%, 0.5), - ), $_susy-defaults); diff --git a/_sass/minimal-mistakes/vendor/susy/plugins/svg-grid/_svg-unprefix.scss b/_sass/minimal-mistakes/vendor/susy/plugins/svg-grid/_svg-unprefix.scss deleted file mode 100644 index 187157cf..00000000 --- a/_sass/minimal-mistakes/vendor/susy/plugins/svg-grid/_svg-unprefix.scss +++ /dev/null @@ -1,18 +0,0 @@ -// Unprefix Susy SVG Grid -// ====================== - - - -// SVG Grid -// -------- -/// Un-prefixed alias for `susy-svg-grid` -/// -/// @group plugin_svg-grid -/// @alias susy-svg-grid -@function svg-grid( - $grid: $susy, - $colors: susy-get('svg-grid-colors'), - $offset: null -) { - @return susy-svg-grid($grid, $colors, $offset); -} diff --git a/_sass/minimal-mistakes/vendor/susy/plugins/svg-grid/_svg-utilities.scss b/_sass/minimal-mistakes/vendor/susy/plugins/svg-grid/_svg-utilities.scss deleted file mode 100644 index e4bf18ff..00000000 --- a/_sass/minimal-mistakes/vendor/susy/plugins/svg-grid/_svg-utilities.scss +++ /dev/null @@ -1,133 +0,0 @@ -// SVG Utilities -// ============= - - - -// SVG Validate Units -// ------------------ -/// Make sure a length is supported in svg -/// -/// @access private -/// -/// @param {Length} $length - -/// The length to validate -/// @param {String} $name [null] - -/// Optional name of length origin, -/// for error reporting -/// -/// @return {Length} - -/// An svg-validated length, or comparable valid length -@function _susy-svg-validate-units( - $length, - $name: null -) { - $_svg-units: ('em', 'ex', 'px', 'pt', 'pc', 'cm', 'mm', 'in', '%'); - $string: type-of($length) == 'string'; - - @if ($length == 0) or ($string) or index($_svg-units, unit($length)) { - @return $length; - } - - @return _susy-error( - '`#{unit($length)}` #{$name} units are not supported in SVG', - '_susy-svg-validate-units'); -} - - - -// SVG Rect -// -------- -/// Build a single svg rectangle -/// -/// @access private -/// -/// @param {Length} $x - -/// Horizontal position for the rectangle -/// @param {Length} $width - -/// Width of the rectangle -/// @param {Length} $offset [null] - -/// Offset the rectangle, to account for edge gutters -/// -/// @return {String} - -/// Escaped string representing one svg rectangle -@function _susy-svg-rect( - $x, - $width, - $offset: null -) { - $x: _susy-svg-validate-units($x); - $width: _susy-svg-validate-units($width); - $offset: if($offset == 0, null, $offset); - - @if (type-of($offset) == 'number') and (type-of($x) == 'number') { - @if comparable($x, $offset) { - $x: $x + $offset; - } @else { - $x: 'calc(#{$x} + #{$offset})'; - } - } @else if $offset and ($x != 0) { - $x: 'calc(#{$x} + #{$offset})'; - } @else if $offset { - $x: $offset; - } - - @return '%3Crect x="#{$x}" width="#{$width}" height="100%"/%3E'; -} - - - -// SVG Color -// --------- -/// Stringify colors, and escape hex symbol -/// -/// @access private -/// -/// @param {Color} $color - -/// Color to stringify and escape -/// -/// @return {String} - -/// Escaped string value of color -@function _susy-svg-color( - $color -) { - $color: inspect($color); // convert to string - - @if (str-index($color, '#') == 1) { - $color: '%23' + str-slice($color, 2); - } - - @return $color; -} - - - -// SVG Gradient -// ------------ -/// Create a multi-color svg gradient -/// -/// @access private -/// -/// @param {List} $colors - -/// List of colors to be equally spaced from `0%` to `100%` -/// in each column rectangle -/// -/// @return {String} - -/// Escaped string representing one svg gradient -/// (`id="susy-svg-gradient"`) -@function _susy-svg-gradient( - $colors -) { - $gradient: '%3Cdefs%3E%3ClinearGradient spreadMethod="pad"'; - $gradient: '#{$gradient} id="susy-svg-gradient"'; - $gradient: '#{$gradient} x1="0%" y1="0%" x2="100%" y2="0%"%3E'; - - @for $i from 1 through length($colors) { - $color: _susy-svg-color(nth($colors, $i)); - $offset: percentage(($i - 1) / (length($colors) - 1)); - $stop: '%3Cstop offset="#{$offset}" style="stop-color:#{$color};" /%3E'; - - $gradient: $gradient + $stop; - } - - @return $gradient + '%3C/linearGradient%3E%3C/defs%3E'; -} diff --git a/_sass/minimal-mistakes/vendor/susy/susy/_api.scss b/_sass/minimal-mistakes/vendor/susy/susy/_api.scss deleted file mode 100644 index de8c9bdd..00000000 --- a/_sass/minimal-mistakes/vendor/susy/susy/_api.scss +++ /dev/null @@ -1,318 +0,0 @@ -/// Susy3 API Functions -/// =================== -/// These three functions form the core of Susy's -/// layout-building grid API. -/// -/// - Use `span()` and `gutter()` to return any grid-width, -/// and apply the results wherever you need them: -/// CSS `width`, `margin`, `padding`, `flex-basis`, `transform`, etc. -/// - For asymmetrical-fluid grids, -/// `slice()` can help manage your nesting context. -/// -/// All three functions come with an unprefixed alias by default, -/// using the `susy` import. -/// Import the `susy-prefix` partial instead, -/// if you only only want prefixed versions of the API. -/// -/// This is a thin syntax-sugar shell around -/// the "Su" core-math functions: `su-span`, `su-gutter`, and `su-slice`. -/// If you prefer the more constrained syntax of the math engine, -/// you are welcome to use those functions instead. -/// -/// @group b-api -/// @see susy-span -/// @see susy-gutter -/// @see susy-slice -/// @see su-span -/// @see su-gutter -/// @see su-slice - - - -/// ## Shorthand -/// -/// All functions draw on the same shorthand syntax in two parts, -/// seperated by the word `of`. -/// -/// ### Span Syntax: `` [`` ``] -/// The first part describes the -/// **span** width, location, and spread in any order. -/// Only the width is required: -/// -/// - `span(2)` will return the width of 2 columns. -/// - `span(3 wide)` will return 3-columns, with an additional gutter. -/// - location is only needed with asymmetrical grids, -/// where `span(3 at 2)` will return the width of -/// specific columns on the grid. -/// Since these are functions, they will not handle placement for you. -/// -/// ### Context Syntax: `[of ]` -/// The second half of Susy's shorthand -/// describes the grid-**context** – -/// available columns, container-spread, and optional gutter override – -/// in any order. -/// All of these settings have globally-defined defaults: -/// -/// - `span(2 of 6)` will set the context to -/// a slice of 6 columns from the global grid. -/// More details below. -/// - `span(2 of 12 wide)` changes the container-spread -/// as well as the column-context. -/// - `span(2 of 12 set-gutters 0.5em)` -/// will override the global gutters setting -/// for this one calculation. -/// -/// A single unitless number for `columns` -/// will be treated as a slice of the parent grid. -/// On a grid with `columns: susy-repeat(12, 120px)`, -/// the shorthand `of 4` will use the parent `120px` column-width. -/// You can also be more explicit, -/// and say `of susy-repeat(4, 100px)`. -/// If you are using asymmetrical grids, -/// like `columns: (1 1 2 3 5 8)`, -/// Susy can't slice it for you without knowing which columns you want. -/// The `slice` function accepts exactly the same syntax as `span`, -/// but returns a list of columns rather than a width. -/// Use it in your context like `of slice(first 3)`. -/// -/// @group b-api - - - -// Susy Span -// --------- -/// This is the primary function in Susy — -/// used to return the width of a span across one or more columns, -/// and any relevant gutters along the way. -/// With the default settings, -/// `span(3)` will return the width of 3 columns, -/// and the 2 intermediate gutters. -/// This can be used to set the `width` property of grid elements, -/// or `margin` and `padding` -/// to push, pull, and pad your elements. -/// -/// - This is a thin syntax-sugar shell around -/// the core-math `su-span()` function. -/// - The un-prefixed alias `span()` is available by default. -/// -/// @group b-api -/// @see su-span -/// @see $susy -/// -/// @param {list} $span - -/// Shorthand expression to define the width of the span, -/// optionally containing: -/// - a count, length, or column-list span. -/// - `at $n`, `first`, or `last` location on asymmetrical grids, -/// where `at 1 == first`, -/// and `last` will calculate the proper location -/// based on columns and span. -/// - `narrow`, `wide`, or `wider` for optionally spreading -/// across adjacent gutters. -/// - `of $n ` for available grid columns -/// and spread of the container. -/// Span counts like `of 6` are valid -/// in the context of symmetrical grids, -/// where Susy can safely infer a slice of the parent columns. -/// - and `set-gutters $n` to override global gutter settings. -/// -/// @param {map} $config [()] - -/// Optional map of Susy grid configuration settings. -/// See `$susy` documentation for details. -/// -/// @return {length} - -/// Calculated length value, using the units given, -/// or converting to `%` for fraction-based grids, -/// or a full `calc` function when units/fractions -/// are not comparable outside the browser. -/// -/// @example scss - span half the grid -/// .foo { -/// // the result is a bit under 50% to account for gutters -/// width: susy-span(6 of 12); -/// } -/// -/// @example scss - span a specific segment of asymmetrical grid -/// .foo { -/// width: susy-span(3 at 3 of (1 2 3 5 8)); -/// } -@function susy-span( - $span, - $config: () -) { - $output: susy-compile($span, $config); - - @if map-get($output, 'span') { - @return su-call('su-span', $output); - } - - $actual: '[#{type-of($span)}] `#{inspect($span)}`'; - @return _susy-error( - 'Unable to determine span value from #{$actual}.', - 'susy-span'); -} - - - -// Susy Gutter -// ----------- -/// The gutter function returns -/// the width of a single gutter on your grid, -/// to be applied where you see fit – -/// on `margins`, `padding`, `transform`, or element `width`. -/// -/// - This is a thin syntax-sugar shell around -/// the core-math `su-gutter()` function. -/// - The un-prefixed alias `gutter()` is available by default. -/// -/// @group b-api -/// @see su-gutter -/// @see $susy -/// -/// @param {list | number} $context [null] - -/// Optional context for nested gutters, -/// including shorthand for -/// `columns`, `gutters`, and `container-spread` -/// (additional shorthand will be ignored) -/// -/// @param {map} $config [()] - -/// Optional map of Susy grid configuration settings. -/// See `$susy` documentation for details. -/// -/// @return {length} - -/// Width of a gutter as `%` of current context, -/// or in the units defined by `column-width` when available -/// -/// @example scss - add gutters before or after an element -/// .floats { -/// float: left; -/// width: span(3 of 6); -/// margin-left: gutter(of 6); -/// } -/// -/// @example scss - add gutters to padding -/// .flexbox { -/// flex: 1 1 span(3 wide of 6 wide); -/// padding: gutter(of 6) / 2; -/// } -/// -@function susy-gutter( - $context: susy-get('columns'), - $config: () -) { - $context: susy-compile($context, $config, 'context-only'); - - @return su-call('su-gutter', $context); -} - - - -// Susy Slice -// ---------- -/// Working with asymmetrical grids (un-equal column widths) -/// can be challenging –  -/// expecially when they involve fluid/fractional elements. -/// Describing a context `of (15em 6em 6em 6em 15em)` is a lot -/// to put inside the span or gutter function shorthand. -/// This slice function returns a sub-slice of asymmetrical columns to use -/// for a nested context. -/// `slice(3 at 2)` will give you a subset of the global grid, -/// spanning 3 columns, starting with the second. -/// -/// - This is a thin syntax-sugar shell around -/// the core-math `su-slice()` function. -/// - The un-prefixed alias `slice()` is available by default. -/// -/// @group b-api -/// @see su-slice -/// @see $susy -/// -/// @param {list} $span - -/// Shorthand expression to define the subset span, optionally containing: -/// - `at $n`, `first`, or `last` location on asymmetrical grids; -/// - `of $n ` for available grid columns -/// and spread of the container -/// - Span-counts like `of 6` are only valid -/// in the context of symmetrical grids -/// - Valid spreads include `narrow`, `wide`, or `wider` -/// -/// @param {map} $config [()] - -/// Optional map of Susy grid configuration settings. -/// See `$susy` documentation for details. -/// -/// @return {list} - -/// Subset list of columns for use for a nested context -/// -/// @example scss - Return a nested segment of asymmetrical grid -/// $context: susy-slice(3 at 3 of (1 2 3 5 8)); -/// /* $context: #{$context}; */ -@function susy-slice( - $span, - $config: () -) { - $span: susy-compile($span, $config); - - @return su-call('su-slice', $span); -} - - - -/// ## Building Grids -/// The web has come a long way -/// since the days of double-margin-hacks -/// and inconsistent subpixel rounding. -/// In addition to floats and tables, -/// we can now use much more powerful tools, -/// like flexbox and CSS grid, -/// to build more interesting and responsive layouts. -/// -/// With Susy3, we hope you'll start moving in that direction. -/// You can still build classic 12-column Grid Systems, -/// and we'll help you get there, -/// but Susy3 is primarily designed for a grid-math-on-demand -/// approach to layout: -/// applying our functions only where you really need grid math. -/// Read the [intro article by OddBird][welcome] for more details. -/// -/// [welcome]: http://oddbird.net/2017/06/28/susy3/ -/// -/// @group b-api -/// @link http://oddbird.net/2017/06/28/susy3/ Article: Welcome to Susy3 -/// -/// @example scss - floats -/// .float { -/// width: span(3); -/// margin-right: gutter(); -/// } -/// -/// @example scss - flexbox -/// .flexbox { -/// flex: 1 1 span(3); -/// // half a gutter on either side… -/// padding: 0 gutter() / 2; -/// } -/// -/// @example scss - pushing and pulling -/// .push-3 { -/// margin-left: span(3 wide); -/// } -/// -/// .pull-3 { -/// margin-left: 0 - span(3 wide); -/// } -/// -/// @example scss - building an attribute system -/// // markup example:

-/// [data-span] { -/// float: left; -/// -/// &:not([data-span*='last']) { -/// margin-right: gutter(); -/// } -/// } -/// -/// @for $span from 1 through length(susy-get('columns')) { -/// [data-span*='#{$span}'] { -/// width: span($span); -/// } -/// } diff --git a/_sass/minimal-mistakes/vendor/susy/susy/_normalize.scss b/_sass/minimal-mistakes/vendor/susy/susy/_normalize.scss deleted file mode 100644 index a988504c..00000000 --- a/_sass/minimal-mistakes/vendor/susy/susy/_normalize.scss +++ /dev/null @@ -1,261 +0,0 @@ -/// Syntax Normalization -/// ==================== -/// Susy is divided into two layers: -/// "Su" provides the core math functions with a stripped-down syntax, -/// while "Susy" adds global settings, shorthand syntax, -/// and other helpers. -/// Each setting (e.g. span, location, columns, spread, etc.) -/// has a single canonical syntax in Su. -/// -/// This normalization module helps translate between those layers, -/// transforming parsed Susy input into -/// values that Su will understand. -/// -/// @group x-normal -/// -/// @see susy-normalize -/// @see susy-normalize-span -/// @see susy-normalize-columns -/// @see susy-normalize-spread -/// @see susy-normalize-location - - - -// Susy Normalize -// -------------- -/// Normalize the values in a configuration map. -/// In addition to the global `$susy` properties, -/// this map can include local span-related imformation, -/// like `span` and `location`. -/// -/// Normalization does not check that values are valid, -/// which will happen in the Su math layer. -/// These functions merely look for known Susy syntax – -/// returning a map with those shorthand values -/// converted into low-level data for Su. -/// For example `span: all` and `location: first` -/// will be converted into specific numbers. -/// -/// @group x-normal -/// @see $susy -/// @see susy-parse -/// -/// @param {map} $config - -/// Map of Susy configuration settings to normalize. -/// See `$susy` and `susy-parse()` documentation for details. -/// @param {map | null} $context [null] - -/// Map of Susy configuration settings to use as global reference, -/// or `null` to use global settings. -/// -/// @return {map} - -/// Map of Susy configuration settings, -/// with all values normalized for Su math functions. -@function susy-normalize( - $config, - $context: null -) { - // Spread - @each $setting in ('spread', 'container-spread') { - $value: map-get($config, $setting); - - @if $value { - $value: susy-normalize-spread($value); - $config: map-merge($config, ($setting: $value)); - } - } - - // Columns - $columns: map-get($config, 'columns'); - - @if $columns { - $columns: susy-normalize-columns($columns, $context); - $config: map-merge($config, ('columns': $columns)); - } - - @if not $columns { - $map: type-of($context) == 'map'; - $columns: if($map, map-get($context, 'columns'), null); - $columns: $columns or susy-get('columns'); - } - - // Span - $span: map-get($config, 'span'); - - @if $span { - $span: susy-normalize-span($span, $columns); - $config: map-merge($config, ('span': $span)); - } - - // Location - $location: map-get($config, 'location'); - - @if $location { - $location: susy-normalize-location($span, $location, $columns); - $config: map-merge($config, ('location': $location)); - } - - @return $config; -} - - - -// Normalize Span -// -------------- -/// Normalize `span` shorthand for Su. -/// Su span syntax allows an explicit length (e.g. `3em`), -/// unitless column-span number (e.g. `3` columns), -/// or an explicit list of columns (e.g. `(3 5 8)`). -/// -/// Susy span syntax also allows the `all` keyword, -/// which will be converted to a slice of the context -/// in normalization. -/// -/// @group x-normal -/// -/// @param {number | list | 'all'} $span - -/// Span value to normalize. -/// @param {list} $columns - -/// Normalized list of columns in the grid -/// -/// @return {number | list} - -/// Number or list value for `$span` -@function susy-normalize-span( - $span, - $columns: susy-get('columns') -) { - @if ($span == 'all') { - @return length($columns); - } - - @return $span; -} - - - -// Normalize Columns -// ----------------- -/// Normalize `column` shorthand for Su. -/// Su column syntax only allows column lists (e.g. `120px 1 1 1 120px`). -/// -/// Susy span syntax also allows a unitless `slice` number (e.g `of 5`), -/// which will be converted to a slice of the context -/// in normalization. -/// -/// @group x-normal -/// -/// @param {list | integer} $columns - -/// List of available columns, -/// or unitless integer representing a slice of -/// the available context. -/// @param {map | null} $context [null] - -/// Map of Susy configuration settings to use as global reference, -/// or `null` to access global settings. -/// -/// @return {list} - -/// Columns list value, normalized for Su input. -/// -/// @throws -/// when attempting to access a slice of asymmetrical context -@function susy-normalize-columns( - $columns, - $context: null -) { - $context: $context or susy-settings(); - - @if type-of($columns) == 'list' { - @return _susy-flatten($columns); - } - - @if (type-of($columns) == 'number') and (unitless($columns)) { - $span: $columns; - $context: map-get($context, 'columns'); - $symmetrical: susy-repeat(length($context), nth($context, 1)); - - @if ($context == $symmetrical) { - @return susy-repeat($span, nth($context, 1)); - } @else { - $actual: 'of `#{$span}`'; - $columns: 'grid-columns `#{$context}`'; - @return _susy-error( - 'context-slice #{$actual} can not be determined based on #{$columns}.', - 'susy-normalize-columns'); - } - } - - @return $columns; -} - - - -// Normalize Spread -// ---------------- -/// Normalize `spread` shorthand for Su. -/// Su spread syntax only allows the numbers `-1`, `0`, or `1` – -/// representing the number of gutters covered -/// in relation to columns spanned. -/// -/// Susy spread syntax also allows keywords for each value – -/// `narrow` for `-1`, `wide` for `0`, or `wider` for `1` – -/// which will be converted to their respective integers -/// in normalization. -/// -/// @group x-normal -/// -/// @param {0 | 1 | -1 | 'narrow' | 'wide' | 'wider'} $spread - -/// Spread across adjacent gutters, relative to a column-count — -/// either `narrow` (-1), `wide` (0), or `wider` (1) -/// -/// @return {number} - -/// Numeric value for `$spread` -@function susy-normalize-spread( - $spread -) { - $normal-spread: ( - 'narrow': -1, - 'wide': 0, - 'wider': 1, - ); - - @return map-get($normal-spread, $spread) or $spread; -} - - - -// Normalize Location -// ------------------ -/// Normalize `location` shorthand for Su. -/// Su location syntax requires the (1-indexed) number for a column. -/// -/// Susy also allows the `first` and `last` keywords, -/// where `first` is always `1`, -/// and `last` is calculated based on span and column values. -/// Both keywords are normalized into an integer index -/// in normalization. -/// -/// @group x-normal -/// -/// @param {number} $span - -/// Number of grid-columns to be spanned -/// @param {integer | 'first' | 'last'} $location - -/// Starting (1-indexed) column position of a span, -/// or a named location keyword. -/// @param {list} $columns - -/// Already-normalized list of columns in the grid. -/// -/// @return {integer} - -/// Numeric value for `$location` -@function susy-normalize-location( - $span, - $location, - $columns -) { - $count: length($columns); - $normal-locations: ( - 'first': 1, - 'alpha': 1, - 'last': $count - $span + 1, - 'omega': $count - $span + 1, - ); - - @return map-get($normal-locations, $location) or $location; -} diff --git a/_sass/minimal-mistakes/vendor/susy/susy/_parse.scss b/_sass/minimal-mistakes/vendor/susy/susy/_parse.scss deleted file mode 100644 index 98aa40a9..00000000 --- a/_sass/minimal-mistakes/vendor/susy/susy/_parse.scss +++ /dev/null @@ -1,163 +0,0 @@ -/// Shorthand Syntax Parser -/// ======================= -/// The syntax parser converts [shorthand syntax][short] -/// into a map of settings that can be compared/merged with -/// other config maps and global setting. -/// -/// [short]: b-api.html -/// -/// @group x-parser - - - -// Parse -// ----- -/// The `parse` function provides all the syntax-sugar in Susy, -/// converting user shorthand -/// into a usable map of keys and values -/// that can be normalized and passed to Su. -/// -/// @group x-parser -/// @see $susy -/// -/// @param {list} $shorthand - -/// Shorthand expression to define the width of the span, -/// optionally containing: -/// - a count, length, or column-list span; -/// - `at $n`, `first`, or `last` location on asymmetrical grids; -/// - `narrow`, `wide`, or `wider` for optionally spreading -/// across adjacent gutters; -/// - `of $n ` for available grid columns -/// and spread of the container -/// (span counts like `of 6` are only valid -/// in the context of symmetrical grids); -/// - and `set-gutters $n` to override global gutter settings -/// @param {bool} $context-only [false] - -/// Allow the parser to ignore span and span-spread values, -/// only parsing context and container-spread. -/// This makes it possible to accept spanless values, -/// like the `gutters()` syntax. -/// When parsing context-only, -/// the `of` indicator is optional. -/// -/// @return {map} - -/// Map of span and grid settings -/// parsed from shorthand input – -/// including all the properties available globally – -/// `columns`, `gutters`, `spread`, `container-spread` – -/// along with the span-specific properties -/// `span`, and `location`. -/// -/// @throw -/// when a shorthand value is not recognized -@function susy-parse( - $shorthand, - $context-only: false -) { - $parse-error: 'Unknown shorthand property:'; - $options: ( - 'first': 'location', - 'last': 'location', - 'alpha': 'location', - 'omega': 'location', - 'narrow': 'spread', - 'wide': 'spread', - 'wider': 'spread', - ); - - $return: (); - $span: null; - $columns: null; - - $of: null; - $next: false; - - // Allow context-only shorthand, without span - @if ($context-only) and (not index($shorthand, 'of')) { - @if su-valid-columns($shorthand, 'fail-silent') { - $shorthand: 'of' $shorthand; - } @else { - $shorthand: join('of', $shorthand); - } - } - - // loop through the shorthand list - @for $i from 1 through length($shorthand) { - $item: nth($shorthand, $i); - $type: type-of($item); - $error: false; - $details: '[#{$type}] `#{$item}`'; - - // if we know what's supposed to be coming next… - @if $next { - - // Add to the return map - $return: map-merge($return, ($next: $item)); - - // Reset next to `false` - $next: false; - - } @else { // If we don't know what's supposed to be coming… - - // Keywords… - @if ($type == 'string') { - // Check the map for keywords… - @if map-has-key($options, $item) { - $setting: map-get($options, $item); - - // Spread could be on the span or the container… - @if ($setting == 'spread') and ($of) { - $return: map-merge($return, ('container-spread': $item)); - } @else { - $return: map-merge($return, ($setting: $item)); - } - - } @else if ($item == 'all') { - // `All` is a span shortcut - $span: 'all'; - } @else if ($item == 'at') { - // Some keywords setup what's next… - $next: 'location'; - } @else if ($item == 'set-gutters') { - $next: 'gutters'; - } @else if ($item == 'of') { - $of: true; - } @else { - $error: true; - } - - } @else if ($type == 'number') or ($type == 'list') { // Numbers & lists… - - @if not ($span or $of) { - // We don't have a span, and we're not expecting context… - $span: $item; - } @else if ($of) and (not $columns) { - // We are expecting context… - $columns: $item; - } @else { - $error: true; - } - - } @else { - $error: true; - } - } - - @if $error { - @return _susy-error('#{$parse-error} #{$details}', 'susy-parse'); - } - } - - // If we have span, merge it in - @if $span { - $return: map-merge($return, ('span': $span)); - } - - // If we have columns, merge them in - @if $columns { - $return: map-merge($return, ('columns': $columns)); - } - - // Return the map of settings… - @return $return; -} diff --git a/_sass/minimal-mistakes/vendor/susy/susy/_settings.scss b/_sass/minimal-mistakes/vendor/susy/susy/_settings.scss deleted file mode 100644 index b824477c..00000000 --- a/_sass/minimal-mistakes/vendor/susy/susy/_settings.scss +++ /dev/null @@ -1,329 +0,0 @@ -/// Susy3 Configuration -/// =================== -/// Susy3 has 4 core settings, in a single settings map. -/// You'll notice a few differences from Susy2: -/// -/// **Columns** no longer accept a single number, like `12`, -/// but use a syntax more similar to the new -/// CSS [grid-template-columns][columns] – -/// a list of relative sizes for each column on the grid. -/// Unitless numbers in Susy act very similar to `fr` units in CSS, -/// and the `susy-repeat()` function (similar to the css `repeat()`) -/// helps quickly establish equal-width columns. -/// -/// [columns]: https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns -/// -/// - `susy-repeat(12)` will create 12 fluid, equal-width columns -/// - `susy-repeat(6, 120px)` will create 6 equal `120px`-wide columns -/// - `120px susy-repeat(4) 120px` will create 6 columns, -/// the first and last are `120px`, -/// while the middle 4 are equal fractions of the remainder. -/// Susy will output `calc()` values in order to achieve this. -/// -/// **Gutters** haven't changed – -/// a single fraction or explicit width – -/// but the `calc()` output feature -/// means you can now use any combination of units and fractions -/// to create static-gutters on a fluid grid, etc. -/// -/// **Spread** existed in the Susy2 API as a span option, -/// and was otherwise handled behind the scenes. -/// Now we're giving you full control over all spread issues. -/// You can find a more [detailed explanation of spread on the blog][spread]. -/// -/// [spread]: http://oddbird.net/2017/06/13/susy-spread/ -/// -/// You can access your global settings at any time -/// with the `susy-settings()` function, -/// or grab a single setting from the global scope -/// with `susy-get('columns')`, `susy-get('gutters')` etc. -/// -/// @group a-config -/// @link http://oddbird.net/2017/06/13/susy-spread/ -/// Article: Understanding Spread in Susy3 -/// -/// @see $susy -/// @see susy-settings -/// @see susy-get - - - -// Susy -// ---- -/// The grid is defined in a single map variable, -/// with four initial properties: -/// `columns`, `gutters`, `spread` and `container-spread`. -/// Anything you put in the root `$susy` variable map -/// will be treated as a global project default. -/// You can create similar configuration maps -/// under different variable names, -/// to override the defaults as-needed. -/// -/// @group a-config -/// @type Map -/// -/// @see $_susy-defaults -/// @see {function} susy-repeat -/// @link -/// https://codepen.io/mirisuzanne/pen/EgmJJp?editors=1100 -/// Spread examples on CodePen -/// -/// @prop {list} columns - -/// Columns are described by a list of numbers, -/// representing the relative width of each column. -/// The syntax is a simplified version of CSS native -/// `grid-template-columns`, -/// expecting a list of grid-column widths. -/// Unitless numbers create fractional fluid columns -/// (similar to the CSS-native `fr` unit), -/// while length values (united numbers) -/// are used to define static columns. -/// You can mix-and match units and fractions, -/// to create a mixed grid. -/// Susy will generate `calc()` values when necessary, -/// to make all your units work together. -/// -/// Use the `susy-repeat($count, $value)` function -/// to more easily repetative columns, -/// similar to the CSS-native `repeat()`. -/// -/// - `susy-repeat(8)`: -/// an 8-column, symmetrical, fluid grid. -///
Identical to `(1 1 1 1 1 1 1 1)`. -/// - `susy-repeat(6, 8em)`: -/// a 6-column, symmetrical, em-based grid. -///
Identical to `(8em 8em 8em 8em 8em 8em)`. -/// - `(300px susy-repeat(4) 300px)`: -/// a 6-column, asymmetrical, mixed fluid/static grid -/// using `calc()` output. -///
Identical to `(300px 1 1 1 1 300px)`. -/// -/// **NOTE** that `12` is no longer a valid 12-column grid definition, -/// and you must list all the columns individually -/// (or by using the `susy-repeat()` function). -/// -/// @prop {number} gutters - -/// Gutters are defined as a single width, -/// or fluid ratio, similar to the native-CSS -/// `grid-column-gap` syntax. -/// Similar to columns, -/// gutters can use any valid CSS length unit, -/// or unitless numbers to define a relative fraction. -/// -/// - `0.5`: -/// a fluid gutter, half the size of a single-fraction column. -/// - `1em`: -/// a static gutter, `1em` wide. -/// -/// Mix static gutters with fluid columns, or vice versa, -/// and Susy will generate the required `calc()` to make it work. -/// -/// @prop {string} spread [narrow] - -/// Spread of an element across adjacent gutters: -/// either `narrow` (none), `wide` (one), or `wider` (two) -/// -/// - Both spread settings default to `narrow`, -/// the most common use-case. -/// A `narrow` spread only has gutters *between* columns -/// (one less gutter than columns). -/// This is how all css-native grids work, -/// and most margin-based grid systems. -/// - A `wide` spread includes the same number of gutters as columns, -/// spanning across a single side-gutter. -/// This is how most padding-based grid systems often work, -/// and is also useful for pushing and pulling elements into place. -/// - The rare `wider` spread includes gutters -/// on both sides of the column-span -/// (one more gutters than columns). -/// -/// @prop {string} container-spread [narrow] - -/// Spread of a container around adjacent gutters: -/// either `narrow` (none), `wide` (one), or `wider` (two). -/// See `spread` property for details. -/// -/// @since 3.0.0-beta.1 - -/// `columns` setting no longer accepts numbers -/// (e.g. `12`) for symmetrical fluid grids, -/// or the initial `12 x 120px` syntax for -/// symmetrical fixed-unit grids. -/// Use `susy-repeat(12)` or `susy-repeat(12, 120px)` instead. -/// -/// @example scss - default values -/// // 4 symmetrical, fluid columns -/// // gutters are 1/4 the size of a column -/// // elements span 1 less gutter than columns -/// // containers span 1 less gutter as well -/// $susy: ( -/// 'columns': susy-repeat(4), -/// 'gutters': 0.25, -/// 'spread': 'narrow', -/// 'container-spread': 'narrow', -/// ); -/// -/// @example scss - inside-static gutters -/// // 6 symmetrical, fluid columns… -/// // gutters are static, triggering calc()… -/// // elements span equal columns & gutters… -/// // containers span equal columns & gutters… -/// $susy: ( -/// 'columns': susy-repeat(6), -/// 'gutters': 0.5em, -/// 'spread': 'wide', -/// 'container-spread': 'wide', -/// ); -$susy: () !default; - - - -// Susy Repeat -// ----------- -/// Similar to the `repeat(, )` function -/// that is available in native CSS Grid templates, -/// the `susy-repeat()` function helps generate repetative layouts -/// by repeating any value a given number of times. -/// Where Susy previously allowed `8` as a column definition -/// for 8 equal columns, you should now use `susy-repeat(8)`. -/// -/// @group a-config -/// -/// @param {integer} $count - -/// The number of repetitions, e.g. `12` for a 12-column grid. -/// @param {*} $value [1] - -/// The value to be repeated. -/// Technically any value can be repeated here, -/// but the function exists to repeat column-width descriptions: -/// e.g. the default `1` for single-fraction fluid columns, -/// `5em` for a static column, -/// or even `5em 120px` if you are alternating column widths. -/// -/// @return {list} - -/// List of repeated values -/// -/// @example scss -/// // 12 column grid, with 5em columns -/// $susy: ( -/// columns: susy-repeat(12, 5em), -/// ); -/// -/// @example scss -/// // asymmetrical 5-column grid -/// $susy: ( -/// columns: 20px susy-repeat(3, 100px) 20px, -/// ); -/// -/// /* result: #{susy-get('columns')} */ -@function susy-repeat( - $count, - $value: 1 -) { - $return: (); - - @for $i from 1 through $count { - $return: join($return, $value); - } - - @return $return; -} - - - -// Susy Defaults -// ------------- -/// Configuration map of Susy factory defaults. -/// Do not override this map directly – -/// use `$susy` for user and project setting overrides. -/// -/// @access private -/// @type Map -/// -/// @see $susy -/// -/// @prop {number | list} columns [susy-repeat(4)] -/// @prop {number} gutters [0.25] -/// @prop {string} spread ['narrow'] -/// @prop {string} container-spread ['narrow'] -$_susy-defaults: ( - 'columns': susy-repeat(4), - 'gutters': 0.25, - 'spread': 'narrow', - 'container-spread': 'narrow', -); - - - -// Susy Settings -// ------------- -/// Return a combined map of Susy settings, -/// based on the factory defaults (`$_susy-defaults`), -/// user-defined project configuration (`$susy`), -/// and any local overrides required – -/// such as a configuration map passed into a function. -/// -/// @group a-config -/// -/// @param {maps} $overrides… - -/// Optional map override of global configuration settings. -/// See `$susy` above for properties. -/// -/// @return {map} - -/// Combined map of Susy configuration settings, -/// in order of specificity: -/// any `$overrides...`, -/// then `$susy` project settings, -/// and finally the `$_susy-defaults` -/// -/// @example scss - global settings -/// @each $key, $value in susy-settings() { -/// /* #{$key}: #{$value} */ -/// } -/// -/// @example scss - local settings -/// $local: ('columns': 1 2 3 5 8); -/// -/// @each $key, $value in susy-settings($local) { -/// /* #{$key}: #{$value} */ -/// } -@function susy-settings( - $overrides... -) { - $settings: map-merge($_susy-defaults, $susy); - - @each $config in $overrides { - $settings: map-merge($settings, $config); - } - - @return $settings; -} - - - -// Susy Get -// -------- -/// Return the current global value of any Susy setting -/// -/// @group a-config -/// -/// @param {string} $key - -/// Setting to retrieve from the configuration. -/// -/// @return {*} - -/// Value mapped to `$key` in the configuration maps, -/// in order of specificity: -/// `$susy`, then `$_susy-defaults` -/// -/// @example scss - -/// /* columns: #{susy-get('columns')} */ -/// /* gutters: #{susy-get('gutters')} */ -@function susy-get( - $key -) { - $settings: susy-settings(); - - @if not map-has-key($settings, $key) { - @return _susy-error( - 'There is no Susy setting called `#{$key}`', - 'susy-get'); - } - - @return map-get($settings, $key); -} diff --git a/_sass/minimal-mistakes/vendor/susy/susy/_su-math.scss b/_sass/minimal-mistakes/vendor/susy/susy/_su-math.scss deleted file mode 100644 index 1e885284..00000000 --- a/_sass/minimal-mistakes/vendor/susy/susy/_su-math.scss +++ /dev/null @@ -1,441 +0,0 @@ -/// Grid Math Engine -/// ================ -/// The `su` functions give you direct access to the math layer, -/// without any syntax-sugar like shorthand parsing, and normalization. -/// If you prefer named arguments, and stripped-down syntax, -/// you can use these functions directly in your code – -/// replacing `span`, `gutter`, and `slice`. -/// -/// These functions are also useful -/// for building mixins or other extensions to Susy. -/// Apply the Susy syntax to new mixins and functions, -/// using our "Plugin Helpers", -/// or write your own syntax and pass the normalized results along -/// to `su` for compilation. -/// -/// @group su-math -/// -/// @see su-span -/// @see su-gutter -/// @see su-slice -/// @ignore _su-sum -/// @ignore _su-calc-span -/// @ignore _su-calc-sum -/// @ignore _su-needs-calc-output - - - -// Su Span -// ------- -/// Calculates and returns a CSS-ready span width, -/// based on normalized span and context data – -/// a low-level version of `susy-span`, -/// with all of the logic and none of the syntax sugar. -/// -/// - Grids defined with unitless numbers will return `%` values. -/// - Grids defined with comparable units -/// will return a value in the units provided. -/// - Grids defined with a mix of units, -/// or a combination of untiless numbers and unit-lengths, -/// will return a `calc()` string. -/// -/// @group su-math -/// @see susy-span -/// -/// @param {number | list} $span - -/// Number or list of grid columns to span -/// @param {list} $columns - -/// List of columns available -/// @param {number} $gutters - -/// Width of a gutter in column-comparable units -/// @param {0 | 1 | -1} $spread - -/// Number of gutters spanned, -/// relative to `span` count -/// @param {0 | 1 | -1} $container-spread [$spread] - -/// Number of gutters spanned, -/// relative to `columns` count -/// @param {integer} $location [1] - -/// Optional position of sub-span among full set of columns -/// -/// @return {length} - -/// Relative or static length of a span on the grid -@function su-span( - $span, - $columns, - $gutters, - $spread, - $container-spread: $spread, - $location: 1 -) { - $span: su-valid-span($span); - $columns: su-valid-columns($columns); - $gutters: su-valid-gutters($gutters); - $spread: su-valid-spread($spread); - - @if (type-of($span) == 'number') { - @if (not unitless($span)) { - @return $span; - } - - $location: su-valid-location($span, $location, $columns); - $span: su-slice($span, $columns, $location, $validate: false); - } - - @if _su-needs-calc-output($span, $columns, $gutters, $spread, not 'validate') { - @return _su-calc-span($span, $columns, $gutters, $spread, $container-spread, not 'validate'); - } - - $span-width: _su-sum($span, $gutters, $spread, $validate: false); - - @if unitless($span-width) { - $container-spread: su-valid-spread($container-spread); - $container: _su-sum($columns, $gutters, $container-spread, $validate: false); - @return percentage($span-width / $container); - } - - @return $span-width; -} - - - -// Su Gutter -// --------- -/// Calculates and returns a CSS-ready gutter width, -/// based on normalized grid data – -/// a low-level version of `susy-gutter`, -/// with all of the logic and none of the syntax sugar. -/// -/// - Grids defined with unitless numbers will return `%` values. -/// - Grids defined with comparable units -/// will return a value in the units provided. -/// - Grids defined with a mix of units, -/// or a combination of untiless numbers and unit-lengths, -/// will return a `calc()` string. -/// -/// @group su-math -/// @see susy-gutter -/// -/// @param {list} $columns - -/// List of columns in the grid -/// @param {number} $gutters - -/// Width of a gutter in column-comparable units -/// @param {0 | 1 | -1} $container-spread - -/// Number of gutters spanned, -/// relative to `columns` count -/// -/// @return {length} - -/// Relative or static length of one gutter in a grid -@function su-gutter( - $columns, - $gutters, - $container-spread -) { - @if (type-of($gutters) == 'number') { - @if ($gutters == 0) or (not unitless($gutters)) { - @return $gutters; - } - } - - @if _su-needs-calc-output($gutters, $columns, $gutters, -1, not 'validate') { - @return _su-calc-span($gutters, $columns, $gutters, -1, $container-spread, not 'validate'); - } - - $container: _su-sum($columns, $gutters, $container-spread); - @return percentage($gutters / $container); -} - - - -// Su Slice -// -------- -/// Returns a list of columns -/// based on a given span/location slice of the grid – -/// a low-level version of `susy-slice`, -/// with all of the logic and none of the syntax sugar. -/// -/// @group su-math -/// @see susy-slice -/// -/// @param {number} $span - -/// Number of grid columns to span -/// @param {list} $columns - -/// List of columns in the grid -/// @param {number} $location [1] - -/// Starting index of a span in the list of columns -/// @param {bool} $validate [true] - -/// Check that arguments are valid before proceeding -/// -/// @return {list} - -/// Subset list of grid columns, based on span and location -@function su-slice( - $span, - $columns, - $location: 1, - $validate: true -) { - @if $validate { - $columns: su-valid-columns($columns); - $location: su-valid-location($span, $location, $columns); - } - - $floor: floor($span); - $sub-columns: (); - - @for $i from $location to ($location + $floor) { - $sub-columns: append($sub-columns, nth($columns, $i)); - } - - @if $floor != $span { - $remainder: $span - $floor; - $column: $location + $floor; - $sub-columns: append($sub-columns, nth($columns, $column) * $remainder); - } - - @return $sub-columns; -} - - - -// Su Sum -// ------ -/// Get the total sum of column-units in a layout. -/// -/// @group su-math -/// @access private -/// -/// @param {list} $columns - -/// List of columns in the grid -/// @param {number} $gutters - -/// Width of a gutter in column-comparable units -/// @param {0 | 1 | -1} $spread - -/// Number of gutters spanned, -/// relative to `columns` count -/// @param {bool} $validate [true] - -/// Check that arguments are valid before proceeding -/// -/// @return {number} - -/// Total sum of column-units in a grid -@function _su-sum( - $columns, - $gutters, - $spread, - $validate: true -) { - @if $validate { - $columns: su-valid-span($columns); - $gutters: su-valid-gutters($gutters); - $spread: su-valid-spread($spread); - } - - // Calculate column-sum - $column-sum: 0; - @each $column in $columns { - $column-sum: $column-sum + $column; - } - - $gutter-sum: (ceil(length($columns)) + $spread) * $gutters; - $total: if(($gutter-sum > 0), $column-sum + $gutter-sum, $column-sum); - - @return $total; -} - - - -// Su Calc -// ------- -/// Return a usable span width as a `calc()` function, -/// in order to create mixed-unit grids. -/// -/// @group su-math -/// @access private -/// -/// @param {number | list} $span - -/// Pre-sliced list of grid columns to span -/// @param {list} $columns - -/// List of columns available -/// @param {number} $gutters - -/// Width of a gutter in column-comparable units -/// @param {0 | 1 | -1} $spread - -/// Number of gutters spanned, -/// relative to `span` count -/// @param {0 | 1 | -1} $container-spread [$spread] - -/// Number of gutters spanned, -/// relative to `columns` count -/// @param {bool} $validate [true] - -/// Check that arguments are valid before proceeding -/// -/// @return {length} - -/// Relative or static length of a span on the grid -@function _su-calc-span( - $span, - $columns, - $gutters, - $spread, - $container-spread: $spread, - $validate: true -) { - @if $validate { - $span: su-valid-span($span); - $columns: su-valid-columns($columns); - $gutters: su-valid-gutters($gutters); - $spread: su-valid-spread($spread); - $container-spread: su-valid-spread($container-spread); - } - - // Span and context - $span: _su-calc-sum($span, $gutters, $spread, not 'validate'); - $context: _su-calc-sum($columns, $gutters, $container-spread, not 'validate'); - - // Fixed and fluid - $fixed-span: map-get($span, 'fixed'); - $fluid-span: map-get($span, 'fluid'); - $fixed-context: map-get($context, 'fixed'); - $fluid-context: map-get($context, 'fluid'); - - $calc: '#{$fixed-span}'; - $fluid-calc: '(100% - #{$fixed-context})'; - - // Fluid-values - @if (not $fluid-span) { - $fluid-calc: null; - } @else if ($fluid-span != $fluid-context) { - $fluid-span: '* #{$fluid-span}'; - $fluid-context: if($fluid-context, '/ #{$fluid-context}', ''); - $fluid-calc: '(#{$fluid-calc $fluid-context $fluid-span})'; - } - - @if $fluid-calc { - $calc: if(($calc != ''), '#{$calc} + ', ''); - $calc: '#{$calc + $fluid-calc}'; - } - - @return calc(#{unquote($calc)}); -} - - - -// Su Calc-Sum -// ----------- -/// Get the total sum of fixed and fluid column-units -/// for creating a mixed-unit layout with `calc()` values. -/// -/// @group su-math -/// @access private -/// -/// @param {list} $columns - -/// List of columns available -/// @param {number} $gutters - -/// Width of a gutter in column-comparable units -/// @param {0 | 1 | -1} $spread - -/// Number of gutters spanned, -/// relative to `span` count -/// @param {bool} $validate [true] - -/// Check that arguments are valid before proceeding -/// -/// @return {map} - -/// Map with `fixed` and `fluid` keys -/// containing the proper math as strings -@function _su-calc-sum( - $columns, - $gutters, - $spread, - $validate: true -) { - @if $validate { - $columns: su-valid-span($columns); - $gutters: su-valid-gutters($gutters); - $spread: su-valid-spread($spread); - } - - $fluid: 0; - $fixed: (); - $calc: null; - - // Gutters - $gutters: $gutters * (length($columns) + $spread); - - // Columns - @each $col in append($columns, $gutters) { - @if unitless($col) { - $fluid: $fluid + $col; - } @else { - $fixed: _su-map-add-units($fixed, $col); - } - } - - // Compile Fixed Units - @each $unit, $total in $fixed { - @if ($total != (0 * $total)) { - $calc: if($calc, '#{$calc} + #{$total}', '#{$total}'); - } - } - - // Calc null or string - @if $calc { - $calc: if(str-index($calc, '+'), '(#{$calc})', '#{$calc}'); - } - - // Fluid 0 => null - $fluid: if(($fluid == 0), null, $fluid); - - - // Return map - $return: ( - 'fixed': $calc, - 'fluid': $fluid, - ); - - @return $return; -} - - - -// Needs Calc -// ---------- -/// Check if `calc()` will be needed in defining a span, -/// if the necessary units in a grid are not comparable. -/// -/// @group su-math -/// @access private -/// -/// @param {list} $span - -/// Slice of columns to span -/// @param {list} $columns - -/// List of available columns in the grid -/// @param {number} $gutters - -/// Width of a gutter -/// @param {0 | 1 | -1} $spread - -/// Number of gutters spanned, -/// relative to `span` count -/// @param {bool} $validate [true] - -/// Check that arguments are valid before proceeding -/// -/// @return {bool} - -/// `True` when units do not match, and `calc()` will be required -@function _su-needs-calc-output( - $span, - $columns, - $gutters, - $spread, - $validate: true -) { - @if $validate { - $span: su-valid-span($span); - $columns: su-valid-columns($columns); - $gutters: su-valid-gutters($gutters); - } - - $has-gutter: if((length($span) > 1) or ($spread >= 0), true, false); - $check: if($has-gutter, append($span, $gutters), $span); - $safe-span: _su-is-comparable($check...); - - @if ($safe-span == 'static') { - @return false; - } @else if (not $safe-span) { - @return true; - } - - $safe-fluid: _su-is-comparable($gutters, $columns...); - - @return not $safe-fluid; -} diff --git a/_sass/minimal-mistakes/vendor/susy/susy/_su-validate.scss b/_sass/minimal-mistakes/vendor/susy/susy/_su-validate.scss deleted file mode 100644 index 5befad30..00000000 --- a/_sass/minimal-mistakes/vendor/susy/susy/_su-validate.scss +++ /dev/null @@ -1,213 +0,0 @@ -/// Validation -/// ========== -/// Each argument to Su has a single canonical syntax. -/// These validation functions check to ensure -/// that each argument is valid, -/// in order to provide useful errors -/// before attempting to calculate the results/ -/// -/// @group x-validation -/// -/// @see su-valid-columns -/// @see su-valid-gutters -/// @see su-valid-spread -/// @see su-valid-location - - - -// Valid Span -// ---------- -/// Check that the `span` argument -/// is a number, length, or column-list -/// -/// @group x-validation -/// -/// @param {number | list} $span - -/// Number of columns, or length of span -/// -/// @return {number | list} - -/// Validated `$span` number, length, or columns list -/// -/// @throw -/// when span value is not a number, or valid column list -@function su-valid-span( - $span -) { - $type: type-of($span); - @if ($type == 'number') { - @return $span; - } @else if ($type == 'list') and su-valid-columns($span, 'silent-failure') { - @return $span; - } - - $actual: '[#{type-of($span)}] `#{inspect($span)}`'; - @return _susy-error( - '#{$actual} is not a valid number, length, or column-list for $span.', - 'su-valid-span'); -} - - - -// Valid Columns -// ------------- -/// Check that the `columns` argument is a valid -/// list of column-lengths -/// -/// @group x-validation -/// -/// @param {list} $columns - -/// List of column-lengths -/// @param {bool} $silent-failure [true] - -/// Set false to return null on failure -/// -/// @return {list} - -/// Validated `$columns` list -/// -/// @throw -/// when column value is not a valid list of numbers -@function su-valid-columns( - $columns, - $silent-failure: false -) { - @if (type-of($columns) == 'list') { - $fail: false; - - @each $col in $columns { - @if (type-of($col) != 'number') { - $fail: true; - } - } - - @if not $fail { - @return $columns; - } - } - - // Silent Failure - @if $silent-failure { - @return null; - } - - // Error Message - $actual: '[#{type-of($columns)}] `#{inspect($columns)}`'; - - @return _susy-error( - '#{$actual} is not a valid list of numbers for $columns.', - 'su-valid-columns'); -} - - - -// Valid Gutters -// ------------- -/// Check that the `gutters` argument is a valid number -/// -/// @group x-validation -/// -/// @param {number} $gutters - -/// Width of a gutter -/// -/// @return {number} - -/// Validated `$gutters` number -/// -/// @throw -/// when gutter value is not a number -@function su-valid-gutters( - $gutters -) { - $type: type-of($gutters); - - @if ($type == 'number') { - @return $gutters; - } - - $actual: '[#{$type}] `#{inspect($gutters)}`'; - @return _susy-error( - '#{$actual} is not a number or length for $gutters.', - 'su-valid-gutters'); -} - - - -// Valid Spread -// ------------ -/// Check that the `spread` argument is a valid -/// intiger between `-1` and `1` -/// -/// @group x-validation -/// -/// @param {0 | 1 | -1} $spread - -/// Number of gutters to include in a span, -/// relative to the number columns -/// -/// @return {0 | 1 | -1} - -/// Validated `$spread` number -/// -/// @throw -/// when spread value is not a valid spread -@function su-valid-spread( - $spread -) { - @if index(0 1 -1, $spread) { - @return $spread; - } - - $actual: '[#{type-of($spread)}] `#{inspect($spread)}`'; - @return _susy-error( - '#{$actual} is not a normalized [0 | 1 | -1] value for `$spread`.', - 'su-valid-spread'); -} - - - -// Valid Location -// -------------- -/// Check that the `location` argument is a valid number, -/// within the scope of available columns -/// -/// @group x-validation -/// -/// @param {number} $span - -/// Number of grid-columns to be spanned -/// @param {integer | string} $location - -/// Starting (1-indexed) column-position of that span -/// @param {list} $columns - -/// List of available columns in the grid -/// -/// @return {integer} - -/// Validated `$location` intiger -/// -/// @throw -/// when location value is not a valid index, -/// given the context and span. -@function su-valid-location( - $span, - $location, - $columns -) { - $count: length($columns); - - @if $location { - @if (type-of($location) != 'number') or (not unitless($location)) { - $actual: '[#{type-of($location)}] `#{$location}`'; - @return _susy-error( - '#{$actual} is not a unitless number for $location.', - 'su-valid-location'); - } @else if (round($location) != $location) { - @return _susy-error( - 'Location (`#{$location}`) must be a 1-indexed intiger position.', - 'su-valid-location'); - } @else if ($location > $count) or ($location < 1) { - @return _susy-error( - 'Position `#{$location}` does not exist in grid `#{$columns}`.', - 'su-valid-location'); - } @else if ($location + $span - 1 > $count) { - $details: 'grid `#{$columns}` for span `#{$span}` at `#{$location}`'; - @return _susy-error( - 'There are not enough columns in #{$details}.', - 'su-valid-location'); - } - } - - @return $location; -} diff --git a/_sass/minimal-mistakes/vendor/susy/susy/_syntax-helpers.scss b/_sass/minimal-mistakes/vendor/susy/susy/_syntax-helpers.scss deleted file mode 100644 index 929a5353..00000000 --- a/_sass/minimal-mistakes/vendor/susy/susy/_syntax-helpers.scss +++ /dev/null @@ -1,191 +0,0 @@ -/// Syntax Utilities for Extending Susy -/// =================================== -/// There are many steps involved -/// when translating between the Susy syntax layer, -/// and the Su core math. -/// That entire process can be condensed with these two functions. -/// For anyone that wants to access the full power of Susy, -/// and build their own plugins, functions, or mixins – -/// this is the primary API for compiling user input, -/// and accessing the core math. -/// -/// This is the same technique we use internally, -/// to keep our API layer simple and weight-3. -/// Every function accepts two arguments, -/// a "shorthand" description of the span or context, -/// and an optional settings-map to override global defaults. -/// -/// - Use `susy-compile()` to parse, merge, and normalize -/// all the user settings into a single map. -/// - Then use `su-call()` to call one of the core math functions, -/// with whatever data is needed for that function. -/// -/// @group plugin-utils -/// @see susy-compile -/// @see su-call -/// -/// @example scss - Susy API `gutter` function -/// @function susy-gutter( -/// $context: susy-get('columns'), -/// $config: () -/// ) { -/// // compile and normalize all user arguments and global settings -/// $context: susy-compile($context, $config, 'context-only'); -/// // call `su-gutter` with the appropriate data -/// @return su-call('su-gutter', $context); -/// } -/// -/// @example scss - Sample `span` mixin for floated grids -/// @mixin span( -/// $span, -/// $config: () -/// ) { -/// $context: susy-compile($span, $config); -/// width: su-call('su-span', $context); -/// -/// @if index($span, 'last') { -/// float: right; -/// } @else { -/// float: left; -/// margin-right: su-call('su-gutter', $context); -/// } -/// } - - - -// Compile -// ------- -/// Susy's syntax layer has various moving parts, -/// with syntax-parsing for the grid/span shorthand, -/// and normalization for each of the resulting values. -/// The compile function rolls this all together -/// in a single call – -/// for quick access from our internal API functions, -/// or any additional functions and mixins you add to your project. -/// Pass user input and configuration maps to the compiler, -/// and it will hand back a map of values ready for Su. -/// Combine this with the `su-call` function -/// to quickly parse, normalize, and process grid calculations. -/// -/// @group plugin-utils -/// @see su-call -/// -/// @param {list | map} $shorthand - -/// Shorthand expression to define the width of the span, -/// optionally containing: -/// - a count, length, or column-list span; -/// - `at $n`, `first`, or `last` location on asymmetrical grids; -/// - `narrow`, `wide`, or `wider` for optionally spreading -/// across adjacent gutters; -/// - `of $n ` for available grid columns -/// and spread of the container -/// (span counts like `of 6` are only valid -/// in the context of symmetrical grids); -/// - and `set-gutters $n` to override global gutter settings -/// @param {map} $config [null] - -/// Optional map of Susy grid configuration settings -/// @param {bool} $context-only [false] - -/// Allow the parser to ignore span and span-spread values, -/// only parsing context and container-spread -/// -/// @return {map} - -/// Parsed and normalized map of settings, -/// based on global and local configuration, -/// alongwith shorthad adjustments. -/// -/// @example scss - -/// $user-input: 3 wide of susy-repeat(6, 120px) set-gutters 10px; -/// $grid-data: susy-compile($user-input, $susy); -/// -/// @each $key, $value in $grid-data { -/// /* #{$key}: #{$value}, */ -/// } -@function susy-compile( - $short, - $config: null, - $context-only: false -) { - // Get and normalize config - $config: if($config, susy-settings($config), susy-settings()); - $normal-config: susy-normalize($config); - - // Parse and normalize shorthand - @if (type-of($short) != 'map') and (length($short) > 0) { - $short: susy-parse($short, $context-only); - } - - $normal-short: susy-normalize($short, $normal-config); - - // Merge and return - @return map-merge($normal-config, $normal-short); -} - - - -// Call -// ---- -/// The Susy parsing and normalization process -/// results in a map of configuration settings, -/// much like the global `$susy` settings map. -/// In order to pass that information along to Su math functions, -/// the proper values have to be picked out, -/// and converted to arguments. -/// -/// The `su-call` function streamlines that process, -/// weeding out the unnecessary data, -/// and passing the rest along to Su in the proper format. -/// Combine this with `susy-compile` to quickly parse, -/// normalize, and process grid calculations. -/// -/// @group plugin-utils -/// -/// @require su-span -/// @require su-gutter -/// @require su-slice -/// @see susy-compile -/// -/// @param {'su-span' | 'su-gutter' | 'su-slice'} $name - -/// Name of the Su math function to call. -/// @param {map} $config - -/// Parsed and normalized map of Susy configuration settings -/// to use for math-function arguments. -/// -/// @return {*} - -/// Results of the function being called. -/// -/// @example scss - -/// $user-input: 3 wide of susy-repeat(6, 120px) set-gutters 10px; -/// $grid-data: susy-compile($user-input, $susy); -/// -/// .su-span { -/// width: su-call('su-span', $grid-data); -/// } -@function su-call( - $name, - $config -) { - $grid-function-args: ( - 'su-span': ('span', 'columns', 'gutters', 'spread', 'container-spread', 'location'), - 'su-gutter': ('columns', 'gutters', 'container-spread'), - 'su-slice': ('span', 'columns', 'location'), - ); - - $args: map-get($grid-function-args, $name); - - @if not $args { - $options: 'Try one of these: #{map-keys($grid-function-args)}'; - @return _susy-error( - '#{$name} is not a public Su function. #{$options}', - 'su-call'); - } - - $call: if(function-exists('get-function'), get-function($name), $name); - $output: (); - - @each $arg in $args { - $value: map-get($config, $arg); - $output: if($value, map-merge($output, ($arg: $value)), $output); - } - - @return call($call, $output...); -} diff --git a/_sass/minimal-mistakes/vendor/susy/susy/_unprefix.scss b/_sass/minimal-mistakes/vendor/susy/susy/_unprefix.scss deleted file mode 100644 index 2cfd1b81..00000000 --- a/_sass/minimal-mistakes/vendor/susy/susy/_unprefix.scss +++ /dev/null @@ -1,56 +0,0 @@ -// Unprefix Susy -// ============= - - -// Span -// ---- -/// Un-prefixed alias for `susy-span` -/// (available by default) -/// -/// @group api -/// @alias susy-span -/// -/// @param {list} $span -/// @param {map} $config [()] -@function span( - $span, - $config: () -) { - @return susy-span($span, $config); -} - - -// Gutter -// ------ -/// Un-prefixed alias for `susy-gutter` -/// (available by default) -/// -/// @group api -/// @alias susy-gutter -/// -/// @param {integer | list} $context [null] - -/// @param {map} $config [()] -@function gutter( - $context: susy-get('columns'), - $config: () -) { - @return susy-gutter($context, $config); -} - - -// Slice -// ----- -/// Un-prefixed alias for `susy-slice` -/// (available by default) -/// -/// @group api -/// @alias susy-slice -/// -/// @param {list} $span -/// @param {map} $config [()] -@function slice( - $span, - $config: () -) { - @return susy-slice($span, $config); -} diff --git a/_sass/minimal-mistakes/vendor/susy/susy/_utilities.scss b/_sass/minimal-mistakes/vendor/susy/susy/_utilities.scss deleted file mode 100644 index 3c62de2d..00000000 --- a/_sass/minimal-mistakes/vendor/susy/susy/_utilities.scss +++ /dev/null @@ -1,167 +0,0 @@ -// Sass Utilities -// ============== -// - Susy Error Output Override [variable] -// - Susy Error [function] - - - -// Susy Error Output Override -// -------------------------- -/// Turn off error output for testing -/// @group x-utility -/// @access private -$_susy-error-output-override: false !default; - - - -// Susy Error -// ---------- -/// Optionally return error messages without failing, -/// as a way to test error cases -/// -/// @group x-utility -/// @access private -/// -/// @param {string} $message - -/// A useful error message, explaining the problem -/// @param {string} $source - -/// The original source of the error for debugging -/// @param {bool} $override [$_susy-error-output-override] - -/// Optionally return the error rather than failing -/// @return {string} - -/// Combined error with source and message -/// @throws When `$override == true` -@function _susy-error( - $message, - $source, - $override: $_susy-error-output-override -) { - @if $override { - @return 'ERROR [#{$source}] #{$message}'; - } - - @error '[#{$source}] #{$message}'; -} - - -// Su Is Comparable -// ---------------- -/// Check that the units in a grid are comparable -/// -/// @group x-validation -/// @access private -/// -/// @param {numbers} $lengths… - -/// Arglist of all the number values to compare -/// (columns, gutters, span, etc) -/// -/// @return {'fluid' | 'static' | false} - -/// The type of span (fluid or static) when units match, -/// or `false` for mismatched units -@function _su-is-comparable( - $lengths... -) { - $first: nth($lengths, 1); - - @if (length($lengths) == 1) { - @return if(unitless($first), 'fluid', 'static'); - } - - @for $i from 2 through length($lengths) { - $comp: nth($lengths, $i); - - $fail: not comparable($first, $comp); - $fail: $fail or (unitless($first) and not unitless($comp)); - $fail: $fail or (unitless($comp) and not unitless($first)); - - @if $fail { - @return false; - } - } - - @return if(unitless($first), 'fluid', 'static'); -} - - -// Su Map Add Units -// ---------------- -/// The calc features use a map of units and values -/// to compile the proper algorythm. -/// This function adds a new value to any comparable existing unit/value, -/// or adds a new unit/value pair to the map -/// -/// @group x-utility -/// @access private -/// -/// @param {map} $map - -/// A map of unit/value pairs, e.g. ('px': 120px) -/// @param {length} $value - -/// A new length to be added to the map -/// @return {map} - -/// The updated map, with new value added -/// -/// @example scss - -/// $map: (0px: 120px); -/// $map: _su-map-add-units($map, 1in); // add a comparable unit -/// $map: _su-map-add-units($map, 3vw); // add a new unit -/// -/// @each $units, $value in $map { -/// /* #{$units}: #{$value} */ -/// } -@function _su-map-add-units( - $map, - $value -) { - $unit: $value * 0; - $has: map-get($map, $unit) or 0; - - @if ($has == 0) { - @each $try, $could in $map { - $match: comparable($try, $value); - $unit: if($match, $try, $unit); - $has: if($match, $could, $has); - } - } - - @return map-merge($map, ($unit: $has + $value)); -} - - -// Susy Flatten -// ------------ -/// Flatten a multidimensional list -/// -/// @group x-utility -/// @access private -/// -/// @param {list} $list - -/// The list to be flattened -/// @return {list} - -/// The flattened list -/// -/// @example scss - -/// $list: 120px (30em 30em) 120px; -/// /* #{_susy-flatten($list)} */ -@function _susy-flatten( - $list -) { - $flat: (); - - // Don't iterate over maps - @if (type-of($list) == 'map') { - @return $list; - } - - // Iterate over lists (or single items) - @each $item in $list { - @if (type-of($item) == 'list') { - $item: _susy-flatten($item); - $flat: join($flat, $item); - } @else { - $flat: append($flat, $item); - } - } - - // Return flattened list - @return $flat; -} diff --git a/_tutorials/0-hatch-python-packaging.md b/_tutorials/0-hatch-python-packaging.md deleted file mode 100644 index 9ce089df..00000000 --- a/_tutorials/0-hatch-python-packaging.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: "0. Get to know Hatch" -excerpt: " - Hatch is an end-to-end Python packaging and workflow tool. In this tutorial, you will install and learn how to configure Hatch for Python packaging." -learning_objectives: - - "How to install Hatch using either pipx or pip" - - "How to configure hatch using the hatch .toml file" -link: https://www.pyopensci.org/python-package-guide/tutorials/get-to-know-hatch.html -btn_label: View Tutorial -btn_class: btn--success btn--large ---- diff --git a/_tutorials/1-what-is-package.md b/_tutorials/1-what-is-package.md deleted file mode 100644 index a415a31e..00000000 --- a/_tutorials/1-what-is-package.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -title: "1. What is a Python package" -excerpt: " - Learn about what a Python package is and the basic components that make up a Python package." -learning_objectives: - - "Understand what a Python package is" - - "Be able to list the 5 core components of a Python package" - - "Be able to explain the difference between generalizable code and code that supports a specific scientific application" -link: https://www.pyopensci.org/python-package-guide/tutorials/intro.html -btn_label: View Tutorial -btn_class: btn--success btn--large ---- diff --git a/_tutorials/10-develop-python-package-hatch.md b/_tutorials/10-develop-python-package-hatch.md deleted file mode 100644 index 8c5b7eb1..00000000 --- a/_tutorials/10-develop-python-package-hatch.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -title: "Use Hatch environments in your package" -excerpt: " - After you create a package with the pyOpenSci template, use Hatch to run tests, build distributions, check metadata, and work with your development environments day to day." -learning_objectives: - - "Run tests and builds through Hatch" - - "Understand how Hatch reads pyproject.toml for environments" - - "Optional: speed up environments by using the UV installer with Hatch" -link: https://www.pyopensci.org/python-package-guide/tutorials/develop-python-package-hatch.html -btn_label: View Tutorial -btn_class: btn--success btn--large ---- diff --git a/_tutorials/11-setup-py-to-pyproject-toml.md b/_tutorials/11-setup-py-to-pyproject-toml.md deleted file mode 100644 index 6218791e..00000000 --- a/_tutorials/11-setup-py-to-pyproject-toml.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: "Migrate setup.py to pyproject.toml with Hatch" -excerpt: " - If your project already uses setup.py, Hatch can help you move toward a modern pyproject.toml while keeping metadata and build settings aligned with current packaging standards." -learning_objectives: - - "Know when this migration path applies (existing setup.py projects)" - - "Use Hatch to transition configuration into pyproject.toml" -link: https://www.pyopensci.org/python-package-guide/tutorials/setup-py-to-pyproject-toml.html -btn_label: View Tutorial -btn_class: btn--success btn--large ---- diff --git a/_tutorials/2-make-code-installable.md b/_tutorials/2-make-code-installable.md deleted file mode 100644 index 15172c00..00000000 --- a/_tutorials/2-make-code-installable.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -title: "2. Make your Python code installable" -excerpt: " - Learn how to create the most basic version of a Python package which can then be installed into a Python environment." -learning_objectives: - - "Understand what a Python package is" -link: https://www.pyopensci.org/python-package-guide/tutorials/create-python-package.html -btn_label: View Tutorial -btn_class: btn--success btn--large ---- diff --git a/_tutorials/3-publish-to-pypi.md b/_tutorials/3-publish-to-pypi.md deleted file mode 100644 index 7ccf7084..00000000 --- a/_tutorials/3-publish-to-pypi.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -title: "3. Publish your Python package to PyPI" -excerpt: " - Learn how to publish your Python package to test.PyPI.org and to PyPI.org." -learning_objectives: - - "Build your package’s distributions" - - "Setup an account on test PyPI (the process is similar for the real PyPI)" - - "Publish your package to test PyPI" -link: https://www.pyopensci.org/python-package-guide/tutorials/publish-pypi.html -btn_label: View Tutorial -btn_class: btn--success btn--large ---- diff --git a/_tutorials/4-publish-conda-forge.md b/_tutorials/4-publish-conda-forge.md deleted file mode 100644 index b7f1d0fa..00000000 --- a/_tutorials/4-publish-conda-forge.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: "4. Publish to conda forge using grayskull" -excerpt: " - Learn how to publish your Python package to the conda-forge channel of conda using the grayskull Python package." -learning_objectives: - - "Create a conda-forge recipe using grayskull" - - "Submit that package for review and publication on conda-forge." -link: https://www.pyopensci.org/python-package-guide/tutorials/publish-conda-forge.html -btn_label: View Tutorial -btn_class: btn--success btn--large ---- diff --git a/_tutorials/5-add-readme.md b/_tutorials/5-add-readme.md deleted file mode 100644 index 32ae2cbf..00000000 --- a/_tutorials/5-add-readme.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: "5. Add a README file to your Python package" -excerpt: " - A README file is often the landing page that a user will use to understand your package. Learn about how to create a useful README file for your Python package." -learning_objectives: - - "How to add a README.md file to your package." - - "What the core elements of a README.md file are." -link: https://www.pyopensci.org/python-package-guide/tutorials/add-readme.html -btn_label: View Tutorial -btn_class: btn--success btn--large ---- diff --git a/_tutorials/6-add-license-code-of-conduct.md b/_tutorials/6-add-license-code-of-conduct.md deleted file mode 100644 index d11fd3c2..00000000 --- a/_tutorials/6-add-license-code-of-conduct.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -title: "6 . Add a LICENSE and CODE_OF_CONDUCT" -excerpt: " - License and code of conduct files are important to add to your Python package as they provide instructions for both how users can use your package and also how the community of users should interact with you as a maintainer." -learning_objectives: - - "How to select and add a LICENSE file to your package repository with a focus on the GitHub interface." - - "How to add a CODE_OF_CONDUCT file to your package repository." - - "How you can use the Contributors Covenant website to add generic language as a starting place for your CODE_OF_CONDUCT." -link: https://www.pyopensci.org/python-package-guide/tutorials/add-license-coc.html -btn_label: View Tutorial -btn_class: btn--success btn--large ---- diff --git a/_tutorials/7-add-metadata-pyproject-toml.md b/_tutorials/7-add-metadata-pyproject-toml.md deleted file mode 100644 index 3591aac8..00000000 --- a/_tutorials/7-add-metadata-pyproject-toml.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: "7. Add metadata with a pyproject.toml file" -excerpt: " - To enhance the visibility of your package on PyPI and provide more information about its compatibility with Python versions, project development status, and project maintainers, you should add additional metadata to your pyproject.toml file. This lesson will guide you through the process." -learning_objectives: - - "More about the pyproject.toml file and how it’s used to store different types of metadata about your package" - - "How to declare information (metadata) about your project to help users find and understand it on PyPI." -link: https://www.pyopensci.org/python-package-guide/tutorials/pyproject-toml.html -btn_label: View Tutorial -btn_class: btn--success btn--large ---- diff --git a/_tutorials/8-command-line-reference.md b/_tutorials/8-command-line-reference.md deleted file mode 100644 index bdb68f2a..00000000 --- a/_tutorials/8-command-line-reference.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: "Command line reference for packaging" -excerpt: " - Quick-reference tables for shell commands used across the packaging tutorials, from installing Hatch and pipx through building, testing, and publishing." -learning_objectives: - - "Find the right command for each packaging step in one place" - - "See environment setup commands for Windows (macOS and Linux notes coming later)" -link: https://www.pyopensci.org/python-package-guide/tutorials/command-line-reference.html -btn_label: View Tutorial -btn_class: btn--success btn--large ---- diff --git a/_tutorials/9-trusted-publishing.md b/_tutorials/9-trusted-publishing.md deleted file mode 100644 index 4e180f54..00000000 --- a/_tutorials/9-trusted-publishing.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -title: "Trusted publishing with GitHub Actions" -excerpt: " - Automate building and publishing to PyPI from GitHub Actions and configure PyPI Trusted Publishing so you can ship releases without long-lived API tokens on the repository." -learning_objectives: - - "Configure a release workflow on GitHub Actions" - - "Set up PyPI Trusted Publishing for your project" - - "Pin action versions securely in your workflow file" -link: https://www.pyopensci.org/python-package-guide/tutorials/trusted-publishing.html -btn_label: View Tutorial -btn_class: btn--success btn--large ---- diff --git a/assets/css/main.scss b/assets/css/main.scss deleted file mode 100644 index 1f0ead31..00000000 --- a/assets/css/main.scss +++ /dev/null @@ -1,9 +0,0 @@ ---- -# Only the main Sass file needs front matter (the dashes are enough) -search: false ---- - -@charset "utf-8"; - -@import "minimal-mistakes/skins/{{ site.minimal_mistakes_skin | default: 'default' }}"; // skin -@import "minimal-mistakes"; // main partials diff --git a/assets/fonts/Itim-Regular.woff2 b/assets/fonts/Itim-Regular.woff2 deleted file mode 100644 index bdde555b..00000000 Binary files a/assets/fonts/Itim-Regular.woff2 and /dev/null differ diff --git a/assets/fonts/NunitoSans-Italic-VariableFont.woff2 b/assets/fonts/NunitoSans-Italic-VariableFont.woff2 deleted file mode 100644 index 425c6213..00000000 Binary files a/assets/fonts/NunitoSans-Italic-VariableFont.woff2 and /dev/null differ diff --git a/assets/fonts/NunitoSans-VariableFont.woff2 b/assets/fonts/NunitoSans-VariableFont.woff2 deleted file mode 100644 index e7abd657..00000000 Binary files a/assets/fonts/NunitoSans-VariableFont.woff2 and /dev/null differ diff --git a/assets/fonts/Poppins-Bold.woff2 b/assets/fonts/Poppins-Bold.woff2 deleted file mode 100644 index 13e0e28b..00000000 Binary files a/assets/fonts/Poppins-Bold.woff2 and /dev/null differ diff --git a/assets/fonts/Poppins-Italic.woff2 b/assets/fonts/Poppins-Italic.woff2 deleted file mode 100644 index 0f4fa9bb..00000000 Binary files a/assets/fonts/Poppins-Italic.woff2 and /dev/null differ diff --git a/assets/fonts/Poppins-Light.woff2 b/assets/fonts/Poppins-Light.woff2 deleted file mode 100644 index 7eba2c4f..00000000 Binary files a/assets/fonts/Poppins-Light.woff2 and /dev/null differ diff --git a/assets/fonts/Poppins-Medium.woff2 b/assets/fonts/Poppins-Medium.woff2 deleted file mode 100644 index 406acb66..00000000 Binary files a/assets/fonts/Poppins-Medium.woff2 and /dev/null differ diff --git a/assets/fonts/Poppins-Regular.woff2 b/assets/fonts/Poppins-Regular.woff2 deleted file mode 100644 index 964d6d2f..00000000 Binary files a/assets/fonts/Poppins-Regular.woff2 and /dev/null differ diff --git a/assets/fonts/Poppins-SemiBold.woff2 b/assets/fonts/Poppins-SemiBold.woff2 deleted file mode 100644 index 9e4d0c0e..00000000 Binary files a/assets/fonts/Poppins-SemiBold.woff2 and /dev/null differ diff --git a/assets/js/_main.js b/assets/js/_main.js deleted file mode 100644 index 06c7092f..00000000 --- a/assets/js/_main.js +++ /dev/null @@ -1,136 +0,0 @@ -/* ========================================================================== - jQuery plugin settings and other scripts - ========================================================================== */ - -$(function() { - // FitVids init - $("#main").fitVids(); - - // Sticky sidebar - var stickySideBar = function() { - var show = - $(".author__urls-wrapper").find("button").length === 0 - ? $(window).width() > 1024 // width should match $large Sass variable - : !$(".author__urls-wrapper").find("button").is(":visible"); - if (show) { - // fix - $(".sidebar").addClass("sticky"); - } else { - // unfix - $(".sidebar").removeClass("sticky"); - } - }; - - stickySideBar(); - - $(window).resize(function() { - stickySideBar(); - }); - - // Follow menu drop down - $(".author__urls-wrapper").find("button").on("click", function() { - $(".author__urls").toggleClass("is--visible"); - $(".author__urls-wrapper").find("button").toggleClass("open"); - }); - - // Close search screen with Esc key - $(document).keyup(function(e) { - if (e.keyCode === 27) { - if ($(".initial-content").hasClass("is--hidden")) { - $(".search-content").toggleClass("is--visible"); - $(".initial-content").toggleClass("is--hidden"); - } - } - }); - - // Search toggle - $(".search__toggle").on("click", function() { - $(".search-content").toggleClass("is--visible"); - $(".initial-content").toggleClass("is--hidden"); - // set focus on input - setTimeout(function() { - $(".search-content").find("input").focus(); - }, 400); - }); - - // Smooth scrolling - var scroll = new SmoothScroll('a[href*="#"]', { - offset: 20, - speed: 400, - speedAsDuration: true, - durationMax: 500 - }); - - // Gumshoe scroll spy init - if($("nav.toc").length > 0) { - var spy = new Gumshoe("nav.toc a", { - // Active classes - navClass: "active", // applied to the nav list item - contentClass: "active", // applied to the content - - // Nested navigation - nested: false, // if true, add classes to parents of active link - nestedClass: "active", // applied to the parent items - - // Offset & reflow - offset: 20, // how far from the top of the page to activate a content area - reflow: true, // if true, listen for reflows - - // Event support - events: true // if true, emit custom events - }); - } - - // add lightbox class to all image links - $( - "a[href$='.jpg'],a[href$='.jpeg'],a[href$='.JPG'],a[href$='.png'],a[href$='.gif'],a[href$='.webp']" - ).has("> img").addClass("image-popup"); - - // Magnific-Popup options - $(".image-popup").magnificPopup({ - // disableOn: function() { - // if( $(window).width() < 500 ) { - // return false; - // } - // return true; - // }, - type: "image", - tLoading: "Loading image #%curr%...", - gallery: { - enabled: true, - navigateByImgClick: true, - preload: [0, 1] // Will preload 0 - before current, and 1 after the current image - }, - image: { - tError: 'Image #%curr% could not be loaded.' - }, - removalDelay: 500, // Delay in milliseconds before popup is removed - // Class that is added to body when popup is open. - // make it unique to apply your CSS animations just to this exact popup - mainClass: "mfp-zoom-in", - callbacks: { - beforeOpen: function() { - // just a hack that adds mfp-anim class to markup - this.st.image.markup = this.st.image.markup.replace( - "mfp-figure", - "mfp-figure mfp-with-anim" - ); - } - }, - closeOnContentClick: true, - midClick: true // allow opening popup on middle mouse click. Always set it to true if you don't provide alternative source. - }); - - // Add anchors for headings - $('.page__content').find('h1, h2, h3, h4, h5, h6').each(function() { - var id = $(this).attr('id'); - if (id) { - var anchor = document.createElement("a"); - anchor.className = 'header-link'; - anchor.href = '#' + id; - anchor.innerHTML = 'Permalink'; - anchor.title = "Permalink"; - $(this).append(anchor); - } - }); -}); diff --git a/assets/js/dropdown.js b/assets/js/dropdown.js deleted file mode 100644 index 8412b6f0..00000000 --- a/assets/js/dropdown.js +++ /dev/null @@ -1,93 +0,0 @@ -// jQuery to manage drop downs and accessibility -$(document).ready(function() { - $(".hamburger__btn-toggle").click(function(e) { - $(this).toggleClass("close"); - // Toggle expanded to try on click - $(this).attr('aria-expanded', function(i, attr) { - return attr == 'true' ? 'false' : 'true' - }); - - e.preventDefault(); - $(".nav__links").toggleClass("vertical"); - }); - - $(".dropdown").click(function(e) { - // Close when user clicks nav - $(this).find(".dropdown-content").toggleClass("open"); - // Handle making sure other drop menus are "closed" when another is open - $(this).siblings(".dropdown").find(".dropdown-content").removeClass("open"); - $(this).siblings(".dropdown").find(".dropbtn").attr("aria-expanded", "false"); - $(this).find(".dropbtn").attr('aria-expanded', function(i, attr) { - return attr == 'true' ? 'false' : 'true' - }); - }); - - // Close dropdown when you click outside of the nav ul - $(document).click(function(e) { - if (!e.target.closest("ul") && $(".dropdown-content").hasClass("open")) { - $(".dropdown-content").removeClass("open"); - } - }); -}); - - - -// Isotope filtering -// This blog has a good example of smart resizing ... https://jewelfarazi.me/create-jquery-isotope-responsive-masonry-layout/ -var qsRegex; -var buttonFilter; - -// Init Isotope -var $grid = $('.grid-isotope').imagesLoaded(function() { - $grid.isotope({ - itemSelector: '.element-item', - layoutMode: 'masonry', - masonry: { - columnWidth: '.grid-sizer', - horizontalOrder: true, - }, - filter: function() { - var $this = $(this); - var searchResult = qsRegex ? $this.text().match(qsRegex) : true; - var buttonResult = buttonFilter ? $this.is(buttonFilter) : true; - return searchResult && buttonResult; - } - }); -}); - -// Filter on button click -$('#filters').on('click', 'button', function() { - buttonFilter = $(this).attr('data-filter'); - $grid.isotope(); -}); - -// Use value of search field to filter -var $quicksearch = $('#quicksearch').keyup(debounce(function() { - qsRegex = new RegExp($quicksearch.val(), 'gi'); - $grid.isotope(); -})); - -// Change is-checked class on buttons -$('.button-group').each(function(i, buttonGroup) { - var $buttonGroup = $(buttonGroup); - $buttonGroup.on('click', 'button', function() { - $buttonGroup.find('.is-checked').removeClass('is-checked'); - $(this).addClass('is-checked'); - }); -}); - -// Debounce so filtering doesn't happen every millisecond -function debounce(fn, threshold) { - var timeout; - threshold = threshold || 100; - return function debounced() { - clearTimeout(timeout); - var args = arguments; - var _this = this; - - function delayed() { - fn.apply(_this, args); - } - timeout = setTimeout(delayed, threshold); - }; -} diff --git a/assets/js/lunr/lunr-en.js b/assets/js/lunr/lunr-en.js deleted file mode 100644 index 43429309..00000000 --- a/assets/js/lunr/lunr-en.js +++ /dev/null @@ -1,73 +0,0 @@ ---- -layout: none ---- - -var idx = lunr(function () { - this.field('title') - this.field('excerpt') - this.field('categories') - this.field('tags') - this.ref('id') - - this.pipeline.remove(lunr.trimmer) - - for (var item in store) { - this.add({ - title: store[item].title, - excerpt: store[item].excerpt, - categories: store[item].categories, - tags: store[item].tags, - id: item - }) - } -}); - -$(document).ready(function() { - $('input#search').on('keyup', function () { - var resultdiv = $('#results'); - var query = $(this).val().toLowerCase(); - var result = - idx.query(function (q) { - query.split(lunr.tokenizer.separator).forEach(function (term) { - q.term(term, { boost: 100 }) - if(query.lastIndexOf(" ") != query.length-1){ - q.term(term, { usePipeline: false, wildcard: lunr.Query.wildcard.TRAILING, boost: 10 }) - } - if (term != ""){ - q.term(term, { usePipeline: false, editDistance: 1, boost: 1 }) - } - }) - }); - resultdiv.empty(); - resultdiv.prepend('

'+result.length+' {{ site.data.ui-text[site.locale].results_found | default: "Result(s) found" }}

'); - for (var item in result) { - var ref = result[item].ref; - if(store[ref].teaser){ - var searchitem = - '
'+ - '
'+ - '

'+ - ''+store[ref].title+''+ - '

'+ - '
'+ - ''+ - '
'+ - '

'+store[ref].excerpt.split(" ").splice(0,20).join(" ")+'...

'+ - '
'+ - '
'; - } - else{ - var searchitem = - '
'+ - '
'+ - '

'+ - ''+store[ref].title+''+ - '

'+ - '

'+store[ref].excerpt.split(" ").splice(0,20).join(" ")+'...

'+ - '
'+ - '
'; - } - resultdiv.append(searchitem); - } - }); -}); diff --git a/assets/js/lunr/lunr-gr.js b/assets/js/lunr/lunr-gr.js deleted file mode 100644 index 10eb0e71..00000000 --- a/assets/js/lunr/lunr-gr.js +++ /dev/null @@ -1,526 +0,0 @@ ---- -layout: none ---- - -step1list = new Array(); -step1list["ΦΑΓΙΑ"] = "ΦΑ"; -step1list["ΦΑΓΙΟΥ"] = "ΦΑ"; -step1list["ΦΑΓΙΩΝ"] = "ΦΑ"; -step1list["ΣΚΑΓΙΑ"] = "ΣΚΑ"; -step1list["ΣΚΑΓΙΟΥ"] = "ΣΚΑ"; -step1list["ΣΚΑΓΙΩΝ"] = "ΣΚΑ"; -step1list["ΟΛΟΓΙΟΥ"] = "ΟΛΟ"; -step1list["ΟΛΟΓΙΑ"] = "ΟΛΟ"; -step1list["ΟΛΟΓΙΩΝ"] = "ΟΛΟ"; -step1list["ΣΟΓΙΟΥ"] = "ΣΟ"; -step1list["ΣΟΓΙΑ"] = "ΣΟ"; -step1list["ΣΟΓΙΩΝ"] = "ΣΟ"; -step1list["ΤΑΤΟΓΙΑ"] = "ΤΑΤΟ"; -step1list["ΤΑΤΟΓΙΟΥ"] = "ΤΑΤΟ"; -step1list["ΤΑΤΟΓΙΩΝ"] = "ΤΑΤΟ"; -step1list["ΚΡΕΑΣ"] = "ΚΡΕ"; -step1list["ΚΡΕΑΤΟΣ"] = "ΚΡΕ"; -step1list["ΚΡΕΑΤΑ"] = "ΚΡΕ"; -step1list["ΚΡΕΑΤΩΝ"] = "ΚΡΕ"; -step1list["ΠΕΡΑΣ"] = "ΠΕΡ"; -step1list["ΠΕΡΑΤΟΣ"] = "ΠΕΡ"; -step1list["ΠΕΡΑΤΑ"] = "ΠΕΡ"; -step1list["ΠΕΡΑΤΩΝ"] = "ΠΕΡ"; -step1list["ΤΕΡΑΣ"] = "ΤΕΡ"; -step1list["ΤΕΡΑΤΟΣ"] = "ΤΕΡ"; -step1list["ΤΕΡΑΤΑ"] = "ΤΕΡ"; -step1list["ΤΕΡΑΤΩΝ"] = "ΤΕΡ"; -step1list["ΦΩΣ"] = "ΦΩ"; -step1list["ΦΩΤΟΣ"] = "ΦΩ"; -step1list["ΦΩΤΑ"] = "ΦΩ"; -step1list["ΦΩΤΩΝ"] = "ΦΩ"; -step1list["ΚΑΘΕΣΤΩΣ"] = "ΚΑΘΕΣΤ"; -step1list["ΚΑΘΕΣΤΩΤΟΣ"] = "ΚΑΘΕΣΤ"; -step1list["ΚΑΘΕΣΤΩΤΑ"] = "ΚΑΘΕΣΤ"; -step1list["ΚΑΘΕΣΤΩΤΩΝ"] = "ΚΑΘΕΣΤ"; -step1list["ΓΕΓΟΝΟΣ"] = "ΓΕΓΟΝ"; -step1list["ΓΕΓΟΝΟΤΟΣ"] = "ΓΕΓΟΝ"; -step1list["ΓΕΓΟΝΟΤΑ"] = "ΓΕΓΟΝ"; -step1list["ΓΕΓΟΝΟΤΩΝ"] = "ΓΕΓΟΝ"; - -v = "[ΑΕΗΙΟΥΩ]"; -v2 = "[ΑΕΗΙΟΩ]" - -function stemWord(w) { - var stem; - var suffix; - var firstch; - var origword = w; - test1 = new Boolean(true); - - if(w.length < 4) { - return w; - } - - var re; - var re2; - var re3; - var re4; - - re = /(.*)(ΦΑΓΙΑ|ΦΑΓΙΟΥ|ΦΑΓΙΩΝ|ΣΚΑΓΙΑ|ΣΚΑΓΙΟΥ|ΣΚΑΓΙΩΝ|ΟΛΟΓΙΟΥ|ΟΛΟΓΙΑ|ΟΛΟΓΙΩΝ|ΣΟΓΙΟΥ|ΣΟΓΙΑ|ΣΟΓΙΩΝ|ΤΑΤΟΓΙΑ|ΤΑΤΟΓΙΟΥ|ΤΑΤΟΓΙΩΝ|ΚΡΕΑΣ|ΚΡΕΑΤΟΣ|ΚΡΕΑΤΑ|ΚΡΕΑΤΩΝ|ΠΕΡΑΣ|ΠΕΡΑΤΟΣ|ΠΕΡΑΤΑ|ΠΕΡΑΤΩΝ|ΤΕΡΑΣ|ΤΕΡΑΤΟΣ|ΤΕΡΑΤΑ|ΤΕΡΑΤΩΝ|ΦΩΣ|ΦΩΤΟΣ|ΦΩΤΑ|ΦΩΤΩΝ|ΚΑΘΕΣΤΩΣ|ΚΑΘΕΣΤΩΤΟΣ|ΚΑΘΕΣΤΩΤΑ|ΚΑΘΕΣΤΩΤΩΝ|ΓΕΓΟΝΟΣ|ΓΕΓΟΝΟΤΟΣ|ΓΕΓΟΝΟΤΑ|ΓΕΓΟΝΟΤΩΝ)$/; - - if(re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - suffix = fp[2]; - w = stem + step1list[suffix]; - test1 = false; - } - - re = /^(.+?)(ΑΔΕΣ|ΑΔΩΝ)$/; - - if(re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - w = stem; - - reg1 = /(ΟΚ|ΜΑΜ|ΜΑΝ|ΜΠΑΜΠ|ΠΑΤΕΡ|ΓΙΑΓΙ|ΝΤΑΝΤ|ΚΥΡ|ΘΕΙ|ΠΕΘΕΡ)$/; - - if(!(reg1.test(w))) { - w = w + "ΑΔ"; - } - } - - re2 = /^(.+?)(ΕΔΕΣ|ΕΔΩΝ)$/; - - if(re2.test(w)) { - var fp = re2.exec(w); - stem = fp[1]; - w = stem; - - exept2 = /(ΟΠ|ΙΠ|ΕΜΠ|ΥΠ|ΓΗΠ|ΔΑΠ|ΚΡΑΣΠ|ΜΙΛ)$/; - - if(exept2.test(w)) { - w = w + "ΕΔ"; - } - } - - re3 = /^(.+?)(ΟΥΔΕΣ|ΟΥΔΩΝ)$/; - - if(re3.test(w)) { - var fp = re3.exec(w); - stem = fp[1]; - w = stem; - - exept3 = /(ΑΡΚ|ΚΑΛΙΑΚ|ΠΕΤΑΛ|ΛΙΧ|ΠΛΕΞ|ΣΚ|Σ|ΦΛ|ΦΡ|ΒΕΛ|ΛΟΥΛ|ΧΝ|ΣΠ|ΤΡΑΓ|ΦΕ)$/; - - if(exept3.test(w)) { - w = w + "ΟΥΔ"; - } - } - - re4 = /^(.+?)(ΕΩΣ|ΕΩΝ)$/; - - if(re4.test(w)) { - var fp = re4.exec(w); - stem = fp[1]; - w = stem; - test1 = false; - - exept4 = /^(Θ|Δ|ΕΛ|ΓΑΛ|Ν|Π|ΙΔ|ΠΑΡ)$/; - - if(exept4.test(w)) { - w = w + "Ε"; - } - } - - re = /^(.+?)(ΙΑ|ΙΟΥ|ΙΩΝ)$/; - - if(re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - w = stem; - re2 = new RegExp(v + "$"); - test1 = false; - - if(re2.test(w)) { - w = stem + "Ι"; - } - } - - re = /^(.+?)(ΙΚΑ|ΙΚΟ|ΙΚΟΥ|ΙΚΩΝ)$/; - - if(re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - w = stem; - test1 = false; - - re2 = new RegExp(v + "$"); - exept5 = /^(ΑΛ|ΑΔ|ΕΝΔ|ΑΜΑΝ|ΑΜΜΟΧΑΛ|ΗΘ|ΑΝΗΘ|ΑΝΤΙΔ|ΦΥΣ|ΒΡΩΜ|ΓΕΡ|ΕΞΩΔ|ΚΑΛΠ|ΚΑΛΛΙΝ|ΚΑΤΑΔ|ΜΟΥΛ|ΜΠΑΝ|ΜΠΑΓΙΑΤ|ΜΠΟΛ|ΜΠΟΣ|ΝΙΤ|ΞΙΚ|ΣΥΝΟΜΗΛ|ΠΕΤΣ|ΠΙΤΣ|ΠΙΚΑΝΤ|ΠΛΙΑΤΣ|ΠΟΣΤΕΛΝ|ΠΡΩΤΟΔ|ΣΕΡΤ|ΣΥΝΑΔ|ΤΣΑΜ|ΥΠΟΔ|ΦΙΛΟΝ|ΦΥΛΟΔ|ΧΑΣ)$/; - - if((exept5.test(w)) || (re2.test(w))) { - w = w + "ΙΚ"; - } - } - - re = /^(.+?)(ΑΜΕ)$/; - re2 = /^(.+?)(ΑΓΑΜΕ|ΗΣΑΜΕ|ΟΥΣΑΜΕ|ΗΚΑΜΕ|ΗΘΗΚΑΜΕ)$/; - if(w == "ΑΓΑΜΕ") { - w = "ΑΓΑΜ"; - } - - if(re2.test(w)) { - var fp = re2.exec(w); - stem = fp[1]; - w = stem; - test1 = false; - } - - if(re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - w = stem; - test1 = false; - - exept6 = /^(ΑΝΑΠ|ΑΠΟΘ|ΑΠΟΚ|ΑΠΟΣΤ|ΒΟΥΒ|ΞΕΘ|ΟΥΛ|ΠΕΘ|ΠΙΚΡ|ΠΟΤ|ΣΙΧ|Χ)$/; - - if(exept6.test(w)) { - w = w + "ΑΜ"; - } - } - - re2 = /^(.+?)(ΑΝΕ)$/; - re3 = /^(.+?)(ΑΓΑΝΕ|ΗΣΑΝΕ|ΟΥΣΑΝΕ|ΙΟΝΤΑΝΕ|ΙΟΤΑΝΕ|ΙΟΥΝΤΑΝΕ|ΟΝΤΑΝΕ|ΟΤΑΝΕ|ΟΥΝΤΑΝΕ|ΗΚΑΝΕ|ΗΘΗΚΑΝΕ)$/; - - if(re3.test(w)) { - var fp = re3.exec(w); - stem = fp[1]; - w = stem; - test1 = false; - - re3 = /^(ΤΡ|ΤΣ)$/; - - if(re3.test(w)) { - w = w + "ΑΓΑΝ"; - } - } - - if(re2.test(w)) { - var fp = re2.exec(w); - stem = fp[1]; - w = stem; - test1 = false; - - re2 = new RegExp(v2 + "$"); - exept7 = /^(ΒΕΤΕΡ|ΒΟΥΛΚ|ΒΡΑΧΜ|Γ|ΔΡΑΔΟΥΜ|Θ|ΚΑΛΠΟΥΖ|ΚΑΣΤΕΛ|ΚΟΡΜΟΡ|ΛΑΟΠΛ|ΜΩΑΜΕΘ|Μ|ΜΟΥΣΟΥΛΜ|Ν|ΟΥΛ|Π|ΠΕΛΕΚ|ΠΛ|ΠΟΛΙΣ|ΠΟΡΤΟΛ|ΣΑΡΑΚΑΤΣ|ΣΟΥΛΤ|ΤΣΑΡΛΑΤ|ΟΡΦ|ΤΣΙΓΓ|ΤΣΟΠ|ΦΩΤΟΣΤΕΦ|Χ|ΨΥΧΟΠΛ|ΑΓ|ΟΡΦ|ΓΑΛ|ΓΕΡ|ΔΕΚ|ΔΙΠΛ|ΑΜΕΡΙΚΑΝ|ΟΥΡ|ΠΙΘ|ΠΟΥΡΙΤ|Σ|ΖΩΝΤ|ΙΚ|ΚΑΣΤ|ΚΟΠ|ΛΙΧ|ΛΟΥΘΗΡ|ΜΑΙΝΤ|ΜΕΛ|ΣΙΓ|ΣΠ|ΣΤΕΓ|ΤΡΑΓ|ΤΣΑΓ|Φ|ΕΡ|ΑΔΑΠ|ΑΘΙΓΓ|ΑΜΗΧ|ΑΝΙΚ|ΑΝΟΡΓ|ΑΠΗΓ|ΑΠΙΘ|ΑΤΣΙΓΓ|ΒΑΣ|ΒΑΣΚ|ΒΑΘΥΓΑΛ|ΒΙΟΜΗΧ|ΒΡΑΧΥΚ|ΔΙΑΤ|ΔΙΑΦ|ΕΝΟΡΓ|ΘΥΣ|ΚΑΠΝΟΒΙΟΜΗΧ|ΚΑΤΑΓΑΛ|ΚΛΙΒ|ΚΟΙΛΑΡΦ|ΛΙΒ|ΜΕΓΛΟΒΙΟΜΗΧ|ΜΙΚΡΟΒΙΟΜΗΧ|ΝΤΑΒ|ΞΗΡΟΚΛΙΒ|ΟΛΙΓΟΔΑΜ|ΟΛΟΓΑΛ|ΠΕΝΤΑΡΦ|ΠΕΡΗΦ|ΠΕΡΙΤΡ|ΠΛΑΤ|ΠΟΛΥΔΑΠ|ΠΟΛΥΜΗΧ|ΣΤΕΦ|ΤΑΒ|ΤΕΤ|ΥΠΕΡΗΦ|ΥΠΟΚΟΠ|ΧΑΜΗΛΟΔΑΠ|ΨΗΛΟΤΑΒ)$/; - - if((re2.test(w)) || (exept7.test(w))) { - w = w + "ΑΝ"; - } - } - - re3 = /^(.+?)(ΕΤΕ)$/; - re4 = /^(.+?)(ΗΣΕΤΕ)$/; - - if(re4.test(w)) { - var fp = re4.exec(w); - stem = fp[1]; - w = stem; - test1 = false; - } - - if(re3.test(w)) { - var fp = re3.exec(w); - stem = fp[1]; - w = stem; - test1 = false; - - re3 = new RegExp(v2 + "$"); - exept8 = /(ΟΔ|ΑΙΡ|ΦΟΡ|ΤΑΘ|ΔΙΑΘ|ΣΧ|ΕΝΔ|ΕΥΡ|ΤΙΘ|ΥΠΕΡΘ|ΡΑΘ|ΕΝΘ|ΡΟΘ|ΣΘ|ΠΥΡ|ΑΙΝ|ΣΥΝΔ|ΣΥΝ|ΣΥΝΘ|ΧΩΡ|ΠΟΝ|ΒΡ|ΚΑΘ|ΕΥΘ|ΕΚΘ|ΝΕΤ|ΡΟΝ|ΑΡΚ|ΒΑΡ|ΒΟΛ|ΩΦΕΛ)$/; - exept9 = /^(ΑΒΑΡ|ΒΕΝ|ΕΝΑΡ|ΑΒΡ|ΑΔ|ΑΘ|ΑΝ|ΑΠΛ|ΒΑΡΟΝ|ΝΤΡ|ΣΚ|ΚΟΠ|ΜΠΟΡ|ΝΙΦ|ΠΑΓ|ΠΑΡΑΚΑΛ|ΣΕΡΠ|ΣΚΕΛ|ΣΥΡΦ|ΤΟΚ|Υ|Δ|ΕΜ|ΘΑΡΡ|Θ)$/; - - if((re3.test(w)) || (exept8.test(w)) || (exept9.test(w))) { - w = w + "ΕΤ"; - } - } - - re = /^(.+?)(ΟΝΤΑΣ|ΩΝΤΑΣ)$/; - - if(re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - w = stem; - test1 = false; - - exept10 = /^(ΑΡΧ)$/; - exept11 = /(ΚΡΕ)$/; - if(exept10.test(w)) { - w = w + "ΟΝΤ"; - } - if(exept11.test(w)) { - w = w + "ΩΝΤ"; - } - } - - re = /^(.+?)(ΟΜΑΣΤΕ|ΙΟΜΑΣΤΕ)$/; - - if(re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - w = stem; - test1 = false; - - exept11 = /^(ΟΝ)$/; - - if(exept11.test(w)) { - w = w + "ΟΜΑΣΤ"; - } - } - - re = /^(.+?)(ΕΣΤΕ)$/; - re2 = /^(.+?)(ΙΕΣΤΕ)$/; - - if(re2.test(w)) { - var fp = re2.exec(w); - stem = fp[1]; - w = stem; - test1 = false; - - re2 = /^(Π|ΑΠ|ΣΥΜΠ|ΑΣΥΜΠ|ΑΚΑΤΑΠ|ΑΜΕΤΑΜΦ)$/; - - if(re2.test(w)) { - w = w + "ΙΕΣΤ"; - } - } - - if(re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - w = stem; - test1 = false; - - exept12 = /^(ΑΛ|ΑΡ|ΕΚΤΕΛ|Ζ|Μ|Ξ|ΠΑΡΑΚΑΛ|ΑΡ|ΠΡΟ|ΝΙΣ)$/; - - if(exept12.test(w)) { - w = w + "ΕΣΤ"; - } - } - - re = /^(.+?)(ΗΚΑ|ΗΚΕΣ|ΗΚΕ)$/; - re2 = /^(.+?)(ΗΘΗΚΑ|ΗΘΗΚΕΣ|ΗΘΗΚΕ)$/; - - if(re2.test(w)) { - var fp = re2.exec(w); - stem = fp[1]; - w = stem; - test1 = false; - } - - if(re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - w = stem; - test1 = false; - - exept13 = /(ΣΚΩΛ|ΣΚΟΥΛ|ΝΑΡΘ|ΣΦ|ΟΘ|ΠΙΘ)$/; - exept14 = /^(ΔΙΑΘ|Θ|ΠΑΡΑΚΑΤΑΘ|ΠΡΟΣΘ|ΣΥΝΘ|)$/; - - if((exept13.test(w)) || (exept14.test(w))) { - w = w + "ΗΚ"; - } - } - - re = /^(.+?)(ΟΥΣΑ|ΟΥΣΕΣ|ΟΥΣΕ)$/; - - if(re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - w = stem; - test1 = false; - - exept15 = /^(ΦΑΡΜΑΚ|ΧΑΔ|ΑΓΚ|ΑΝΑΡΡ|ΒΡΟΜ|ΕΚΛΙΠ|ΛΑΜΠΙΔ|ΛΕΧ|Μ|ΠΑΤ|Ρ|Λ|ΜΕΔ|ΜΕΣΑΖ|ΥΠΟΤΕΙΝ|ΑΜ|ΑΙΘ|ΑΝΗΚ|ΔΕΣΠΟΖ|ΕΝΔΙΑΦΕΡ|ΔΕ|ΔΕΥΤΕΡΕΥ|ΚΑΘΑΡΕΥ|ΠΛΕ|ΤΣΑ)$/; - exept16 = /(ΠΟΔΑΡ|ΒΛΕΠ|ΠΑΝΤΑΧ|ΦΡΥΔ|ΜΑΝΤΙΛ|ΜΑΛΛ|ΚΥΜΑΤ|ΛΑΧ|ΛΗΓ|ΦΑΓ|ΟΜ|ΠΡΩΤ)$/; - - if((exept15.test(w)) || (exept16.test(w))) { - w = w + "ΟΥΣ"; - } - } - - re = /^(.+?)(ΑΓΑ|ΑΓΕΣ|ΑΓΕ)$/; - - if(re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - w = stem; - test1 = false; - - exept17 = /^(ΨΟΦ|ΝΑΥΛΟΧ)$/; - exept20 = /(ΚΟΛΛ)$/; - exept18 = /^(ΑΒΑΣΤ|ΠΟΛΥΦ|ΑΔΗΦ|ΠΑΜΦ|Ρ|ΑΣΠ|ΑΦ|ΑΜΑΛ|ΑΜΑΛΛΙ|ΑΝΥΣΤ|ΑΠΕΡ|ΑΣΠΑΡ|ΑΧΑΡ|ΔΕΡΒΕΝ|ΔΡΟΣΟΠ|ΞΕΦ|ΝΕΟΠ|ΝΟΜΟΤ|ΟΛΟΠ|ΟΜΟΤ|ΠΡΟΣΤ|ΠΡΟΣΩΠΟΠ|ΣΥΜΠ|ΣΥΝΤ|Τ|ΥΠΟΤ|ΧΑΡ|ΑΕΙΠ|ΑΙΜΟΣΤ|ΑΝΥΠ|ΑΠΟΤ|ΑΡΤΙΠ|ΔΙΑΤ|ΕΝ|ΕΠΙΤ|ΚΡΟΚΑΛΟΠ|ΣΙΔΗΡΟΠ|Λ|ΝΑΥ|ΟΥΛΑΜ|ΟΥΡ|Π|ΤΡ|Μ)$/; - exept19 = /(ΟΦ|ΠΕΛ|ΧΟΡΤ|ΛΛ|ΣΦ|ΡΠ|ΦΡ|ΠΡ|ΛΟΧ|ΣΜΗΝ)$/; - - if(((exept18.test(w)) || (exept19.test(w))) && !((exept17.test(w)) || (exept20.test(w)))) { - w = w + "ΑΓ"; - } - } - - re = /^(.+?)(ΗΣΕ|ΗΣΟΥ|ΗΣΑ)$/; - - if(re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - w = stem; - test1 = false; - - exept21 = /^(Ν|ΧΕΡΣΟΝ|ΔΩΔΕΚΑΝ|ΕΡΗΜΟΝ|ΜΕΓΑΛΟΝ|ΕΠΤΑΝ)$/; - - if(exept21.test(w)) { - w = w + "ΗΣ"; - } - } - - re = /^(.+?)(ΗΣΤΕ)$/; - - if(re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - w = stem; - test1 = false; - - exept22 = /^(ΑΣΒ|ΣΒ|ΑΧΡ|ΧΡ|ΑΠΛ|ΑΕΙΜΝ|ΔΥΣΧΡ|ΕΥΧΡ|ΚΟΙΝΟΧΡ|ΠΑΛΙΜΨ)$/; - - if(exept22.test(w)) { - w = w + "ΗΣΤ"; - } - } - - re = /^(.+?)(ΟΥΝΕ|ΗΣΟΥΝΕ|ΗΘΟΥΝΕ)$/; - - if(re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - w = stem; - test1 = false; - - exept23 = /^(Ν|Ρ|ΣΠΙ|ΣΤΡΑΒΟΜΟΥΤΣ|ΚΑΚΟΜΟΥΤΣ|ΕΞΩΝ)$/; - - if(exept23.test(w)) { - w = w + "ΟΥΝ"; - } - } - - re = /^(.+?)(ΟΥΜΕ|ΗΣΟΥΜΕ|ΗΘΟΥΜΕ)$/; - - if(re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - w = stem; - test1 = false; - - exept24 = /^(ΠΑΡΑΣΟΥΣ|Φ|Χ|ΩΡΙΟΠΛ|ΑΖ|ΑΛΛΟΣΟΥΣ|ΑΣΟΥΣ)$/; - - if(exept24.test(w)) { - w = w + "ΟΥΜ"; - } - } - - re = /^(.+?)(ΜΑΤΑ|ΜΑΤΩΝ|ΜΑΤΟΣ)$/; - re2 = /^(.+?)(Α|ΑΓΑΤΕ|ΑΓΑΝ|ΑΕΙ|ΑΜΑΙ|ΑΝ|ΑΣ|ΑΣΑΙ|ΑΤΑΙ|ΑΩ|Ε|ΕΙ|ΕΙΣ|ΕΙΤΕ|ΕΣΑΙ|ΕΣ|ΕΤΑΙ|Ι|ΙΕΜΑΙ|ΙΕΜΑΣΤΕ|ΙΕΤΑΙ|ΙΕΣΑΙ|ΙΕΣΑΣΤΕ|ΙΟΜΑΣΤΑΝ|ΙΟΜΟΥΝ|ΙΟΜΟΥΝΑ|ΙΟΝΤΑΝ|ΙΟΝΤΟΥΣΑΝ|ΙΟΣΑΣΤΑΝ|ΙΟΣΑΣΤΕ|ΙΟΣΟΥΝ|ΙΟΣΟΥΝΑ|ΙΟΤΑΝ|ΙΟΥΜΑ|ΙΟΥΜΑΣΤΕ|ΙΟΥΝΤΑΙ|ΙΟΥΝΤΑΝ|Η|ΗΔΕΣ|ΗΔΩΝ|ΗΘΕΙ|ΗΘΕΙΣ|ΗΘΕΙΤΕ|ΗΘΗΚΑΤΕ|ΗΘΗΚΑΝ|ΗΘΟΥΝ|ΗΘΩ|ΗΚΑΤΕ|ΗΚΑΝ|ΗΣ|ΗΣΑΝ|ΗΣΑΤΕ|ΗΣΕΙ|ΗΣΕΣ|ΗΣΟΥΝ|ΗΣΩ|Ο|ΟΙ|ΟΜΑΙ|ΟΜΑΣΤΑΝ|ΟΜΟΥΝ|ΟΜΟΥΝΑ|ΟΝΤΑΙ|ΟΝΤΑΝ|ΟΝΤΟΥΣΑΝ|ΟΣ|ΟΣΑΣΤΑΝ|ΟΣΑΣΤΕ|ΟΣΟΥΝ|ΟΣΟΥΝΑ|ΟΤΑΝ|ΟΥ|ΟΥΜΑΙ|ΟΥΜΑΣΤΕ|ΟΥΝ|ΟΥΝΤΑΙ|ΟΥΝΤΑΝ|ΟΥΣ|ΟΥΣΑΝ|ΟΥΣΑΤΕ|Υ|ΥΣ|Ω|ΩΝ)$/; - - if(re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - w = stem + "ΜΑ"; - } - - if((re2.test(w)) && (test1)) { - var fp = re2.exec(w); - stem = fp[1]; - w = stem; - - } - - re = /^(.+?)(ΕΣΤΕΡ|ΕΣΤΑΤ|ΟΤΕΡ|ΟΤΑΤ|ΥΤΕΡ|ΥΤΑΤ|ΩΤΕΡ|ΩΤΑΤ)$/; - - if(re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - w = stem; - } - - return w; -}; - -var greekStemmer = function (token) { - return token.update(function (word) { - return stemWord(word); - }) -} - -var idx = lunr(function () { - this.field('title') - this.field('excerpt') - this.field('categories') - this.field('tags') - this.ref('id') - - this.pipeline.remove(lunr.trimmer) - this.pipeline.add(greekStemmer) - this.pipeline.remove(lunr.stemmer) - - for (var item in store) { - this.add({ - title: store[item].title, - excerpt: store[item].excerpt, - categories: store[item].categories, - tags: store[item].tags, - id: item - }) - } -}); - -$(document).ready(function() { - $('input#search').on('keyup', function () { - var resultdiv = $('#results'); - var query = $(this).val().toLowerCase(); - var result = - idx.query(function (q) { - query.split(lunr.tokenizer.separator).forEach(function (term) { - q.term(term, { boost: 100 }) - if(query.lastIndexOf(" ") != query.length-1){ - q.term(term, { usePipeline: false, wildcard: lunr.Query.wildcard.TRAILING, boost: 10 }) - } - if (term != ""){ - q.term(term, { usePipeline: false, editDistance: 1, boost: 1 }) - } - }) - }); - resultdiv.empty(); - resultdiv.prepend('

'+result.length+' {{ site.data.ui-text[site.locale].results_found | default: "Result(s) found" }}

'); - for (var item in result) { - var ref = result[item].ref; - if(store[ref].teaser){ - var searchitem = - '
'+ - '
'+ - '

'+ - ''+store[ref].title+''+ - '

'+ - '
'+ - ''+ - '
'+ - '

'+store[ref].excerpt.split(" ").splice(0,20).join(" ")+'...

'+ - '
'+ - '
'; - } - else{ - var searchitem = - '
'+ - '
'+ - '

'+ - ''+store[ref].title+''+ - '

'+ - '

'+store[ref].excerpt.split(" ").splice(0,20).join(" ")+'...

'+ - '
'+ - '
'; - } - resultdiv.append(searchitem); - } - }); -}); diff --git a/assets/js/lunr/lunr-store.js b/assets/js/lunr/lunr-store.js deleted file mode 100644 index 565e3b3e..00000000 --- a/assets/js/lunr/lunr-store.js +++ /dev/null @@ -1,84 +0,0 @@ ---- -layout: none ---- - -var store = [ - {%- for c in site.collections -%} - {%- if forloop.last -%} - {%- assign l = true -%} - {%- endif -%} - {%- assign docs = c.docs | where_exp:'doc','doc.search != false' -%} - {%- for doc in docs -%} - {%- if doc.header.teaser -%} - {%- capture teaser -%}{{ doc.header.teaser }}{%- endcapture -%} - {%- else -%} - {%- assign teaser = site.teaser -%} - {%- endif -%} - { - "title": {{ doc.title | jsonify }}, - "excerpt": - {%- if site.search_full_content == true -%} - {{ doc.content | newline_to_br | - replace:"
", " " | - replace:"

", " " | - replace:"", " " | - replace:"", " " | - replace:"", " " | - replace:"", " " | - replace:"", " " | - replace:"", " "| - strip_html | strip_newlines | jsonify }}, - {%- else -%} - {{ doc.content | newline_to_br | - replace:"
", " " | - replace:"

", " " | - replace:"", " " | - replace:"", " " | - replace:"", " " | - replace:"", " " | - replace:"", " " | - replace:"", " "| - strip_html | strip_newlines | truncatewords: 50 | jsonify }}, - {%- endif -%} - "categories": {{ doc.categories | jsonify }}, - "tags": {{ doc.tags | jsonify }}, - "url": {{ doc.url | relative_url | jsonify }}, - "teaser": {{ teaser | relative_url | jsonify }} - }{%- unless forloop.last and l -%},{%- endunless -%} - {%- endfor -%} - {%- endfor -%}{%- if site.lunr.search_within_pages -%}, - {%- assign pages = site.pages | where_exp:'doc','doc.search != false' -%} - {%- for doc in pages -%} - {%- if forloop.last -%} - {%- assign l = true -%} - {%- endif -%} - { - "title": {{ doc.title | jsonify }}, - "excerpt": - {%- if site.search_full_content == true -%} - {{ doc.content | newline_to_br | - replace:"
", " " | - replace:"

", " " | - replace:"", " " | - replace:"", " " | - replace:"", " " | - replace:"", " " | - replace:"", " " | - replace:"", " "| - strip_html | strip_newlines | jsonify }}, - {%- else -%} - {{ doc.content | newline_to_br | - replace:"
", " " | - replace:"

", " " | - replace:"", " " | - replace:"", " " | - replace:"", " " | - replace:"", " " | - replace:"", " " | - replace:"", " "| - strip_html | strip_newlines | truncatewords: 50 | jsonify }}, - {%- endif -%} - "url": {{ doc.url | absolute_url | jsonify }} - }{%- unless forloop.last and l -%},{%- endunless -%} - {%- endfor -%} -{%- endif -%}] diff --git a/assets/js/lunr/lunr.js b/assets/js/lunr/lunr.js deleted file mode 100644 index 6aa370fb..00000000 --- a/assets/js/lunr/lunr.js +++ /dev/null @@ -1,3475 +0,0 @@ -/** - * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.9 - * Copyright (C) 2020 Oliver Nightingale - * @license MIT - */ - -;(function(){ - -/** - * A convenience function for configuring and constructing - * a new lunr Index. - * - * A lunr.Builder instance is created and the pipeline setup - * with a trimmer, stop word filter and stemmer. - * - * This builder object is yielded to the configuration function - * that is passed as a parameter, allowing the list of fields - * and other builder parameters to be customised. - * - * All documents _must_ be added within the passed config function. - * - * @example - * var idx = lunr(function () { - * this.field('title') - * this.field('body') - * this.ref('id') - * - * documents.forEach(function (doc) { - * this.add(doc) - * }, this) - * }) - * - * @see {@link lunr.Builder} - * @see {@link lunr.Pipeline} - * @see {@link lunr.trimmer} - * @see {@link lunr.stopWordFilter} - * @see {@link lunr.stemmer} - * @namespace {function} lunr - */ -var lunr = function (config) { - var builder = new lunr.Builder - - builder.pipeline.add( - lunr.trimmer, - lunr.stopWordFilter, - lunr.stemmer - ) - - builder.searchPipeline.add( - lunr.stemmer - ) - - config.call(builder, builder) - return builder.build() -} - -lunr.version = "2.3.9" -/*! - * lunr.utils - * Copyright (C) 2020 Oliver Nightingale - */ - -/** - * A namespace containing utils for the rest of the lunr library - * @namespace lunr.utils - */ -lunr.utils = {} - -/** - * Print a warning message to the console. - * - * @param {String} message The message to be printed. - * @memberOf lunr.utils - * @function - */ -lunr.utils.warn = (function (global) { - /* eslint-disable no-console */ - return function (message) { - if (global.console && console.warn) { - console.warn(message) - } - } - /* eslint-enable no-console */ -})(this) - -/** - * Convert an object to a string. - * - * In the case of `null` and `undefined` the function returns - * the empty string, in all other cases the result of calling - * `toString` on the passed object is returned. - * - * @param {Any} obj The object to convert to a string. - * @return {String} string representation of the passed object. - * @memberOf lunr.utils - */ -lunr.utils.asString = function (obj) { - if (obj === void 0 || obj === null) { - return "" - } else { - return obj.toString() - } -} - -/** - * Clones an object. - * - * Will create a copy of an existing object such that any mutations - * on the copy cannot affect the original. - * - * Only shallow objects are supported, passing a nested object to this - * function will cause a TypeError. - * - * Objects with primitives, and arrays of primitives are supported. - * - * @param {Object} obj The object to clone. - * @return {Object} a clone of the passed object. - * @throws {TypeError} when a nested object is passed. - * @memberOf Utils - */ -lunr.utils.clone = function (obj) { - if (obj === null || obj === undefined) { - return obj - } - - var clone = Object.create(null), - keys = Object.keys(obj) - - for (var i = 0; i < keys.length; i++) { - var key = keys[i], - val = obj[key] - - if (Array.isArray(val)) { - clone[key] = val.slice() - continue - } - - if (typeof val === 'string' || - typeof val === 'number' || - typeof val === 'boolean') { - clone[key] = val - continue - } - - throw new TypeError("clone is not deep and does not support nested objects") - } - - return clone -} -lunr.FieldRef = function (docRef, fieldName, stringValue) { - this.docRef = docRef - this.fieldName = fieldName - this._stringValue = stringValue -} - -lunr.FieldRef.joiner = "/" - -lunr.FieldRef.fromString = function (s) { - var n = s.indexOf(lunr.FieldRef.joiner) - - if (n === -1) { - throw "malformed field ref string" - } - - var fieldRef = s.slice(0, n), - docRef = s.slice(n + 1) - - return new lunr.FieldRef (docRef, fieldRef, s) -} - -lunr.FieldRef.prototype.toString = function () { - if (this._stringValue == undefined) { - this._stringValue = this.fieldName + lunr.FieldRef.joiner + this.docRef - } - - return this._stringValue -} -/*! - * lunr.Set - * Copyright (C) 2020 Oliver Nightingale - */ - -/** - * A lunr set. - * - * @constructor - */ -lunr.Set = function (elements) { - this.elements = Object.create(null) - - if (elements) { - this.length = elements.length - - for (var i = 0; i < this.length; i++) { - this.elements[elements[i]] = true - } - } else { - this.length = 0 - } -} - -/** - * A complete set that contains all elements. - * - * @static - * @readonly - * @type {lunr.Set} - */ -lunr.Set.complete = { - intersect: function (other) { - return other - }, - - union: function () { - return this - }, - - contains: function () { - return true - } -} - -/** - * An empty set that contains no elements. - * - * @static - * @readonly - * @type {lunr.Set} - */ -lunr.Set.empty = { - intersect: function () { - return this - }, - - union: function (other) { - return other - }, - - contains: function () { - return false - } -} - -/** - * Returns true if this set contains the specified object. - * - * @param {object} object - Object whose presence in this set is to be tested. - * @returns {boolean} - True if this set contains the specified object. - */ -lunr.Set.prototype.contains = function (object) { - return !!this.elements[object] -} - -/** - * Returns a new set containing only the elements that are present in both - * this set and the specified set. - * - * @param {lunr.Set} other - set to intersect with this set. - * @returns {lunr.Set} a new set that is the intersection of this and the specified set. - */ - -lunr.Set.prototype.intersect = function (other) { - var a, b, elements, intersection = [] - - if (other === lunr.Set.complete) { - return this - } - - if (other === lunr.Set.empty) { - return other - } - - if (this.length < other.length) { - a = this - b = other - } else { - a = other - b = this - } - - elements = Object.keys(a.elements) - - for (var i = 0; i < elements.length; i++) { - var element = elements[i] - if (element in b.elements) { - intersection.push(element) - } - } - - return new lunr.Set (intersection) -} - -/** - * Returns a new set combining the elements of this and the specified set. - * - * @param {lunr.Set} other - set to union with this set. - * @return {lunr.Set} a new set that is the union of this and the specified set. - */ - -lunr.Set.prototype.union = function (other) { - if (other === lunr.Set.complete) { - return lunr.Set.complete - } - - if (other === lunr.Set.empty) { - return this - } - - return new lunr.Set(Object.keys(this.elements).concat(Object.keys(other.elements))) -} -/** - * A function to calculate the inverse document frequency for - * a posting. This is shared between the builder and the index - * - * @private - * @param {object} posting - The posting for a given term - * @param {number} documentCount - The total number of documents. - */ -lunr.idf = function (posting, documentCount) { - var documentsWithTerm = 0 - - for (var fieldName in posting) { - if (fieldName == '_index') continue // Ignore the term index, its not a field - documentsWithTerm += Object.keys(posting[fieldName]).length - } - - var x = (documentCount - documentsWithTerm + 0.5) / (documentsWithTerm + 0.5) - - return Math.log(1 + Math.abs(x)) -} - -/** - * A token wraps a string representation of a token - * as it is passed through the text processing pipeline. - * - * @constructor - * @param {string} [str=''] - The string token being wrapped. - * @param {object} [metadata={}] - Metadata associated with this token. - */ -lunr.Token = function (str, metadata) { - this.str = str || "" - this.metadata = metadata || {} -} - -/** - * Returns the token string that is being wrapped by this object. - * - * @returns {string} - */ -lunr.Token.prototype.toString = function () { - return this.str -} - -/** - * A token update function is used when updating or optionally - * when cloning a token. - * - * @callback lunr.Token~updateFunction - * @param {string} str - The string representation of the token. - * @param {Object} metadata - All metadata associated with this token. - */ - -/** - * Applies the given function to the wrapped string token. - * - * @example - * token.update(function (str, metadata) { - * return str.toUpperCase() - * }) - * - * @param {lunr.Token~updateFunction} fn - A function to apply to the token string. - * @returns {lunr.Token} - */ -lunr.Token.prototype.update = function (fn) { - this.str = fn(this.str, this.metadata) - return this -} - -/** - * Creates a clone of this token. Optionally a function can be - * applied to the cloned token. - * - * @param {lunr.Token~updateFunction} [fn] - An optional function to apply to the cloned token. - * @returns {lunr.Token} - */ -lunr.Token.prototype.clone = function (fn) { - fn = fn || function (s) { return s } - return new lunr.Token (fn(this.str, this.metadata), this.metadata) -} -/*! - * lunr.tokenizer - * Copyright (C) 2020 Oliver Nightingale - */ - -/** - * A function for splitting a string into tokens ready to be inserted into - * the search index. Uses `lunr.tokenizer.separator` to split strings, change - * the value of this property to change how strings are split into tokens. - * - * This tokenizer will convert its parameter to a string by calling `toString` and - * then will split this string on the character in `lunr.tokenizer.separator`. - * Arrays will have their elements converted to strings and wrapped in a lunr.Token. - * - * Optional metadata can be passed to the tokenizer, this metadata will be cloned and - * added as metadata to every token that is created from the object to be tokenized. - * - * @static - * @param {?(string|object|object[])} obj - The object to convert into tokens - * @param {?object} metadata - Optional metadata to associate with every token - * @returns {lunr.Token[]} - * @see {@link lunr.Pipeline} - */ -lunr.tokenizer = function (obj, metadata) { - if (obj == null || obj == undefined) { - return [] - } - - if (Array.isArray(obj)) { - return obj.map(function (t) { - return new lunr.Token( - lunr.utils.asString(t).toLowerCase(), - lunr.utils.clone(metadata) - ) - }) - } - - var str = obj.toString().toLowerCase(), - len = str.length, - tokens = [] - - for (var sliceEnd = 0, sliceStart = 0; sliceEnd <= len; sliceEnd++) { - var char = str.charAt(sliceEnd), - sliceLength = sliceEnd - sliceStart - - if ((char.match(lunr.tokenizer.separator) || sliceEnd == len)) { - - if (sliceLength > 0) { - var tokenMetadata = lunr.utils.clone(metadata) || {} - tokenMetadata["position"] = [sliceStart, sliceLength] - tokenMetadata["index"] = tokens.length - - tokens.push( - new lunr.Token ( - str.slice(sliceStart, sliceEnd), - tokenMetadata - ) - ) - } - - sliceStart = sliceEnd + 1 - } - - } - - return tokens -} - -/** - * The separator used to split a string into tokens. Override this property to change the behaviour of - * `lunr.tokenizer` behaviour when tokenizing strings. By default this splits on whitespace and hyphens. - * - * @static - * @see lunr.tokenizer - */ -lunr.tokenizer.separator = /[\s\-]+/ -/*! - * lunr.Pipeline - * Copyright (C) 2020 Oliver Nightingale - */ - -/** - * lunr.Pipelines maintain an ordered list of functions to be applied to all - * tokens in documents entering the search index and queries being ran against - * the index. - * - * An instance of lunr.Index created with the lunr shortcut will contain a - * pipeline with a stop word filter and an English language stemmer. Extra - * functions can be added before or after either of these functions or these - * default functions can be removed. - * - * When run the pipeline will call each function in turn, passing a token, the - * index of that token in the original list of all tokens and finally a list of - * all the original tokens. - * - * The output of functions in the pipeline will be passed to the next function - * in the pipeline. To exclude a token from entering the index the function - * should return undefined, the rest of the pipeline will not be called with - * this token. - * - * For serialisation of pipelines to work, all functions used in an instance of - * a pipeline should be registered with lunr.Pipeline. Registered functions can - * then be loaded. If trying to load a serialised pipeline that uses functions - * that are not registered an error will be thrown. - * - * If not planning on serialising the pipeline then registering pipeline functions - * is not necessary. - * - * @constructor - */ -lunr.Pipeline = function () { - this._stack = [] -} - -lunr.Pipeline.registeredFunctions = Object.create(null) - -/** - * A pipeline function maps lunr.Token to lunr.Token. A lunr.Token contains the token - * string as well as all known metadata. A pipeline function can mutate the token string - * or mutate (or add) metadata for a given token. - * - * A pipeline function can indicate that the passed token should be discarded by returning - * null, undefined or an empty string. This token will not be passed to any downstream pipeline - * functions and will not be added to the index. - * - * Multiple tokens can be returned by returning an array of tokens. Each token will be passed - * to any downstream pipeline functions and all will returned tokens will be added to the index. - * - * Any number of pipeline functions may be chained together using a lunr.Pipeline. - * - * @interface lunr.PipelineFunction - * @param {lunr.Token} token - A token from the document being processed. - * @param {number} i - The index of this token in the complete list of tokens for this document/field. - * @param {lunr.Token[]} tokens - All tokens for this document/field. - * @returns {(?lunr.Token|lunr.Token[])} - */ - -/** - * Register a function with the pipeline. - * - * Functions that are used in the pipeline should be registered if the pipeline - * needs to be serialised, or a serialised pipeline needs to be loaded. - * - * Registering a function does not add it to a pipeline, functions must still be - * added to instances of the pipeline for them to be used when running a pipeline. - * - * @param {lunr.PipelineFunction} fn - The function to check for. - * @param {String} label - The label to register this function with - */ -lunr.Pipeline.registerFunction = function (fn, label) { - if (label in this.registeredFunctions) { - lunr.utils.warn('Overwriting existing registered function: ' + label) - } - - fn.label = label - lunr.Pipeline.registeredFunctions[fn.label] = fn -} - -/** - * Warns if the function is not registered as a Pipeline function. - * - * @param {lunr.PipelineFunction} fn - The function to check for. - * @private - */ -lunr.Pipeline.warnIfFunctionNotRegistered = function (fn) { - var isRegistered = fn.label && (fn.label in this.registeredFunctions) - - if (!isRegistered) { - lunr.utils.warn('Function is not registered with pipeline. This may cause problems when serialising the index.\n', fn) - } -} - -/** - * Loads a previously serialised pipeline. - * - * All functions to be loaded must already be registered with lunr.Pipeline. - * If any function from the serialised data has not been registered then an - * error will be thrown. - * - * @param {Object} serialised - The serialised pipeline to load. - * @returns {lunr.Pipeline} - */ -lunr.Pipeline.load = function (serialised) { - var pipeline = new lunr.Pipeline - - serialised.forEach(function (fnName) { - var fn = lunr.Pipeline.registeredFunctions[fnName] - - if (fn) { - pipeline.add(fn) - } else { - throw new Error('Cannot load unregistered function: ' + fnName) - } - }) - - return pipeline -} - -/** - * Adds new functions to the end of the pipeline. - * - * Logs a warning if the function has not been registered. - * - * @param {lunr.PipelineFunction[]} functions - Any number of functions to add to the pipeline. - */ -lunr.Pipeline.prototype.add = function () { - var fns = Array.prototype.slice.call(arguments) - - fns.forEach(function (fn) { - lunr.Pipeline.warnIfFunctionNotRegistered(fn) - this._stack.push(fn) - }, this) -} - -/** - * Adds a single function after a function that already exists in the - * pipeline. - * - * Logs a warning if the function has not been registered. - * - * @param {lunr.PipelineFunction} existingFn - A function that already exists in the pipeline. - * @param {lunr.PipelineFunction} newFn - The new function to add to the pipeline. - */ -lunr.Pipeline.prototype.after = function (existingFn, newFn) { - lunr.Pipeline.warnIfFunctionNotRegistered(newFn) - - var pos = this._stack.indexOf(existingFn) - if (pos == -1) { - throw new Error('Cannot find existingFn') - } - - pos = pos + 1 - this._stack.splice(pos, 0, newFn) -} - -/** - * Adds a single function before a function that already exists in the - * pipeline. - * - * Logs a warning if the function has not been registered. - * - * @param {lunr.PipelineFunction} existingFn - A function that already exists in the pipeline. - * @param {lunr.PipelineFunction} newFn - The new function to add to the pipeline. - */ -lunr.Pipeline.prototype.before = function (existingFn, newFn) { - lunr.Pipeline.warnIfFunctionNotRegistered(newFn) - - var pos = this._stack.indexOf(existingFn) - if (pos == -1) { - throw new Error('Cannot find existingFn') - } - - this._stack.splice(pos, 0, newFn) -} - -/** - * Removes a function from the pipeline. - * - * @param {lunr.PipelineFunction} fn The function to remove from the pipeline. - */ -lunr.Pipeline.prototype.remove = function (fn) { - var pos = this._stack.indexOf(fn) - if (pos == -1) { - return - } - - this._stack.splice(pos, 1) -} - -/** - * Runs the current list of functions that make up the pipeline against the - * passed tokens. - * - * @param {Array} tokens The tokens to run through the pipeline. - * @returns {Array} - */ -lunr.Pipeline.prototype.run = function (tokens) { - var stackLength = this._stack.length - - for (var i = 0; i < stackLength; i++) { - var fn = this._stack[i] - var memo = [] - - for (var j = 0; j < tokens.length; j++) { - var result = fn(tokens[j], j, tokens) - - if (result === null || result === void 0 || result === '') continue - - if (Array.isArray(result)) { - for (var k = 0; k < result.length; k++) { - memo.push(result[k]) - } - } else { - memo.push(result) - } - } - - tokens = memo - } - - return tokens -} - -/** - * Convenience method for passing a string through a pipeline and getting - * strings out. This method takes care of wrapping the passed string in a - * token and mapping the resulting tokens back to strings. - * - * @param {string} str - The string to pass through the pipeline. - * @param {?object} metadata - Optional metadata to associate with the token - * passed to the pipeline. - * @returns {string[]} - */ -lunr.Pipeline.prototype.runString = function (str, metadata) { - var token = new lunr.Token (str, metadata) - - return this.run([token]).map(function (t) { - return t.toString() - }) -} - -/** - * Resets the pipeline by removing any existing processors. - * - */ -lunr.Pipeline.prototype.reset = function () { - this._stack = [] -} - -/** - * Returns a representation of the pipeline ready for serialisation. - * - * Logs a warning if the function has not been registered. - * - * @returns {Array} - */ -lunr.Pipeline.prototype.toJSON = function () { - return this._stack.map(function (fn) { - lunr.Pipeline.warnIfFunctionNotRegistered(fn) - - return fn.label - }) -} -/*! - * lunr.Vector - * Copyright (C) 2020 Oliver Nightingale - */ - -/** - * A vector is used to construct the vector space of documents and queries. These - * vectors support operations to determine the similarity between two documents or - * a document and a query. - * - * Normally no parameters are required for initializing a vector, but in the case of - * loading a previously dumped vector the raw elements can be provided to the constructor. - * - * For performance reasons vectors are implemented with a flat array, where an elements - * index is immediately followed by its value. E.g. [index, value, index, value]. This - * allows the underlying array to be as sparse as possible and still offer decent - * performance when being used for vector calculations. - * - * @constructor - * @param {Number[]} [elements] - The flat list of element index and element value pairs. - */ -lunr.Vector = function (elements) { - this._magnitude = 0 - this.elements = elements || [] -} - - -/** - * Calculates the position within the vector to insert a given index. - * - * This is used internally by insert and upsert. If there are duplicate indexes then - * the position is returned as if the value for that index were to be updated, but it - * is the callers responsibility to check whether there is a duplicate at that index - * - * @param {Number} insertIdx - The index at which the element should be inserted. - * @returns {Number} - */ -lunr.Vector.prototype.positionForIndex = function (index) { - // For an empty vector the tuple can be inserted at the beginning - if (this.elements.length == 0) { - return 0 - } - - var start = 0, - end = this.elements.length / 2, - sliceLength = end - start, - pivotPoint = Math.floor(sliceLength / 2), - pivotIndex = this.elements[pivotPoint * 2] - - while (sliceLength > 1) { - if (pivotIndex < index) { - start = pivotPoint - } - - if (pivotIndex > index) { - end = pivotPoint - } - - if (pivotIndex == index) { - break - } - - sliceLength = end - start - pivotPoint = start + Math.floor(sliceLength / 2) - pivotIndex = this.elements[pivotPoint * 2] - } - - if (pivotIndex == index) { - return pivotPoint * 2 - } - - if (pivotIndex > index) { - return pivotPoint * 2 - } - - if (pivotIndex < index) { - return (pivotPoint + 1) * 2 - } -} - -/** - * Inserts an element at an index within the vector. - * - * Does not allow duplicates, will throw an error if there is already an entry - * for this index. - * - * @param {Number} insertIdx - The index at which the element should be inserted. - * @param {Number} val - The value to be inserted into the vector. - */ -lunr.Vector.prototype.insert = function (insertIdx, val) { - this.upsert(insertIdx, val, function () { - throw "duplicate index" - }) -} - -/** - * Inserts or updates an existing index within the vector. - * - * @param {Number} insertIdx - The index at which the element should be inserted. - * @param {Number} val - The value to be inserted into the vector. - * @param {function} fn - A function that is called for updates, the existing value and the - * requested value are passed as arguments - */ -lunr.Vector.prototype.upsert = function (insertIdx, val, fn) { - this._magnitude = 0 - var position = this.positionForIndex(insertIdx) - - if (this.elements[position] == insertIdx) { - this.elements[position + 1] = fn(this.elements[position + 1], val) - } else { - this.elements.splice(position, 0, insertIdx, val) - } -} - -/** - * Calculates the magnitude of this vector. - * - * @returns {Number} - */ -lunr.Vector.prototype.magnitude = function () { - if (this._magnitude) return this._magnitude - - var sumOfSquares = 0, - elementsLength = this.elements.length - - for (var i = 1; i < elementsLength; i += 2) { - var val = this.elements[i] - sumOfSquares += val * val - } - - return this._magnitude = Math.sqrt(sumOfSquares) -} - -/** - * Calculates the dot product of this vector and another vector. - * - * @param {lunr.Vector} otherVector - The vector to compute the dot product with. - * @returns {Number} - */ -lunr.Vector.prototype.dot = function (otherVector) { - var dotProduct = 0, - a = this.elements, b = otherVector.elements, - aLen = a.length, bLen = b.length, - aVal = 0, bVal = 0, - i = 0, j = 0 - - while (i < aLen && j < bLen) { - aVal = a[i], bVal = b[j] - if (aVal < bVal) { - i += 2 - } else if (aVal > bVal) { - j += 2 - } else if (aVal == bVal) { - dotProduct += a[i + 1] * b[j + 1] - i += 2 - j += 2 - } - } - - return dotProduct -} - -/** - * Calculates the similarity between this vector and another vector. - * - * @param {lunr.Vector} otherVector - The other vector to calculate the - * similarity with. - * @returns {Number} - */ -lunr.Vector.prototype.similarity = function (otherVector) { - return this.dot(otherVector) / this.magnitude() || 0 -} - -/** - * Converts the vector to an array of the elements within the vector. - * - * @returns {Number[]} - */ -lunr.Vector.prototype.toArray = function () { - var output = new Array (this.elements.length / 2) - - for (var i = 1, j = 0; i < this.elements.length; i += 2, j++) { - output[j] = this.elements[i] - } - - return output -} - -/** - * A JSON serializable representation of the vector. - * - * @returns {Number[]} - */ -lunr.Vector.prototype.toJSON = function () { - return this.elements -} -/* eslint-disable */ -/*! - * lunr.stemmer - * Copyright (C) 2020 Oliver Nightingale - * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt - */ - -/** - * lunr.stemmer is an english language stemmer, this is a JavaScript - * implementation of the PorterStemmer taken from http://tartarus.org/~martin - * - * @static - * @implements {lunr.PipelineFunction} - * @param {lunr.Token} token - The string to stem - * @returns {lunr.Token} - * @see {@link lunr.Pipeline} - * @function - */ -lunr.stemmer = (function(){ - var step2list = { - "ational" : "ate", - "tional" : "tion", - "enci" : "ence", - "anci" : "ance", - "izer" : "ize", - "bli" : "ble", - "alli" : "al", - "entli" : "ent", - "eli" : "e", - "ousli" : "ous", - "ization" : "ize", - "ation" : "ate", - "ator" : "ate", - "alism" : "al", - "iveness" : "ive", - "fulness" : "ful", - "ousness" : "ous", - "aliti" : "al", - "iviti" : "ive", - "biliti" : "ble", - "logi" : "log" - }, - - step3list = { - "icate" : "ic", - "ative" : "", - "alize" : "al", - "iciti" : "ic", - "ical" : "ic", - "ful" : "", - "ness" : "" - }, - - c = "[^aeiou]", // consonant - v = "[aeiouy]", // vowel - C = c + "[^aeiouy]*", // consonant sequence - V = v + "[aeiou]*", // vowel sequence - - mgr0 = "^(" + C + ")?" + V + C, // [C]VC... is m>0 - meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$", // [C]VC[V] is m=1 - mgr1 = "^(" + C + ")?" + V + C + V + C, // [C]VCVC... is m>1 - s_v = "^(" + C + ")?" + v; // vowel in stem - - var re_mgr0 = new RegExp(mgr0); - var re_mgr1 = new RegExp(mgr1); - var re_meq1 = new RegExp(meq1); - var re_s_v = new RegExp(s_v); - - var re_1a = /^(.+?)(ss|i)es$/; - var re2_1a = /^(.+?)([^s])s$/; - var re_1b = /^(.+?)eed$/; - var re2_1b = /^(.+?)(ed|ing)$/; - var re_1b_2 = /.$/; - var re2_1b_2 = /(at|bl|iz)$/; - var re3_1b_2 = new RegExp("([^aeiouylsz])\\1$"); - var re4_1b_2 = new RegExp("^" + C + v + "[^aeiouwxy]$"); - - var re_1c = /^(.+?[^aeiou])y$/; - var re_2 = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; - - var re_3 = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; - - var re_4 = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; - var re2_4 = /^(.+?)(s|t)(ion)$/; - - var re_5 = /^(.+?)e$/; - var re_5_1 = /ll$/; - var re3_5 = new RegExp("^" + C + v + "[^aeiouwxy]$"); - - var porterStemmer = function porterStemmer(w) { - var stem, - suffix, - firstch, - re, - re2, - re3, - re4; - - if (w.length < 3) { return w; } - - firstch = w.substr(0,1); - if (firstch == "y") { - w = firstch.toUpperCase() + w.substr(1); - } - - // Step 1a - re = re_1a - re2 = re2_1a; - - if (re.test(w)) { w = w.replace(re,"$1$2"); } - else if (re2.test(w)) { w = w.replace(re2,"$1$2"); } - - // Step 1b - re = re_1b; - re2 = re2_1b; - if (re.test(w)) { - var fp = re.exec(w); - re = re_mgr0; - if (re.test(fp[1])) { - re = re_1b_2; - w = w.replace(re,""); - } - } else if (re2.test(w)) { - var fp = re2.exec(w); - stem = fp[1]; - re2 = re_s_v; - if (re2.test(stem)) { - w = stem; - re2 = re2_1b_2; - re3 = re3_1b_2; - re4 = re4_1b_2; - if (re2.test(w)) { w = w + "e"; } - else if (re3.test(w)) { re = re_1b_2; w = w.replace(re,""); } - else if (re4.test(w)) { w = w + "e"; } - } - } - - // Step 1c - replace suffix y or Y by i if preceded by a non-vowel which is not the first letter of the word (so cry -> cri, by -> by, say -> say) - re = re_1c; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - w = stem + "i"; - } - - // Step 2 - re = re_2; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - suffix = fp[2]; - re = re_mgr0; - if (re.test(stem)) { - w = stem + step2list[suffix]; - } - } - - // Step 3 - re = re_3; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - suffix = fp[2]; - re = re_mgr0; - if (re.test(stem)) { - w = stem + step3list[suffix]; - } - } - - // Step 4 - re = re_4; - re2 = re2_4; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - re = re_mgr1; - if (re.test(stem)) { - w = stem; - } - } else if (re2.test(w)) { - var fp = re2.exec(w); - stem = fp[1] + fp[2]; - re2 = re_mgr1; - if (re2.test(stem)) { - w = stem; - } - } - - // Step 5 - re = re_5; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - re = re_mgr1; - re2 = re_meq1; - re3 = re3_5; - if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) { - w = stem; - } - } - - re = re_5_1; - re2 = re_mgr1; - if (re.test(w) && re2.test(w)) { - re = re_1b_2; - w = w.replace(re,""); - } - - // and turn initial Y back to y - - if (firstch == "y") { - w = firstch.toLowerCase() + w.substr(1); - } - - return w; - }; - - return function (token) { - return token.update(porterStemmer); - } -})(); - -lunr.Pipeline.registerFunction(lunr.stemmer, 'stemmer') -/*! - * lunr.stopWordFilter - * Copyright (C) 2020 Oliver Nightingale - */ - -/** - * lunr.generateStopWordFilter builds a stopWordFilter function from the provided - * list of stop words. - * - * The built in lunr.stopWordFilter is built using this generator and can be used - * to generate custom stopWordFilters for applications or non English languages. - * - * @function - * @param {Array} token The token to pass through the filter - * @returns {lunr.PipelineFunction} - * @see lunr.Pipeline - * @see lunr.stopWordFilter - */ -lunr.generateStopWordFilter = function (stopWords) { - var words = stopWords.reduce(function (memo, stopWord) { - memo[stopWord] = stopWord - return memo - }, {}) - - return function (token) { - if (token && words[token.toString()] !== token.toString()) return token - } -} - -/** - * lunr.stopWordFilter is an English language stop word list filter, any words - * contained in the list will not be passed through the filter. - * - * This is intended to be used in the Pipeline. If the token does not pass the - * filter then undefined will be returned. - * - * @function - * @implements {lunr.PipelineFunction} - * @params {lunr.Token} token - A token to check for being a stop word. - * @returns {lunr.Token} - * @see {@link lunr.Pipeline} - */ -lunr.stopWordFilter = lunr.generateStopWordFilter([ - 'a', - 'able', - 'about', - 'across', - 'after', - 'all', - 'almost', - 'also', - 'am', - 'among', - 'an', - 'and', - 'any', - 'are', - 'as', - 'at', - 'be', - 'because', - 'been', - 'but', - 'by', - 'can', - 'cannot', - 'could', - 'dear', - 'did', - 'do', - 'does', - 'either', - 'else', - 'ever', - 'every', - 'for', - 'from', - 'get', - 'got', - 'had', - 'has', - 'have', - 'he', - 'her', - 'hers', - 'him', - 'his', - 'how', - 'however', - 'i', - 'if', - 'in', - 'into', - 'is', - 'it', - 'its', - 'just', - 'least', - 'let', - 'like', - 'likely', - 'may', - 'me', - 'might', - 'most', - 'must', - 'my', - 'neither', - 'no', - 'nor', - 'not', - 'of', - 'off', - 'often', - 'on', - 'only', - 'or', - 'other', - 'our', - 'own', - 'rather', - 'said', - 'say', - 'says', - 'she', - 'should', - 'since', - 'so', - 'some', - 'than', - 'that', - 'the', - 'their', - 'them', - 'then', - 'there', - 'these', - 'they', - 'this', - 'tis', - 'to', - 'too', - 'twas', - 'us', - 'wants', - 'was', - 'we', - 'were', - 'what', - 'when', - 'where', - 'which', - 'while', - 'who', - 'whom', - 'why', - 'will', - 'with', - 'would', - 'yet', - 'you', - 'your' -]) - -lunr.Pipeline.registerFunction(lunr.stopWordFilter, 'stopWordFilter') -/*! - * lunr.trimmer - * Copyright (C) 2020 Oliver Nightingale - */ - -/** - * lunr.trimmer is a pipeline function for trimming non word - * characters from the beginning and end of tokens before they - * enter the index. - * - * This implementation may not work correctly for non latin - * characters and should either be removed or adapted for use - * with languages with non-latin characters. - * - * @static - * @implements {lunr.PipelineFunction} - * @param {lunr.Token} token The token to pass through the filter - * @returns {lunr.Token} - * @see lunr.Pipeline - */ -lunr.trimmer = function (token) { - return token.update(function (s) { - return s.replace(/^\W+/, '').replace(/\W+$/, '') - }) -} - -lunr.Pipeline.registerFunction(lunr.trimmer, 'trimmer') -/*! - * lunr.TokenSet - * Copyright (C) 2020 Oliver Nightingale - */ - -/** - * A token set is used to store the unique list of all tokens - * within an index. Token sets are also used to represent an - * incoming query to the index, this query token set and index - * token set are then intersected to find which tokens to look - * up in the inverted index. - * - * A token set can hold multiple tokens, as in the case of the - * index token set, or it can hold a single token as in the - * case of a simple query token set. - * - * Additionally token sets are used to perform wildcard matching. - * Leading, contained and trailing wildcards are supported, and - * from this edit distance matching can also be provided. - * - * Token sets are implemented as a minimal finite state automata, - * where both common prefixes and suffixes are shared between tokens. - * This helps to reduce the space used for storing the token set. - * - * @constructor - */ -lunr.TokenSet = function () { - this.final = false - this.edges = {} - this.id = lunr.TokenSet._nextId - lunr.TokenSet._nextId += 1 -} - -/** - * Keeps track of the next, auto increment, identifier to assign - * to a new tokenSet. - * - * TokenSets require a unique identifier to be correctly minimised. - * - * @private - */ -lunr.TokenSet._nextId = 1 - -/** - * Creates a TokenSet instance from the given sorted array of words. - * - * @param {String[]} arr - A sorted array of strings to create the set from. - * @returns {lunr.TokenSet} - * @throws Will throw an error if the input array is not sorted. - */ -lunr.TokenSet.fromArray = function (arr) { - var builder = new lunr.TokenSet.Builder - - for (var i = 0, len = arr.length; i < len; i++) { - builder.insert(arr[i]) - } - - builder.finish() - return builder.root -} - -/** - * Creates a token set from a query clause. - * - * @private - * @param {Object} clause - A single clause from lunr.Query. - * @param {string} clause.term - The query clause term. - * @param {number} [clause.editDistance] - The optional edit distance for the term. - * @returns {lunr.TokenSet} - */ -lunr.TokenSet.fromClause = function (clause) { - if ('editDistance' in clause) { - return lunr.TokenSet.fromFuzzyString(clause.term, clause.editDistance) - } else { - return lunr.TokenSet.fromString(clause.term) - } -} - -/** - * Creates a token set representing a single string with a specified - * edit distance. - * - * Insertions, deletions, substitutions and transpositions are each - * treated as an edit distance of 1. - * - * Increasing the allowed edit distance will have a dramatic impact - * on the performance of both creating and intersecting these TokenSets. - * It is advised to keep the edit distance less than 3. - * - * @param {string} str - The string to create the token set from. - * @param {number} editDistance - The allowed edit distance to match. - * @returns {lunr.Vector} - */ -lunr.TokenSet.fromFuzzyString = function (str, editDistance) { - var root = new lunr.TokenSet - - var stack = [{ - node: root, - editsRemaining: editDistance, - str: str - }] - - while (stack.length) { - var frame = stack.pop() - - // no edit - if (frame.str.length > 0) { - var char = frame.str.charAt(0), - noEditNode - - if (char in frame.node.edges) { - noEditNode = frame.node.edges[char] - } else { - noEditNode = new lunr.TokenSet - frame.node.edges[char] = noEditNode - } - - if (frame.str.length == 1) { - noEditNode.final = true - } - - stack.push({ - node: noEditNode, - editsRemaining: frame.editsRemaining, - str: frame.str.slice(1) - }) - } - - if (frame.editsRemaining == 0) { - continue - } - - // insertion - if ("*" in frame.node.edges) { - var insertionNode = frame.node.edges["*"] - } else { - var insertionNode = new lunr.TokenSet - frame.node.edges["*"] = insertionNode - } - - if (frame.str.length == 0) { - insertionNode.final = true - } - - stack.push({ - node: insertionNode, - editsRemaining: frame.editsRemaining - 1, - str: frame.str - }) - - // deletion - // can only do a deletion if we have enough edits remaining - // and if there are characters left to delete in the string - if (frame.str.length > 1) { - stack.push({ - node: frame.node, - editsRemaining: frame.editsRemaining - 1, - str: frame.str.slice(1) - }) - } - - // deletion - // just removing the last character from the str - if (frame.str.length == 1) { - frame.node.final = true - } - - // substitution - // can only do a substitution if we have enough edits remaining - // and if there are characters left to substitute - if (frame.str.length >= 1) { - if ("*" in frame.node.edges) { - var substitutionNode = frame.node.edges["*"] - } else { - var substitutionNode = new lunr.TokenSet - frame.node.edges["*"] = substitutionNode - } - - if (frame.str.length == 1) { - substitutionNode.final = true - } - - stack.push({ - node: substitutionNode, - editsRemaining: frame.editsRemaining - 1, - str: frame.str.slice(1) - }) - } - - // transposition - // can only do a transposition if there are edits remaining - // and there are enough characters to transpose - if (frame.str.length > 1) { - var charA = frame.str.charAt(0), - charB = frame.str.charAt(1), - transposeNode - - if (charB in frame.node.edges) { - transposeNode = frame.node.edges[charB] - } else { - transposeNode = new lunr.TokenSet - frame.node.edges[charB] = transposeNode - } - - if (frame.str.length == 1) { - transposeNode.final = true - } - - stack.push({ - node: transposeNode, - editsRemaining: frame.editsRemaining - 1, - str: charA + frame.str.slice(2) - }) - } - } - - return root -} - -/** - * Creates a TokenSet from a string. - * - * The string may contain one or more wildcard characters (*) - * that will allow wildcard matching when intersecting with - * another TokenSet. - * - * @param {string} str - The string to create a TokenSet from. - * @returns {lunr.TokenSet} - */ -lunr.TokenSet.fromString = function (str) { - var node = new lunr.TokenSet, - root = node - - /* - * Iterates through all characters within the passed string - * appending a node for each character. - * - * When a wildcard character is found then a self - * referencing edge is introduced to continually match - * any number of any characters. - */ - for (var i = 0, len = str.length; i < len; i++) { - var char = str[i], - final = (i == len - 1) - - if (char == "*") { - node.edges[char] = node - node.final = final - - } else { - var next = new lunr.TokenSet - next.final = final - - node.edges[char] = next - node = next - } - } - - return root -} - -/** - * Converts this TokenSet into an array of strings - * contained within the TokenSet. - * - * This is not intended to be used on a TokenSet that - * contains wildcards, in these cases the results are - * undefined and are likely to cause an infinite loop. - * - * @returns {string[]} - */ -lunr.TokenSet.prototype.toArray = function () { - var words = [] - - var stack = [{ - prefix: "", - node: this - }] - - while (stack.length) { - var frame = stack.pop(), - edges = Object.keys(frame.node.edges), - len = edges.length - - if (frame.node.final) { - /* In Safari, at this point the prefix is sometimes corrupted, see: - * https://github.com/olivernn/lunr.js/issues/279 Calling any - * String.prototype method forces Safari to "cast" this string to what - * it's supposed to be, fixing the bug. */ - frame.prefix.charAt(0) - words.push(frame.prefix) - } - - for (var i = 0; i < len; i++) { - var edge = edges[i] - - stack.push({ - prefix: frame.prefix.concat(edge), - node: frame.node.edges[edge] - }) - } - } - - return words -} - -/** - * Generates a string representation of a TokenSet. - * - * This is intended to allow TokenSets to be used as keys - * in objects, largely to aid the construction and minimisation - * of a TokenSet. As such it is not designed to be a human - * friendly representation of the TokenSet. - * - * @returns {string} - */ -lunr.TokenSet.prototype.toString = function () { - // NOTE: Using Object.keys here as this.edges is very likely - // to enter 'hash-mode' with many keys being added - // - // avoiding a for-in loop here as it leads to the function - // being de-optimised (at least in V8). From some simple - // benchmarks the performance is comparable, but allowing - // V8 to optimize may mean easy performance wins in the future. - - if (this._str) { - return this._str - } - - var str = this.final ? '1' : '0', - labels = Object.keys(this.edges).sort(), - len = labels.length - - for (var i = 0; i < len; i++) { - var label = labels[i], - node = this.edges[label] - - str = str + label + node.id - } - - return str -} - -/** - * Returns a new TokenSet that is the intersection of - * this TokenSet and the passed TokenSet. - * - * This intersection will take into account any wildcards - * contained within the TokenSet. - * - * @param {lunr.TokenSet} b - An other TokenSet to intersect with. - * @returns {lunr.TokenSet} - */ -lunr.TokenSet.prototype.intersect = function (b) { - var output = new lunr.TokenSet, - frame = undefined - - var stack = [{ - qNode: b, - output: output, - node: this - }] - - while (stack.length) { - frame = stack.pop() - - // NOTE: As with the #toString method, we are using - // Object.keys and a for loop instead of a for-in loop - // as both of these objects enter 'hash' mode, causing - // the function to be de-optimised in V8 - var qEdges = Object.keys(frame.qNode.edges), - qLen = qEdges.length, - nEdges = Object.keys(frame.node.edges), - nLen = nEdges.length - - for (var q = 0; q < qLen; q++) { - var qEdge = qEdges[q] - - for (var n = 0; n < nLen; n++) { - var nEdge = nEdges[n] - - if (nEdge == qEdge || qEdge == '*') { - var node = frame.node.edges[nEdge], - qNode = frame.qNode.edges[qEdge], - final = node.final && qNode.final, - next = undefined - - if (nEdge in frame.output.edges) { - // an edge already exists for this character - // no need to create a new node, just set the finality - // bit unless this node is already final - next = frame.output.edges[nEdge] - next.final = next.final || final - - } else { - // no edge exists yet, must create one - // set the finality bit and insert it - // into the output - next = new lunr.TokenSet - next.final = final - frame.output.edges[nEdge] = next - } - - stack.push({ - qNode: qNode, - output: next, - node: node - }) - } - } - } - } - - return output -} -lunr.TokenSet.Builder = function () { - this.previousWord = "" - this.root = new lunr.TokenSet - this.uncheckedNodes = [] - this.minimizedNodes = {} -} - -lunr.TokenSet.Builder.prototype.insert = function (word) { - var node, - commonPrefix = 0 - - if (word < this.previousWord) { - throw new Error ("Out of order word insertion") - } - - for (var i = 0; i < word.length && i < this.previousWord.length; i++) { - if (word[i] != this.previousWord[i]) break - commonPrefix++ - } - - this.minimize(commonPrefix) - - if (this.uncheckedNodes.length == 0) { - node = this.root - } else { - node = this.uncheckedNodes[this.uncheckedNodes.length - 1].child - } - - for (var i = commonPrefix; i < word.length; i++) { - var nextNode = new lunr.TokenSet, - char = word[i] - - node.edges[char] = nextNode - - this.uncheckedNodes.push({ - parent: node, - char: char, - child: nextNode - }) - - node = nextNode - } - - node.final = true - this.previousWord = word -} - -lunr.TokenSet.Builder.prototype.finish = function () { - this.minimize(0) -} - -lunr.TokenSet.Builder.prototype.minimize = function (downTo) { - for (var i = this.uncheckedNodes.length - 1; i >= downTo; i--) { - var node = this.uncheckedNodes[i], - childKey = node.child.toString() - - if (childKey in this.minimizedNodes) { - node.parent.edges[node.char] = this.minimizedNodes[childKey] - } else { - // Cache the key for this node since - // we know it can't change anymore - node.child._str = childKey - - this.minimizedNodes[childKey] = node.child - } - - this.uncheckedNodes.pop() - } -} -/*! - * lunr.Index - * Copyright (C) 2020 Oliver Nightingale - */ - -/** - * An index contains the built index of all documents and provides a query interface - * to the index. - * - * Usually instances of lunr.Index will not be created using this constructor, instead - * lunr.Builder should be used to construct new indexes, or lunr.Index.load should be - * used to load previously built and serialized indexes. - * - * @constructor - * @param {Object} attrs - The attributes of the built search index. - * @param {Object} attrs.invertedIndex - An index of term/field to document reference. - * @param {Object} attrs.fieldVectors - Field vectors - * @param {lunr.TokenSet} attrs.tokenSet - An set of all corpus tokens. - * @param {string[]} attrs.fields - The names of indexed document fields. - * @param {lunr.Pipeline} attrs.pipeline - The pipeline to use for search terms. - */ -lunr.Index = function (attrs) { - this.invertedIndex = attrs.invertedIndex - this.fieldVectors = attrs.fieldVectors - this.tokenSet = attrs.tokenSet - this.fields = attrs.fields - this.pipeline = attrs.pipeline -} - -/** - * A result contains details of a document matching a search query. - * @typedef {Object} lunr.Index~Result - * @property {string} ref - The reference of the document this result represents. - * @property {number} score - A number between 0 and 1 representing how similar this document is to the query. - * @property {lunr.MatchData} matchData - Contains metadata about this match including which term(s) caused the match. - */ - -/** - * Although lunr provides the ability to create queries using lunr.Query, it also provides a simple - * query language which itself is parsed into an instance of lunr.Query. - * - * For programmatically building queries it is advised to directly use lunr.Query, the query language - * is best used for human entered text rather than program generated text. - * - * At its simplest queries can just be a single term, e.g. `hello`, multiple terms are also supported - * and will be combined with OR, e.g `hello world` will match documents that contain either 'hello' - * or 'world', though those that contain both will rank higher in the results. - * - * Wildcards can be included in terms to match one or more unspecified characters, these wildcards can - * be inserted anywhere within the term, and more than one wildcard can exist in a single term. Adding - * wildcards will increase the number of documents that will be found but can also have a negative - * impact on query performance, especially with wildcards at the beginning of a term. - * - * Terms can be restricted to specific fields, e.g. `title:hello`, only documents with the term - * hello in the title field will match this query. Using a field not present in the index will lead - * to an error being thrown. - * - * Modifiers can also be added to terms, lunr supports edit distance and boost modifiers on terms. A term - * boost will make documents matching that term score higher, e.g. `foo^5`. Edit distance is also supported - * to provide fuzzy matching, e.g. 'hello~2' will match documents with hello with an edit distance of 2. - * Avoid large values for edit distance to improve query performance. - * - * Each term also supports a presence modifier. By default a term's presence in document is optional, however - * this can be changed to either required or prohibited. For a term's presence to be required in a document the - * term should be prefixed with a '+', e.g. `+foo bar` is a search for documents that must contain 'foo' and - * optionally contain 'bar'. Conversely a leading '-' sets the terms presence to prohibited, i.e. it must not - * appear in a document, e.g. `-foo bar` is a search for documents that do not contain 'foo' but may contain 'bar'. - * - * To escape special characters the backslash character '\' can be used, this allows searches to include - * characters that would normally be considered modifiers, e.g. `foo\~2` will search for a term "foo~2" instead - * of attempting to apply a boost of 2 to the search term "foo". - * - * @typedef {string} lunr.Index~QueryString - * @example Simple single term query - * hello - * @example Multiple term query - * hello world - * @example term scoped to a field - * title:hello - * @example term with a boost of 10 - * hello^10 - * @example term with an edit distance of 2 - * hello~2 - * @example terms with presence modifiers - * -foo +bar baz - */ - -/** - * Performs a search against the index using lunr query syntax. - * - * Results will be returned sorted by their score, the most relevant results - * will be returned first. For details on how the score is calculated, please see - * the {@link https://lunrjs.com/guides/searching.html#scoring|guide}. - * - * For more programmatic querying use lunr.Index#query. - * - * @param {lunr.Index~QueryString} queryString - A string containing a lunr query. - * @throws {lunr.QueryParseError} If the passed query string cannot be parsed. - * @returns {lunr.Index~Result[]} - */ -lunr.Index.prototype.search = function (queryString) { - return this.query(function (query) { - var parser = new lunr.QueryParser(queryString, query) - parser.parse() - }) -} - -/** - * A query builder callback provides a query object to be used to express - * the query to perform on the index. - * - * @callback lunr.Index~queryBuilder - * @param {lunr.Query} query - The query object to build up. - * @this lunr.Query - */ - -/** - * Performs a query against the index using the yielded lunr.Query object. - * - * If performing programmatic queries against the index, this method is preferred - * over lunr.Index#search so as to avoid the additional query parsing overhead. - * - * A query object is yielded to the supplied function which should be used to - * express the query to be run against the index. - * - * Note that although this function takes a callback parameter it is _not_ an - * asynchronous operation, the callback is just yielded a query object to be - * customized. - * - * @param {lunr.Index~queryBuilder} fn - A function that is used to build the query. - * @returns {lunr.Index~Result[]} - */ -lunr.Index.prototype.query = function (fn) { - // for each query clause - // * process terms - // * expand terms from token set - // * find matching documents and metadata - // * get document vectors - // * score documents - - var query = new lunr.Query(this.fields), - matchingFields = Object.create(null), - queryVectors = Object.create(null), - termFieldCache = Object.create(null), - requiredMatches = Object.create(null), - prohibitedMatches = Object.create(null) - - /* - * To support field level boosts a query vector is created per - * field. An empty vector is eagerly created to support negated - * queries. - */ - for (var i = 0; i < this.fields.length; i++) { - queryVectors[this.fields[i]] = new lunr.Vector - } - - fn.call(query, query) - - for (var i = 0; i < query.clauses.length; i++) { - /* - * Unless the pipeline has been disabled for this term, which is - * the case for terms with wildcards, we need to pass the clause - * term through the search pipeline. A pipeline returns an array - * of processed terms. Pipeline functions may expand the passed - * term, which means we may end up performing multiple index lookups - * for a single query term. - */ - var clause = query.clauses[i], - terms = null, - clauseMatches = lunr.Set.empty - - if (clause.usePipeline) { - terms = this.pipeline.runString(clause.term, { - fields: clause.fields - }) - } else { - terms = [clause.term] - } - - for (var m = 0; m < terms.length; m++) { - var term = terms[m] - - /* - * Each term returned from the pipeline needs to use the same query - * clause object, e.g. the same boost and or edit distance. The - * simplest way to do this is to re-use the clause object but mutate - * its term property. - */ - clause.term = term - - /* - * From the term in the clause we create a token set which will then - * be used to intersect the indexes token set to get a list of terms - * to lookup in the inverted index - */ - var termTokenSet = lunr.TokenSet.fromClause(clause), - expandedTerms = this.tokenSet.intersect(termTokenSet).toArray() - - /* - * If a term marked as required does not exist in the tokenSet it is - * impossible for the search to return any matches. We set all the field - * scoped required matches set to empty and stop examining any further - * clauses. - */ - if (expandedTerms.length === 0 && clause.presence === lunr.Query.presence.REQUIRED) { - for (var k = 0; k < clause.fields.length; k++) { - var field = clause.fields[k] - requiredMatches[field] = lunr.Set.empty - } - - break - } - - for (var j = 0; j < expandedTerms.length; j++) { - /* - * For each term get the posting and termIndex, this is required for - * building the query vector. - */ - var expandedTerm = expandedTerms[j], - posting = this.invertedIndex[expandedTerm], - termIndex = posting._index - - for (var k = 0; k < clause.fields.length; k++) { - /* - * For each field that this query term is scoped by (by default - * all fields are in scope) we need to get all the document refs - * that have this term in that field. - * - * The posting is the entry in the invertedIndex for the matching - * term from above. - */ - var field = clause.fields[k], - fieldPosting = posting[field], - matchingDocumentRefs = Object.keys(fieldPosting), - termField = expandedTerm + "/" + field, - matchingDocumentsSet = new lunr.Set(matchingDocumentRefs) - - /* - * if the presence of this term is required ensure that the matching - * documents are added to the set of required matches for this clause. - * - */ - if (clause.presence == lunr.Query.presence.REQUIRED) { - clauseMatches = clauseMatches.union(matchingDocumentsSet) - - if (requiredMatches[field] === undefined) { - requiredMatches[field] = lunr.Set.complete - } - } - - /* - * if the presence of this term is prohibited ensure that the matching - * documents are added to the set of prohibited matches for this field, - * creating that set if it does not yet exist. - */ - if (clause.presence == lunr.Query.presence.PROHIBITED) { - if (prohibitedMatches[field] === undefined) { - prohibitedMatches[field] = lunr.Set.empty - } - - prohibitedMatches[field] = prohibitedMatches[field].union(matchingDocumentsSet) - - /* - * Prohibited matches should not be part of the query vector used for - * similarity scoring and no metadata should be extracted so we continue - * to the next field - */ - continue - } - - /* - * The query field vector is populated using the termIndex found for - * the term and a unit value with the appropriate boost applied. - * Using upsert because there could already be an entry in the vector - * for the term we are working with. In that case we just add the scores - * together. - */ - queryVectors[field].upsert(termIndex, clause.boost, function (a, b) { return a + b }) - - /** - * If we've already seen this term, field combo then we've already collected - * the matching documents and metadata, no need to go through all that again - */ - if (termFieldCache[termField]) { - continue - } - - for (var l = 0; l < matchingDocumentRefs.length; l++) { - /* - * All metadata for this term/field/document triple - * are then extracted and collected into an instance - * of lunr.MatchData ready to be returned in the query - * results - */ - var matchingDocumentRef = matchingDocumentRefs[l], - matchingFieldRef = new lunr.FieldRef (matchingDocumentRef, field), - metadata = fieldPosting[matchingDocumentRef], - fieldMatch - - if ((fieldMatch = matchingFields[matchingFieldRef]) === undefined) { - matchingFields[matchingFieldRef] = new lunr.MatchData (expandedTerm, field, metadata) - } else { - fieldMatch.add(expandedTerm, field, metadata) - } - - } - - termFieldCache[termField] = true - } - } - } - - /** - * If the presence was required we need to update the requiredMatches field sets. - * We do this after all fields for the term have collected their matches because - * the clause terms presence is required in _any_ of the fields not _all_ of the - * fields. - */ - if (clause.presence === lunr.Query.presence.REQUIRED) { - for (var k = 0; k < clause.fields.length; k++) { - var field = clause.fields[k] - requiredMatches[field] = requiredMatches[field].intersect(clauseMatches) - } - } - } - - /** - * Need to combine the field scoped required and prohibited - * matching documents into a global set of required and prohibited - * matches - */ - var allRequiredMatches = lunr.Set.complete, - allProhibitedMatches = lunr.Set.empty - - for (var i = 0; i < this.fields.length; i++) { - var field = this.fields[i] - - if (requiredMatches[field]) { - allRequiredMatches = allRequiredMatches.intersect(requiredMatches[field]) - } - - if (prohibitedMatches[field]) { - allProhibitedMatches = allProhibitedMatches.union(prohibitedMatches[field]) - } - } - - var matchingFieldRefs = Object.keys(matchingFields), - results = [], - matches = Object.create(null) - - /* - * If the query is negated (contains only prohibited terms) - * we need to get _all_ fieldRefs currently existing in the - * index. This is only done when we know that the query is - * entirely prohibited terms to avoid any cost of getting all - * fieldRefs unnecessarily. - * - * Additionally, blank MatchData must be created to correctly - * populate the results. - */ - if (query.isNegated()) { - matchingFieldRefs = Object.keys(this.fieldVectors) - - for (var i = 0; i < matchingFieldRefs.length; i++) { - var matchingFieldRef = matchingFieldRefs[i] - var fieldRef = lunr.FieldRef.fromString(matchingFieldRef) - matchingFields[matchingFieldRef] = new lunr.MatchData - } - } - - for (var i = 0; i < matchingFieldRefs.length; i++) { - /* - * Currently we have document fields that match the query, but we - * need to return documents. The matchData and scores are combined - * from multiple fields belonging to the same document. - * - * Scores are calculated by field, using the query vectors created - * above, and combined into a final document score using addition. - */ - var fieldRef = lunr.FieldRef.fromString(matchingFieldRefs[i]), - docRef = fieldRef.docRef - - if (!allRequiredMatches.contains(docRef)) { - continue - } - - if (allProhibitedMatches.contains(docRef)) { - continue - } - - var fieldVector = this.fieldVectors[fieldRef], - score = queryVectors[fieldRef.fieldName].similarity(fieldVector), - docMatch - - if ((docMatch = matches[docRef]) !== undefined) { - docMatch.score += score - docMatch.matchData.combine(matchingFields[fieldRef]) - } else { - var match = { - ref: docRef, - score: score, - matchData: matchingFields[fieldRef] - } - matches[docRef] = match - results.push(match) - } - } - - /* - * Sort the results objects by score, highest first. - */ - return results.sort(function (a, b) { - return b.score - a.score - }) -} - -/** - * Prepares the index for JSON serialization. - * - * The schema for this JSON blob will be described in a - * separate JSON schema file. - * - * @returns {Object} - */ -lunr.Index.prototype.toJSON = function () { - var invertedIndex = Object.keys(this.invertedIndex) - .sort() - .map(function (term) { - return [term, this.invertedIndex[term]] - }, this) - - var fieldVectors = Object.keys(this.fieldVectors) - .map(function (ref) { - return [ref, this.fieldVectors[ref].toJSON()] - }, this) - - return { - version: lunr.version, - fields: this.fields, - fieldVectors: fieldVectors, - invertedIndex: invertedIndex, - pipeline: this.pipeline.toJSON() - } -} - -/** - * Loads a previously serialized lunr.Index - * - * @param {Object} serializedIndex - A previously serialized lunr.Index - * @returns {lunr.Index} - */ -lunr.Index.load = function (serializedIndex) { - var attrs = {}, - fieldVectors = {}, - serializedVectors = serializedIndex.fieldVectors, - invertedIndex = Object.create(null), - serializedInvertedIndex = serializedIndex.invertedIndex, - tokenSetBuilder = new lunr.TokenSet.Builder, - pipeline = lunr.Pipeline.load(serializedIndex.pipeline) - - if (serializedIndex.version != lunr.version) { - lunr.utils.warn("Version mismatch when loading serialised index. Current version of lunr '" + lunr.version + "' does not match serialized index '" + serializedIndex.version + "'") - } - - for (var i = 0; i < serializedVectors.length; i++) { - var tuple = serializedVectors[i], - ref = tuple[0], - elements = tuple[1] - - fieldVectors[ref] = new lunr.Vector(elements) - } - - for (var i = 0; i < serializedInvertedIndex.length; i++) { - var tuple = serializedInvertedIndex[i], - term = tuple[0], - posting = tuple[1] - - tokenSetBuilder.insert(term) - invertedIndex[term] = posting - } - - tokenSetBuilder.finish() - - attrs.fields = serializedIndex.fields - - attrs.fieldVectors = fieldVectors - attrs.invertedIndex = invertedIndex - attrs.tokenSet = tokenSetBuilder.root - attrs.pipeline = pipeline - - return new lunr.Index(attrs) -} -/*! - * lunr.Builder - * Copyright (C) 2020 Oliver Nightingale - */ - -/** - * lunr.Builder performs indexing on a set of documents and - * returns instances of lunr.Index ready for querying. - * - * All configuration of the index is done via the builder, the - * fields to index, the document reference, the text processing - * pipeline and document scoring parameters are all set on the - * builder before indexing. - * - * @constructor - * @property {string} _ref - Internal reference to the document reference field. - * @property {string[]} _fields - Internal reference to the document fields to index. - * @property {object} invertedIndex - The inverted index maps terms to document fields. - * @property {object} documentTermFrequencies - Keeps track of document term frequencies. - * @property {object} documentLengths - Keeps track of the length of documents added to the index. - * @property {lunr.tokenizer} tokenizer - Function for splitting strings into tokens for indexing. - * @property {lunr.Pipeline} pipeline - The pipeline performs text processing on tokens before indexing. - * @property {lunr.Pipeline} searchPipeline - A pipeline for processing search terms before querying the index. - * @property {number} documentCount - Keeps track of the total number of documents indexed. - * @property {number} _b - A parameter to control field length normalization, setting this to 0 disabled normalization, 1 fully normalizes field lengths, the default value is 0.75. - * @property {number} _k1 - A parameter to control how quickly an increase in term frequency results in term frequency saturation, the default value is 1.2. - * @property {number} termIndex - A counter incremented for each unique term, used to identify a terms position in the vector space. - * @property {array} metadataWhitelist - A list of metadata keys that have been whitelisted for entry in the index. - */ -lunr.Builder = function () { - this._ref = "id" - this._fields = Object.create(null) - this._documents = Object.create(null) - this.invertedIndex = Object.create(null) - this.fieldTermFrequencies = {} - this.fieldLengths = {} - this.tokenizer = lunr.tokenizer - this.pipeline = new lunr.Pipeline - this.searchPipeline = new lunr.Pipeline - this.documentCount = 0 - this._b = 0.75 - this._k1 = 1.2 - this.termIndex = 0 - this.metadataWhitelist = [] -} - -/** - * Sets the document field used as the document reference. Every document must have this field. - * The type of this field in the document should be a string, if it is not a string it will be - * coerced into a string by calling toString. - * - * The default ref is 'id'. - * - * The ref should _not_ be changed during indexing, it should be set before any documents are - * added to the index. Changing it during indexing can lead to inconsistent results. - * - * @param {string} ref - The name of the reference field in the document. - */ -lunr.Builder.prototype.ref = function (ref) { - this._ref = ref -} - -/** - * A function that is used to extract a field from a document. - * - * Lunr expects a field to be at the top level of a document, if however the field - * is deeply nested within a document an extractor function can be used to extract - * the right field for indexing. - * - * @callback fieldExtractor - * @param {object} doc - The document being added to the index. - * @returns {?(string|object|object[])} obj - The object that will be indexed for this field. - * @example Extracting a nested field - * function (doc) { return doc.nested.field } - */ - -/** - * Adds a field to the list of document fields that will be indexed. Every document being - * indexed should have this field. Null values for this field in indexed documents will - * not cause errors but will limit the chance of that document being retrieved by searches. - * - * All fields should be added before adding documents to the index. Adding fields after - * a document has been indexed will have no effect on already indexed documents. - * - * Fields can be boosted at build time. This allows terms within that field to have more - * importance when ranking search results. Use a field boost to specify that matches within - * one field are more important than other fields. - * - * @param {string} fieldName - The name of a field to index in all documents. - * @param {object} attributes - Optional attributes associated with this field. - * @param {number} [attributes.boost=1] - Boost applied to all terms within this field. - * @param {fieldExtractor} [attributes.extractor] - Function to extract a field from a document. - * @throws {RangeError} fieldName cannot contain unsupported characters '/' - */ -lunr.Builder.prototype.field = function (fieldName, attributes) { - if (/\//.test(fieldName)) { - throw new RangeError ("Field '" + fieldName + "' contains illegal character '/'") - } - - this._fields[fieldName] = attributes || {} -} - -/** - * A parameter to tune the amount of field length normalisation that is applied when - * calculating relevance scores. A value of 0 will completely disable any normalisation - * and a value of 1 will fully normalise field lengths. The default is 0.75. Values of b - * will be clamped to the range 0 - 1. - * - * @param {number} number - The value to set for this tuning parameter. - */ -lunr.Builder.prototype.b = function (number) { - if (number < 0) { - this._b = 0 - } else if (number > 1) { - this._b = 1 - } else { - this._b = number - } -} - -/** - * A parameter that controls the speed at which a rise in term frequency results in term - * frequency saturation. The default value is 1.2. Setting this to a higher value will give - * slower saturation levels, a lower value will result in quicker saturation. - * - * @param {number} number - The value to set for this tuning parameter. - */ -lunr.Builder.prototype.k1 = function (number) { - this._k1 = number -} - -/** - * Adds a document to the index. - * - * Before adding fields to the index the index should have been fully setup, with the document - * ref and all fields to index already having been specified. - * - * The document must have a field name as specified by the ref (by default this is 'id') and - * it should have all fields defined for indexing, though null or undefined values will not - * cause errors. - * - * Entire documents can be boosted at build time. Applying a boost to a document indicates that - * this document should rank higher in search results than other documents. - * - * @param {object} doc - The document to add to the index. - * @param {object} attributes - Optional attributes associated with this document. - * @param {number} [attributes.boost=1] - Boost applied to all terms within this document. - */ -lunr.Builder.prototype.add = function (doc, attributes) { - var docRef = doc[this._ref], - fields = Object.keys(this._fields) - - this._documents[docRef] = attributes || {} - this.documentCount += 1 - - for (var i = 0; i < fields.length; i++) { - var fieldName = fields[i], - extractor = this._fields[fieldName].extractor, - field = extractor ? extractor(doc) : doc[fieldName], - tokens = this.tokenizer(field, { - fields: [fieldName] - }), - terms = this.pipeline.run(tokens), - fieldRef = new lunr.FieldRef (docRef, fieldName), - fieldTerms = Object.create(null) - - this.fieldTermFrequencies[fieldRef] = fieldTerms - this.fieldLengths[fieldRef] = 0 - - // store the length of this field for this document - this.fieldLengths[fieldRef] += terms.length - - // calculate term frequencies for this field - for (var j = 0; j < terms.length; j++) { - var term = terms[j] - - if (fieldTerms[term] == undefined) { - fieldTerms[term] = 0 - } - - fieldTerms[term] += 1 - - // add to inverted index - // create an initial posting if one doesn't exist - if (this.invertedIndex[term] == undefined) { - var posting = Object.create(null) - posting["_index"] = this.termIndex - this.termIndex += 1 - - for (var k = 0; k < fields.length; k++) { - posting[fields[k]] = Object.create(null) - } - - this.invertedIndex[term] = posting - } - - // add an entry for this term/fieldName/docRef to the invertedIndex - if (this.invertedIndex[term][fieldName][docRef] == undefined) { - this.invertedIndex[term][fieldName][docRef] = Object.create(null) - } - - // store all whitelisted metadata about this token in the - // inverted index - for (var l = 0; l < this.metadataWhitelist.length; l++) { - var metadataKey = this.metadataWhitelist[l], - metadata = term.metadata[metadataKey] - - if (this.invertedIndex[term][fieldName][docRef][metadataKey] == undefined) { - this.invertedIndex[term][fieldName][docRef][metadataKey] = [] - } - - this.invertedIndex[term][fieldName][docRef][metadataKey].push(metadata) - } - } - - } -} - -/** - * Calculates the average document length for this index - * - * @private - */ -lunr.Builder.prototype.calculateAverageFieldLengths = function () { - - var fieldRefs = Object.keys(this.fieldLengths), - numberOfFields = fieldRefs.length, - accumulator = {}, - documentsWithField = {} - - for (var i = 0; i < numberOfFields; i++) { - var fieldRef = lunr.FieldRef.fromString(fieldRefs[i]), - field = fieldRef.fieldName - - documentsWithField[field] || (documentsWithField[field] = 0) - documentsWithField[field] += 1 - - accumulator[field] || (accumulator[field] = 0) - accumulator[field] += this.fieldLengths[fieldRef] - } - - var fields = Object.keys(this._fields) - - for (var i = 0; i < fields.length; i++) { - var fieldName = fields[i] - accumulator[fieldName] = accumulator[fieldName] / documentsWithField[fieldName] - } - - this.averageFieldLength = accumulator -} - -/** - * Builds a vector space model of every document using lunr.Vector - * - * @private - */ -lunr.Builder.prototype.createFieldVectors = function () { - var fieldVectors = {}, - fieldRefs = Object.keys(this.fieldTermFrequencies), - fieldRefsLength = fieldRefs.length, - termIdfCache = Object.create(null) - - for (var i = 0; i < fieldRefsLength; i++) { - var fieldRef = lunr.FieldRef.fromString(fieldRefs[i]), - fieldName = fieldRef.fieldName, - fieldLength = this.fieldLengths[fieldRef], - fieldVector = new lunr.Vector, - termFrequencies = this.fieldTermFrequencies[fieldRef], - terms = Object.keys(termFrequencies), - termsLength = terms.length - - - var fieldBoost = this._fields[fieldName].boost || 1, - docBoost = this._documents[fieldRef.docRef].boost || 1 - - for (var j = 0; j < termsLength; j++) { - var term = terms[j], - tf = termFrequencies[term], - termIndex = this.invertedIndex[term]._index, - idf, score, scoreWithPrecision - - if (termIdfCache[term] === undefined) { - idf = lunr.idf(this.invertedIndex[term], this.documentCount) - termIdfCache[term] = idf - } else { - idf = termIdfCache[term] - } - - score = idf * ((this._k1 + 1) * tf) / (this._k1 * (1 - this._b + this._b * (fieldLength / this.averageFieldLength[fieldName])) + tf) - score *= fieldBoost - score *= docBoost - scoreWithPrecision = Math.round(score * 1000) / 1000 - // Converts 1.23456789 to 1.234. - // Reducing the precision so that the vectors take up less - // space when serialised. Doing it now so that they behave - // the same before and after serialisation. Also, this is - // the fastest approach to reducing a number's precision in - // JavaScript. - - fieldVector.insert(termIndex, scoreWithPrecision) - } - - fieldVectors[fieldRef] = fieldVector - } - - this.fieldVectors = fieldVectors -} - -/** - * Creates a token set of all tokens in the index using lunr.TokenSet - * - * @private - */ -lunr.Builder.prototype.createTokenSet = function () { - this.tokenSet = lunr.TokenSet.fromArray( - Object.keys(this.invertedIndex).sort() - ) -} - -/** - * Builds the index, creating an instance of lunr.Index. - * - * This completes the indexing process and should only be called - * once all documents have been added to the index. - * - * @returns {lunr.Index} - */ -lunr.Builder.prototype.build = function () { - this.calculateAverageFieldLengths() - this.createFieldVectors() - this.createTokenSet() - - return new lunr.Index({ - invertedIndex: this.invertedIndex, - fieldVectors: this.fieldVectors, - tokenSet: this.tokenSet, - fields: Object.keys(this._fields), - pipeline: this.searchPipeline - }) -} - -/** - * Applies a plugin to the index builder. - * - * A plugin is a function that is called with the index builder as its context. - * Plugins can be used to customise or extend the behaviour of the index - * in some way. A plugin is just a function, that encapsulated the custom - * behaviour that should be applied when building the index. - * - * The plugin function will be called with the index builder as its argument, additional - * arguments can also be passed when calling use. The function will be called - * with the index builder as its context. - * - * @param {Function} plugin The plugin to apply. - */ -lunr.Builder.prototype.use = function (fn) { - var args = Array.prototype.slice.call(arguments, 1) - args.unshift(this) - fn.apply(this, args) -} -/** - * Contains and collects metadata about a matching document. - * A single instance of lunr.MatchData is returned as part of every - * lunr.Index~Result. - * - * @constructor - * @param {string} term - The term this match data is associated with - * @param {string} field - The field in which the term was found - * @param {object} metadata - The metadata recorded about this term in this field - * @property {object} metadata - A cloned collection of metadata associated with this document. - * @see {@link lunr.Index~Result} - */ -lunr.MatchData = function (term, field, metadata) { - var clonedMetadata = Object.create(null), - metadataKeys = Object.keys(metadata || {}) - - // Cloning the metadata to prevent the original - // being mutated during match data combination. - // Metadata is kept in an array within the inverted - // index so cloning the data can be done with - // Array#slice - for (var i = 0; i < metadataKeys.length; i++) { - var key = metadataKeys[i] - clonedMetadata[key] = metadata[key].slice() - } - - this.metadata = Object.create(null) - - if (term !== undefined) { - this.metadata[term] = Object.create(null) - this.metadata[term][field] = clonedMetadata - } -} - -/** - * An instance of lunr.MatchData will be created for every term that matches a - * document. However only one instance is required in a lunr.Index~Result. This - * method combines metadata from another instance of lunr.MatchData with this - * objects metadata. - * - * @param {lunr.MatchData} otherMatchData - Another instance of match data to merge with this one. - * @see {@link lunr.Index~Result} - */ -lunr.MatchData.prototype.combine = function (otherMatchData) { - var terms = Object.keys(otherMatchData.metadata) - - for (var i = 0; i < terms.length; i++) { - var term = terms[i], - fields = Object.keys(otherMatchData.metadata[term]) - - if (this.metadata[term] == undefined) { - this.metadata[term] = Object.create(null) - } - - for (var j = 0; j < fields.length; j++) { - var field = fields[j], - keys = Object.keys(otherMatchData.metadata[term][field]) - - if (this.metadata[term][field] == undefined) { - this.metadata[term][field] = Object.create(null) - } - - for (var k = 0; k < keys.length; k++) { - var key = keys[k] - - if (this.metadata[term][field][key] == undefined) { - this.metadata[term][field][key] = otherMatchData.metadata[term][field][key] - } else { - this.metadata[term][field][key] = this.metadata[term][field][key].concat(otherMatchData.metadata[term][field][key]) - } - - } - } - } -} - -/** - * Add metadata for a term/field pair to this instance of match data. - * - * @param {string} term - The term this match data is associated with - * @param {string} field - The field in which the term was found - * @param {object} metadata - The metadata recorded about this term in this field - */ -lunr.MatchData.prototype.add = function (term, field, metadata) { - if (!(term in this.metadata)) { - this.metadata[term] = Object.create(null) - this.metadata[term][field] = metadata - return - } - - if (!(field in this.metadata[term])) { - this.metadata[term][field] = metadata - return - } - - var metadataKeys = Object.keys(metadata) - - for (var i = 0; i < metadataKeys.length; i++) { - var key = metadataKeys[i] - - if (key in this.metadata[term][field]) { - this.metadata[term][field][key] = this.metadata[term][field][key].concat(metadata[key]) - } else { - this.metadata[term][field][key] = metadata[key] - } - } -} -/** - * A lunr.Query provides a programmatic way of defining queries to be performed - * against a {@link lunr.Index}. - * - * Prefer constructing a lunr.Query using the {@link lunr.Index#query} method - * so the query object is pre-initialized with the right index fields. - * - * @constructor - * @property {lunr.Query~Clause[]} clauses - An array of query clauses. - * @property {string[]} allFields - An array of all available fields in a lunr.Index. - */ -lunr.Query = function (allFields) { - this.clauses = [] - this.allFields = allFields -} - -/** - * Constants for indicating what kind of automatic wildcard insertion will be used when constructing a query clause. - * - * This allows wildcards to be added to the beginning and end of a term without having to manually do any string - * concatenation. - * - * The wildcard constants can be bitwise combined to select both leading and trailing wildcards. - * - * @constant - * @default - * @property {number} wildcard.NONE - The term will have no wildcards inserted, this is the default behaviour - * @property {number} wildcard.LEADING - Prepend the term with a wildcard, unless a leading wildcard already exists - * @property {number} wildcard.TRAILING - Append a wildcard to the term, unless a trailing wildcard already exists - * @see lunr.Query~Clause - * @see lunr.Query#clause - * @see lunr.Query#term - * @example query term with trailing wildcard - * query.term('foo', { wildcard: lunr.Query.wildcard.TRAILING }) - * @example query term with leading and trailing wildcard - * query.term('foo', { - * wildcard: lunr.Query.wildcard.LEADING | lunr.Query.wildcard.TRAILING - * }) - */ - -lunr.Query.wildcard = new String ("*") -lunr.Query.wildcard.NONE = 0 -lunr.Query.wildcard.LEADING = 1 -lunr.Query.wildcard.TRAILING = 2 - -/** - * Constants for indicating what kind of presence a term must have in matching documents. - * - * @constant - * @enum {number} - * @see lunr.Query~Clause - * @see lunr.Query#clause - * @see lunr.Query#term - * @example query term with required presence - * query.term('foo', { presence: lunr.Query.presence.REQUIRED }) - */ -lunr.Query.presence = { - /** - * Term's presence in a document is optional, this is the default value. - */ - OPTIONAL: 1, - - /** - * Term's presence in a document is required, documents that do not contain - * this term will not be returned. - */ - REQUIRED: 2, - - /** - * Term's presence in a document is prohibited, documents that do contain - * this term will not be returned. - */ - PROHIBITED: 3 -} - -/** - * A single clause in a {@link lunr.Query} contains a term and details on how to - * match that term against a {@link lunr.Index}. - * - * @typedef {Object} lunr.Query~Clause - * @property {string[]} fields - The fields in an index this clause should be matched against. - * @property {number} [boost=1] - Any boost that should be applied when matching this clause. - * @property {number} [editDistance] - Whether the term should have fuzzy matching applied, and how fuzzy the match should be. - * @property {boolean} [usePipeline] - Whether the term should be passed through the search pipeline. - * @property {number} [wildcard=lunr.Query.wildcard.NONE] - Whether the term should have wildcards appended or prepended. - * @property {number} [presence=lunr.Query.presence.OPTIONAL] - The terms presence in any matching documents. - */ - -/** - * Adds a {@link lunr.Query~Clause} to this query. - * - * Unless the clause contains the fields to be matched all fields will be matched. In addition - * a default boost of 1 is applied to the clause. - * - * @param {lunr.Query~Clause} clause - The clause to add to this query. - * @see lunr.Query~Clause - * @returns {lunr.Query} - */ -lunr.Query.prototype.clause = function (clause) { - if (!('fields' in clause)) { - clause.fields = this.allFields - } - - if (!('boost' in clause)) { - clause.boost = 1 - } - - if (!('usePipeline' in clause)) { - clause.usePipeline = true - } - - if (!('wildcard' in clause)) { - clause.wildcard = lunr.Query.wildcard.NONE - } - - if ((clause.wildcard & lunr.Query.wildcard.LEADING) && (clause.term.charAt(0) != lunr.Query.wildcard)) { - clause.term = "*" + clause.term - } - - if ((clause.wildcard & lunr.Query.wildcard.TRAILING) && (clause.term.slice(-1) != lunr.Query.wildcard)) { - clause.term = "" + clause.term + "*" - } - - if (!('presence' in clause)) { - clause.presence = lunr.Query.presence.OPTIONAL - } - - this.clauses.push(clause) - - return this -} - -/** - * A negated query is one in which every clause has a presence of - * prohibited. These queries require some special processing to return - * the expected results. - * - * @returns boolean - */ -lunr.Query.prototype.isNegated = function () { - for (var i = 0; i < this.clauses.length; i++) { - if (this.clauses[i].presence != lunr.Query.presence.PROHIBITED) { - return false - } - } - - return true -} - -/** - * Adds a term to the current query, under the covers this will create a {@link lunr.Query~Clause} - * to the list of clauses that make up this query. - * - * The term is used as is, i.e. no tokenization will be performed by this method. Instead conversion - * to a token or token-like string should be done before calling this method. - * - * The term will be converted to a string by calling `toString`. Multiple terms can be passed as an - * array, each term in the array will share the same options. - * - * @param {object|object[]} term - The term(s) to add to the query. - * @param {object} [options] - Any additional properties to add to the query clause. - * @returns {lunr.Query} - * @see lunr.Query#clause - * @see lunr.Query~Clause - * @example adding a single term to a query - * query.term("foo") - * @example adding a single term to a query and specifying search fields, term boost and automatic trailing wildcard - * query.term("foo", { - * fields: ["title"], - * boost: 10, - * wildcard: lunr.Query.wildcard.TRAILING - * }) - * @example using lunr.tokenizer to convert a string to tokens before using them as terms - * query.term(lunr.tokenizer("foo bar")) - */ -lunr.Query.prototype.term = function (term, options) { - if (Array.isArray(term)) { - term.forEach(function (t) { this.term(t, lunr.utils.clone(options)) }, this) - return this - } - - var clause = options || {} - clause.term = term.toString() - - this.clause(clause) - - return this -} -lunr.QueryParseError = function (message, start, end) { - this.name = "QueryParseError" - this.message = message - this.start = start - this.end = end -} - -lunr.QueryParseError.prototype = new Error -lunr.QueryLexer = function (str) { - this.lexemes = [] - this.str = str - this.length = str.length - this.pos = 0 - this.start = 0 - this.escapeCharPositions = [] -} - -lunr.QueryLexer.prototype.run = function () { - var state = lunr.QueryLexer.lexText - - while (state) { - state = state(this) - } -} - -lunr.QueryLexer.prototype.sliceString = function () { - var subSlices = [], - sliceStart = this.start, - sliceEnd = this.pos - - for (var i = 0; i < this.escapeCharPositions.length; i++) { - sliceEnd = this.escapeCharPositions[i] - subSlices.push(this.str.slice(sliceStart, sliceEnd)) - sliceStart = sliceEnd + 1 - } - - subSlices.push(this.str.slice(sliceStart, this.pos)) - this.escapeCharPositions.length = 0 - - return subSlices.join('') -} - -lunr.QueryLexer.prototype.emit = function (type) { - this.lexemes.push({ - type: type, - str: this.sliceString(), - start: this.start, - end: this.pos - }) - - this.start = this.pos -} - -lunr.QueryLexer.prototype.escapeCharacter = function () { - this.escapeCharPositions.push(this.pos - 1) - this.pos += 1 -} - -lunr.QueryLexer.prototype.next = function () { - if (this.pos >= this.length) { - return lunr.QueryLexer.EOS - } - - var char = this.str.charAt(this.pos) - this.pos += 1 - return char -} - -lunr.QueryLexer.prototype.width = function () { - return this.pos - this.start -} - -lunr.QueryLexer.prototype.ignore = function () { - if (this.start == this.pos) { - this.pos += 1 - } - - this.start = this.pos -} - -lunr.QueryLexer.prototype.backup = function () { - this.pos -= 1 -} - -lunr.QueryLexer.prototype.acceptDigitRun = function () { - var char, charCode - - do { - char = this.next() - charCode = char.charCodeAt(0) - } while (charCode > 47 && charCode < 58) - - if (char != lunr.QueryLexer.EOS) { - this.backup() - } -} - -lunr.QueryLexer.prototype.more = function () { - return this.pos < this.length -} - -lunr.QueryLexer.EOS = 'EOS' -lunr.QueryLexer.FIELD = 'FIELD' -lunr.QueryLexer.TERM = 'TERM' -lunr.QueryLexer.EDIT_DISTANCE = 'EDIT_DISTANCE' -lunr.QueryLexer.BOOST = 'BOOST' -lunr.QueryLexer.PRESENCE = 'PRESENCE' - -lunr.QueryLexer.lexField = function (lexer) { - lexer.backup() - lexer.emit(lunr.QueryLexer.FIELD) - lexer.ignore() - return lunr.QueryLexer.lexText -} - -lunr.QueryLexer.lexTerm = function (lexer) { - if (lexer.width() > 1) { - lexer.backup() - lexer.emit(lunr.QueryLexer.TERM) - } - - lexer.ignore() - - if (lexer.more()) { - return lunr.QueryLexer.lexText - } -} - -lunr.QueryLexer.lexEditDistance = function (lexer) { - lexer.ignore() - lexer.acceptDigitRun() - lexer.emit(lunr.QueryLexer.EDIT_DISTANCE) - return lunr.QueryLexer.lexText -} - -lunr.QueryLexer.lexBoost = function (lexer) { - lexer.ignore() - lexer.acceptDigitRun() - lexer.emit(lunr.QueryLexer.BOOST) - return lunr.QueryLexer.lexText -} - -lunr.QueryLexer.lexEOS = function (lexer) { - if (lexer.width() > 0) { - lexer.emit(lunr.QueryLexer.TERM) - } -} - -// This matches the separator used when tokenising fields -// within a document. These should match otherwise it is -// not possible to search for some tokens within a document. -// -// It is possible for the user to change the separator on the -// tokenizer so it _might_ clash with any other of the special -// characters already used within the search string, e.g. :. -// -// This means that it is possible to change the separator in -// such a way that makes some words unsearchable using a search -// string. -lunr.QueryLexer.termSeparator = lunr.tokenizer.separator - -lunr.QueryLexer.lexText = function (lexer) { - while (true) { - var char = lexer.next() - - if (char == lunr.QueryLexer.EOS) { - return lunr.QueryLexer.lexEOS - } - - // Escape character is '\' - if (char.charCodeAt(0) == 92) { - lexer.escapeCharacter() - continue - } - - if (char == ":") { - return lunr.QueryLexer.lexField - } - - if (char == "~") { - lexer.backup() - if (lexer.width() > 0) { - lexer.emit(lunr.QueryLexer.TERM) - } - return lunr.QueryLexer.lexEditDistance - } - - if (char == "^") { - lexer.backup() - if (lexer.width() > 0) { - lexer.emit(lunr.QueryLexer.TERM) - } - return lunr.QueryLexer.lexBoost - } - - // "+" indicates term presence is required - // checking for length to ensure that only - // leading "+" are considered - if (char == "+" && lexer.width() === 1) { - lexer.emit(lunr.QueryLexer.PRESENCE) - return lunr.QueryLexer.lexText - } - - // "-" indicates term presence is prohibited - // checking for length to ensure that only - // leading "-" are considered - if (char == "-" && lexer.width() === 1) { - lexer.emit(lunr.QueryLexer.PRESENCE) - return lunr.QueryLexer.lexText - } - - if (char.match(lunr.QueryLexer.termSeparator)) { - return lunr.QueryLexer.lexTerm - } - } -} - -lunr.QueryParser = function (str, query) { - this.lexer = new lunr.QueryLexer (str) - this.query = query - this.currentClause = {} - this.lexemeIdx = 0 -} - -lunr.QueryParser.prototype.parse = function () { - this.lexer.run() - this.lexemes = this.lexer.lexemes - - var state = lunr.QueryParser.parseClause - - while (state) { - state = state(this) - } - - return this.query -} - -lunr.QueryParser.prototype.peekLexeme = function () { - return this.lexemes[this.lexemeIdx] -} - -lunr.QueryParser.prototype.consumeLexeme = function () { - var lexeme = this.peekLexeme() - this.lexemeIdx += 1 - return lexeme -} - -lunr.QueryParser.prototype.nextClause = function () { - var completedClause = this.currentClause - this.query.clause(completedClause) - this.currentClause = {} -} - -lunr.QueryParser.parseClause = function (parser) { - var lexeme = parser.peekLexeme() - - if (lexeme == undefined) { - return - } - - switch (lexeme.type) { - case lunr.QueryLexer.PRESENCE: - return lunr.QueryParser.parsePresence - case lunr.QueryLexer.FIELD: - return lunr.QueryParser.parseField - case lunr.QueryLexer.TERM: - return lunr.QueryParser.parseTerm - default: - var errorMessage = "expected either a field or a term, found " + lexeme.type - - if (lexeme.str.length >= 1) { - errorMessage += " with value '" + lexeme.str + "'" - } - - throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end) - } -} - -lunr.QueryParser.parsePresence = function (parser) { - var lexeme = parser.consumeLexeme() - - if (lexeme == undefined) { - return - } - - switch (lexeme.str) { - case "-": - parser.currentClause.presence = lunr.Query.presence.PROHIBITED - break - case "+": - parser.currentClause.presence = lunr.Query.presence.REQUIRED - break - default: - var errorMessage = "unrecognised presence operator'" + lexeme.str + "'" - throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end) - } - - var nextLexeme = parser.peekLexeme() - - if (nextLexeme == undefined) { - var errorMessage = "expecting term or field, found nothing" - throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end) - } - - switch (nextLexeme.type) { - case lunr.QueryLexer.FIELD: - return lunr.QueryParser.parseField - case lunr.QueryLexer.TERM: - return lunr.QueryParser.parseTerm - default: - var errorMessage = "expecting term or field, found '" + nextLexeme.type + "'" - throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end) - } -} - -lunr.QueryParser.parseField = function (parser) { - var lexeme = parser.consumeLexeme() - - if (lexeme == undefined) { - return - } - - if (parser.query.allFields.indexOf(lexeme.str) == -1) { - var possibleFields = parser.query.allFields.map(function (f) { return "'" + f + "'" }).join(', '), - errorMessage = "unrecognised field '" + lexeme.str + "', possible fields: " + possibleFields - - throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end) - } - - parser.currentClause.fields = [lexeme.str] - - var nextLexeme = parser.peekLexeme() - - if (nextLexeme == undefined) { - var errorMessage = "expecting term, found nothing" - throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end) - } - - switch (nextLexeme.type) { - case lunr.QueryLexer.TERM: - return lunr.QueryParser.parseTerm - default: - var errorMessage = "expecting term, found '" + nextLexeme.type + "'" - throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end) - } -} - -lunr.QueryParser.parseTerm = function (parser) { - var lexeme = parser.consumeLexeme() - - if (lexeme == undefined) { - return - } - - parser.currentClause.term = lexeme.str.toLowerCase() - - if (lexeme.str.indexOf("*") != -1) { - parser.currentClause.usePipeline = false - } - - var nextLexeme = parser.peekLexeme() - - if (nextLexeme == undefined) { - parser.nextClause() - return - } - - switch (nextLexeme.type) { - case lunr.QueryLexer.TERM: - parser.nextClause() - return lunr.QueryParser.parseTerm - case lunr.QueryLexer.FIELD: - parser.nextClause() - return lunr.QueryParser.parseField - case lunr.QueryLexer.EDIT_DISTANCE: - return lunr.QueryParser.parseEditDistance - case lunr.QueryLexer.BOOST: - return lunr.QueryParser.parseBoost - case lunr.QueryLexer.PRESENCE: - parser.nextClause() - return lunr.QueryParser.parsePresence - default: - var errorMessage = "Unexpected lexeme type '" + nextLexeme.type + "'" - throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end) - } -} - -lunr.QueryParser.parseEditDistance = function (parser) { - var lexeme = parser.consumeLexeme() - - if (lexeme == undefined) { - return - } - - var editDistance = parseInt(lexeme.str, 10) - - if (isNaN(editDistance)) { - var errorMessage = "edit distance must be numeric" - throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end) - } - - parser.currentClause.editDistance = editDistance - - var nextLexeme = parser.peekLexeme() - - if (nextLexeme == undefined) { - parser.nextClause() - return - } - - switch (nextLexeme.type) { - case lunr.QueryLexer.TERM: - parser.nextClause() - return lunr.QueryParser.parseTerm - case lunr.QueryLexer.FIELD: - parser.nextClause() - return lunr.QueryParser.parseField - case lunr.QueryLexer.EDIT_DISTANCE: - return lunr.QueryParser.parseEditDistance - case lunr.QueryLexer.BOOST: - return lunr.QueryParser.parseBoost - case lunr.QueryLexer.PRESENCE: - parser.nextClause() - return lunr.QueryParser.parsePresence - default: - var errorMessage = "Unexpected lexeme type '" + nextLexeme.type + "'" - throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end) - } -} - -lunr.QueryParser.parseBoost = function (parser) { - var lexeme = parser.consumeLexeme() - - if (lexeme == undefined) { - return - } - - var boost = parseInt(lexeme.str, 10) - - if (isNaN(boost)) { - var errorMessage = "boost must be numeric" - throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end) - } - - parser.currentClause.boost = boost - - var nextLexeme = parser.peekLexeme() - - if (nextLexeme == undefined) { - parser.nextClause() - return - } - - switch (nextLexeme.type) { - case lunr.QueryLexer.TERM: - parser.nextClause() - return lunr.QueryParser.parseTerm - case lunr.QueryLexer.FIELD: - parser.nextClause() - return lunr.QueryParser.parseField - case lunr.QueryLexer.EDIT_DISTANCE: - return lunr.QueryParser.parseEditDistance - case lunr.QueryLexer.BOOST: - return lunr.QueryParser.parseBoost - case lunr.QueryLexer.PRESENCE: - parser.nextClause() - return lunr.QueryParser.parsePresence - default: - var errorMessage = "Unexpected lexeme type '" + nextLexeme.type + "'" - throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end) - } -} - - /** - * export the module via AMD, CommonJS or as a browser global - * Export code from https://github.com/umdjs/umd/blob/master/returnExports.js - */ - ;(function (root, factory) { - if (typeof define === 'function' && define.amd) { - // AMD. Register as an anonymous module. - define(factory) - } else if (typeof exports === 'object') { - /** - * Node. Does not work with strict CommonJS, but - * only CommonJS-like enviroments that support module.exports, - * like Node. - */ - module.exports = factory() - } else { - // Browser globals (root is window) - root.lunr = factory() - } - }(this, function () { - /** - * Just return a value to define the module export. - * This example returns an object, but the module - * can return a function as the exported value. - */ - return lunr - })) -})(); diff --git a/assets/js/lunr/lunr.min.js b/assets/js/lunr/lunr.min.js deleted file mode 100644 index cdc94cd3..00000000 --- a/assets/js/lunr/lunr.min.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.9 - * Copyright (C) 2020 Oliver Nightingale - * @license MIT - */ -!function(){var e=function(t){var r=new e.Builder;return r.pipeline.add(e.trimmer,e.stopWordFilter,e.stemmer),r.searchPipeline.add(e.stemmer),t.call(r,r),r.build()};e.version="2.3.9",e.utils={},e.utils.warn=function(e){return function(t){e.console&&console.warn&&console.warn(t)}}(this),e.utils.asString=function(e){return void 0===e||null===e?"":e.toString()},e.utils.clone=function(e){if(null===e||void 0===e)return e;for(var t=Object.create(null),r=Object.keys(e),i=0;i0){var c=e.utils.clone(r)||{};c.position=[a,l],c.index=s.length,s.push(new e.Token(i.slice(a,o),c))}a=o+1}}return s},e.tokenizer.separator=/[\s\-]+/,e.Pipeline=function(){this._stack=[]},e.Pipeline.registeredFunctions=Object.create(null),e.Pipeline.registerFunction=function(t,r){r in this.registeredFunctions&&e.utils.warn("Overwriting existing registered function: "+r),t.label=r,e.Pipeline.registeredFunctions[t.label]=t},e.Pipeline.warnIfFunctionNotRegistered=function(t){var r=t.label&&t.label in this.registeredFunctions;r||e.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",t)},e.Pipeline.load=function(t){var r=new e.Pipeline;return t.forEach(function(t){var i=e.Pipeline.registeredFunctions[t];if(!i)throw new Error("Cannot load unregistered function: "+t);r.add(i)}),r},e.Pipeline.prototype.add=function(){var t=Array.prototype.slice.call(arguments);t.forEach(function(t){e.Pipeline.warnIfFunctionNotRegistered(t),this._stack.push(t)},this)},e.Pipeline.prototype.after=function(t,r){e.Pipeline.warnIfFunctionNotRegistered(r);var i=this._stack.indexOf(t);if(i==-1)throw new Error("Cannot find existingFn");i+=1,this._stack.splice(i,0,r)},e.Pipeline.prototype.before=function(t,r){e.Pipeline.warnIfFunctionNotRegistered(r);var i=this._stack.indexOf(t);if(i==-1)throw new Error("Cannot find existingFn");this._stack.splice(i,0,r)},e.Pipeline.prototype.remove=function(e){var t=this._stack.indexOf(e);t!=-1&&this._stack.splice(t,1)},e.Pipeline.prototype.run=function(e){for(var t=this._stack.length,r=0;r1&&(se&&(r=n),s!=e);)i=r-t,n=t+Math.floor(i/2),s=this.elements[2*n];return s==e?2*n:s>e?2*n:sa?l+=2:o==a&&(t+=r[u+1]*i[l+1],u+=2,l+=2);return t},e.Vector.prototype.similarity=function(e){return this.dot(e)/this.magnitude()||0},e.Vector.prototype.toArray=function(){for(var e=new Array(this.elements.length/2),t=1,r=0;t0){var o,a=s.str.charAt(0);a in s.node.edges?o=s.node.edges[a]:(o=new e.TokenSet,s.node.edges[a]=o),1==s.str.length&&(o["final"]=!0),n.push({node:o,editsRemaining:s.editsRemaining,str:s.str.slice(1)})}if(0!=s.editsRemaining){if("*"in s.node.edges)var u=s.node.edges["*"];else{var u=new e.TokenSet;s.node.edges["*"]=u}if(0==s.str.length&&(u["final"]=!0),n.push({node:u,editsRemaining:s.editsRemaining-1,str:s.str}),s.str.length>1&&n.push({node:s.node,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)}),1==s.str.length&&(s.node["final"]=!0),s.str.length>=1){if("*"in s.node.edges)var l=s.node.edges["*"];else{var l=new e.TokenSet;s.node.edges["*"]=l}1==s.str.length&&(l["final"]=!0),n.push({node:l,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)})}if(s.str.length>1){var c,h=s.str.charAt(0),d=s.str.charAt(1);d in s.node.edges?c=s.node.edges[d]:(c=new e.TokenSet,s.node.edges[d]=c),1==s.str.length&&(c["final"]=!0),n.push({node:c,editsRemaining:s.editsRemaining-1,str:h+s.str.slice(2)})}}}return i},e.TokenSet.fromString=function(t){for(var r=new e.TokenSet,i=r,n=0,s=t.length;n=e;t--){var r=this.uncheckedNodes[t],i=r.child.toString();i in this.minimizedNodes?r.parent.edges[r["char"]]=this.minimizedNodes[i]:(r.child._str=i,this.minimizedNodes[i]=r.child),this.uncheckedNodes.pop()}},e.Index=function(e){this.invertedIndex=e.invertedIndex,this.fieldVectors=e.fieldVectors,this.tokenSet=e.tokenSet,this.fields=e.fields,this.pipeline=e.pipeline},e.Index.prototype.search=function(t){return this.query(function(r){var i=new e.QueryParser(t,r);i.parse()})},e.Index.prototype.query=function(t){for(var r=new e.Query(this.fields),i=Object.create(null),n=Object.create(null),s=Object.create(null),o=Object.create(null),a=Object.create(null),u=0;u1?this._b=1:this._b=e},e.Builder.prototype.k1=function(e){this._k1=e},e.Builder.prototype.add=function(t,r){var i=t[this._ref],n=Object.keys(this._fields);this._documents[i]=r||{},this.documentCount+=1;for(var s=0;s=this.length)return e.QueryLexer.EOS;var t=this.str.charAt(this.pos);return this.pos+=1,t},e.QueryLexer.prototype.width=function(){return this.pos-this.start},e.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},e.QueryLexer.prototype.backup=function(){this.pos-=1},e.QueryLexer.prototype.acceptDigitRun=function(){var t,r;do t=this.next(),r=t.charCodeAt(0);while(r>47&&r<58);t!=e.QueryLexer.EOS&&this.backup()},e.QueryLexer.prototype.more=function(){return this.pos1&&(t.backup(),t.emit(e.QueryLexer.TERM)),t.ignore(),t.more())return e.QueryLexer.lexText},e.QueryLexer.lexEditDistance=function(t){return t.ignore(),t.acceptDigitRun(),t.emit(e.QueryLexer.EDIT_DISTANCE),e.QueryLexer.lexText},e.QueryLexer.lexBoost=function(t){return t.ignore(),t.acceptDigitRun(),t.emit(e.QueryLexer.BOOST),e.QueryLexer.lexText},e.QueryLexer.lexEOS=function(t){t.width()>0&&t.emit(e.QueryLexer.TERM)},e.QueryLexer.termSeparator=e.tokenizer.separator,e.QueryLexer.lexText=function(t){for(;;){var r=t.next();if(r==e.QueryLexer.EOS)return e.QueryLexer.lexEOS;if(92!=r.charCodeAt(0)){if(":"==r)return e.QueryLexer.lexField;if("~"==r)return t.backup(),t.width()>0&&t.emit(e.QueryLexer.TERM),e.QueryLexer.lexEditDistance;if("^"==r)return t.backup(),t.width()>0&&t.emit(e.QueryLexer.TERM),e.QueryLexer.lexBoost;if("+"==r&&1===t.width())return t.emit(e.QueryLexer.PRESENCE),e.QueryLexer.lexText;if("-"==r&&1===t.width())return t.emit(e.QueryLexer.PRESENCE),e.QueryLexer.lexText;if(r.match(e.QueryLexer.termSeparator))return e.QueryLexer.lexTerm}else t.escapeCharacter()}},e.QueryParser=function(t,r){this.lexer=new e.QueryLexer(t),this.query=r,this.currentClause={},this.lexemeIdx=0},e.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var t=e.QueryParser.parseClause;t;)t=t(this);return this.query},e.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},e.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},e.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},e.QueryParser.parseClause=function(t){var r=t.peekLexeme();if(void 0!=r)switch(r.type){case e.QueryLexer.PRESENCE:return e.QueryParser.parsePresence;case e.QueryLexer.FIELD:return e.QueryParser.parseField;case e.QueryLexer.TERM:return e.QueryParser.parseTerm;default:var i="expected either a field or a term, found "+r.type;throw r.str.length>=1&&(i+=" with value '"+r.str+"'"),new e.QueryParseError(i,r.start,r.end)}},e.QueryParser.parsePresence=function(t){var r=t.consumeLexeme();if(void 0!=r){switch(r.str){case"-":t.currentClause.presence=e.Query.presence.PROHIBITED;break;case"+":t.currentClause.presence=e.Query.presence.REQUIRED;break;default:var i="unrecognised presence operator'"+r.str+"'";throw new e.QueryParseError(i,r.start,r.end)}var n=t.peekLexeme();if(void 0==n){var i="expecting term or field, found nothing";throw new e.QueryParseError(i,r.start,r.end)}switch(n.type){case e.QueryLexer.FIELD:return e.QueryParser.parseField;case e.QueryLexer.TERM:return e.QueryParser.parseTerm;default:var i="expecting term or field, found '"+n.type+"'";throw new e.QueryParseError(i,n.start,n.end)}}},e.QueryParser.parseField=function(t){var r=t.consumeLexeme();if(void 0!=r){if(t.query.allFields.indexOf(r.str)==-1){var i=t.query.allFields.map(function(e){return"'"+e+"'"}).join(", "),n="unrecognised field '"+r.str+"', possible fields: "+i;throw new e.QueryParseError(n,r.start,r.end)}t.currentClause.fields=[r.str];var s=t.peekLexeme();if(void 0==s){var n="expecting term, found nothing";throw new e.QueryParseError(n,r.start,r.end)}switch(s.type){case e.QueryLexer.TERM:return e.QueryParser.parseTerm;default:var n="expecting term, found '"+s.type+"'";throw new e.QueryParseError(n,s.start,s.end)}}},e.QueryParser.parseTerm=function(t){var r=t.consumeLexeme();if(void 0!=r){t.currentClause.term=r.str.toLowerCase(),r.str.indexOf("*")!=-1&&(t.currentClause.usePipeline=!1);var i=t.peekLexeme();if(void 0==i)return void t.nextClause();switch(i.type){case e.QueryLexer.TERM:return t.nextClause(),e.QueryParser.parseTerm;case e.QueryLexer.FIELD:return t.nextClause(),e.QueryParser.parseField;case e.QueryLexer.EDIT_DISTANCE:return e.QueryParser.parseEditDistance;case e.QueryLexer.BOOST:return e.QueryParser.parseBoost;case e.QueryLexer.PRESENCE:return t.nextClause(),e.QueryParser.parsePresence;default:var n="Unexpected lexeme type '"+i.type+"'";throw new e.QueryParseError(n,i.start,i.end)}}},e.QueryParser.parseEditDistance=function(t){var r=t.consumeLexeme();if(void 0!=r){var i=parseInt(r.str,10);if(isNaN(i)){var n="edit distance must be numeric";throw new e.QueryParseError(n,r.start,r.end)}t.currentClause.editDistance=i;var s=t.peekLexeme();if(void 0==s)return void t.nextClause();switch(s.type){case e.QueryLexer.TERM:return t.nextClause(),e.QueryParser.parseTerm;case e.QueryLexer.FIELD:return t.nextClause(),e.QueryParser.parseField;case e.QueryLexer.EDIT_DISTANCE:return e.QueryParser.parseEditDistance;case e.QueryLexer.BOOST:return e.QueryParser.parseBoost;case e.QueryLexer.PRESENCE:return t.nextClause(),e.QueryParser.parsePresence;default:var n="Unexpected lexeme type '"+s.type+"'";throw new e.QueryParseError(n,s.start,s.end)}}},e.QueryParser.parseBoost=function(t){var r=t.consumeLexeme();if(void 0!=r){var i=parseInt(r.str,10);if(isNaN(i)){var n="boost must be numeric";throw new e.QueryParseError(n,r.start,r.end)}t.currentClause.boost=i;var s=t.peekLexeme();if(void 0==s)return void t.nextClause();switch(s.type){case e.QueryLexer.TERM:return t.nextClause(),e.QueryParser.parseTerm;case e.QueryLexer.FIELD:return t.nextClause(),e.QueryParser.parseField;case e.QueryLexer.EDIT_DISTANCE:return e.QueryParser.parseEditDistance;case e.QueryLexer.BOOST:return e.QueryParser.parseBoost;case e.QueryLexer.PRESENCE:return t.nextClause(),e.QueryParser.parsePresence;default:var n="Unexpected lexeme type '"+s.type+"'";throw new e.QueryParseError(n,s.start,s.end)}}},function(e,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():e.lunr=t()}(this,function(){return e})}(); diff --git a/assets/js/main.min.js b/assets/js/main.min.js deleted file mode 100644 index 0b5c5998..00000000 --- a/assets/js/main.min.js +++ /dev/null @@ -1,6 +0,0 @@ -/*! - * Minimal Mistakes Jekyll Theme 4.24.0 by Michael Rose - * Copyright 2013-2021 Michael Rose - mademistakes.com | @mmistakes - * Licensed under MIT - */ -!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";function m(e){return null!=e&&e===e.window}var t=[],n=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},l=t.push,o=t.indexOf,r={},i=r.toString,v=r.hasOwnProperty,a=v.toString,u=a.call(Object),y={},b=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},T=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function x(e,t,n){var r,o,i=(n=n||T).createElement("script");if(i.text=e,t)for(r in c)(o=t[r]||t.getAttribute&&t.getAttribute(r))&&i.setAttribute(r,o);n.head.appendChild(i).parentNode.removeChild(i)}function h(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?r[i.call(e)]||"object":typeof e}var f="3.6.0",E=function(e,t){return new E.fn.init(e,t)};function d(e){var t=!!e&&"length"in e&&e.length,n=h(e);return!b(e)&&!m(e)&&("array"===n||0===t||"number"==typeof t&&0>10|55296,1023&e|56320))}function r(){C()}var e,d,x,i,o,p,h,m,w,l,u,C,T,a,E,g,s,c,v,S="sizzle"+ +new Date,y=n.document,k=0,b=0,A=le(),N=le(),j=le(),I=le(),L=function(e,t){return e===t&&(u=!0),0},D={}.hasOwnProperty,t=[],O=t.pop,H=t.push,P=t.push,q=t.slice,M=function(e,t){for(var n=0,r=e.length;n+~]|"+$+")"+$+"*"),Q=new RegExp($+"|>"),Y=new RegExp(F),V=new RegExp("^"+R+"$"),G={ID:new RegExp("^#("+R+")"),CLASS:new RegExp("^\\.("+R+")"),TAG:new RegExp("^("+R+"|[*])"),ATTR:new RegExp("^"+B),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+$+"*(even|odd|(([+-]|)(\\d*)n|)"+$+"*(?:([+-]|)"+$+"*(\\d+)|))"+$+"*\\)|)","i"),bool:new RegExp("^(?:"+_+")$","i"),needsContext:new RegExp("^"+$+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+$+"*((?:-\\d)?\\d*)"+$+"*\\)|)(?=[^-]|$)","i")},K=/HTML$/i,Z=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,ee=/^[^{]+\{\s*\[native \w/,te=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ne=/[+~]/,re=new RegExp("\\\\[\\da-fA-F]{1,6}"+$+"?|\\\\([^\\r\\n\\f])","g"),oe=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"�":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},ae=ye(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{P.apply(t=q.call(y.childNodes),y.childNodes),t[y.childNodes.length].nodeType}catch(e){P={apply:t.length?function(e,t){H.apply(e,q.call(t))}:function(e,t){for(var n=e.length,r=0;e[n++]=t[r++];);e.length=n-1}}}function se(t,e,n,r){var o,i,a,s,l,u,c=e&&e.ownerDocument,f=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==f&&9!==f&&11!==f)return n;if(!r&&(C(e),e=e||T,E)){if(11!==f&&(s=te.exec(t)))if(u=s[1]){if(9===f){if(!(i=e.getElementById(u)))return n;if(i.id===u)return n.push(i),n}else if(c&&(i=c.getElementById(u))&&v(e,i)&&i.id===u)return n.push(i),n}else{if(s[2])return P.apply(n,e.getElementsByTagName(t)),n;if((u=s[3])&&d.getElementsByClassName&&e.getElementsByClassName)return P.apply(n,e.getElementsByClassName(u)),n}if(d.qsa&&!I[t+" "]&&(!g||!g.test(t))&&(1!==f||"object"!==e.nodeName.toLowerCase())){if(u=t,c=e,1===f&&(Q.test(t)||X.test(t))){for((c=ne.test(t)&&me(e.parentNode)||e)===e&&d.scope||((a=e.getAttribute("id"))?a=a.replace(oe,ie):e.setAttribute("id",a=S)),o=(l=p(t)).length;o--;)l[o]=(a?"#"+a:":scope")+" "+ve(l[o]);u=l.join(",")}try{return P.apply(n,c.querySelectorAll(u)),n}catch(e){I(t,!0)}finally{a===S&&e.removeAttribute("id")}}}return m(t.replace(z,"$1"),e,n,r)}function le(){var n=[];function r(e,t){return n.push(e+" ")>x.cacheLength&&delete r[n.shift()],r[e+" "]=t}return r}function ue(e){return e[S]=!0,e}function ce(e){var t=T.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){for(var n=e.split("|"),r=n.length;r--;)x.attrHandle[n[r]]=t}function de(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)for(;n=n.nextSibling;)if(n===t)return-1;return e?1:-1}function pe(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function he(a){return ue(function(i){return i=+i,ue(function(e,t){for(var n,r=a([],e.length,i),o=r.length;o--;)e[n=r[o]]&&(e[n]=!(t[n]=e[n]))})})}function me(e){return e&&void 0!==e.getElementsByTagName&&e}for(e in d=se.support={},o=se.isXML=function(e){var t=e&&e.namespaceURI,e=e&&(e.ownerDocument||e).documentElement;return!K.test(t||e&&e.nodeName||"HTML")},C=se.setDocument=function(e){var t,e=e?e.ownerDocument||e:y;return e!=T&&9===e.nodeType&&e.documentElement&&(a=(T=e).documentElement,E=!o(T),y!=T&&(t=T.defaultView)&&t.top!==t&&(t.addEventListener?t.addEventListener("unload",r,!1):t.attachEvent&&t.attachEvent("onunload",r)),d.scope=ce(function(e){return a.appendChild(e).appendChild(T.createElement("div")),void 0!==e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(T.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=ee.test(T.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!T.getElementsByName||!T.getElementsByName(S).length}),d.getById?(x.filter.ID=function(e){var t=e.replace(re,f);return function(e){return e.getAttribute("id")===t}},x.find.ID=function(e,t){if(void 0!==t.getElementById&&E){e=t.getElementById(e);return e?[e]:[]}}):(x.filter.ID=function(e){var t=e.replace(re,f);return function(e){e=void 0!==e.getAttributeNode&&e.getAttributeNode("id");return e&&e.value===t}},x.find.ID=function(e,t){if(void 0!==t.getElementById&&E){var n,r,o,i=t.getElementById(e);if(i){if((n=i.getAttributeNode("id"))&&n.value===e)return[i];for(o=t.getElementsByName(e),r=0;i=o[r++];)if((n=i.getAttributeNode("id"))&&n.value===e)return[i]}return[]}}),x.find.TAG=d.getElementsByTagName?function(e,t){return void 0!==t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],o=0,i=t.getElementsByTagName(e);if("*"!==e)return i;for(;n=i[o++];)1===n.nodeType&&r.push(n);return r},x.find.CLASS=d.getElementsByClassName&&function(e,t){if(void 0!==t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],g=[],(d.qsa=ee.test(T.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&g.push("[*^$]="+$+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||g.push("\\["+$+"*(?:value|"+_+")"),e.querySelectorAll("[id~="+S+"-]").length||g.push("~="),(t=T.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||g.push("\\["+$+"*name"+$+"*="+$+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||g.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||g.push(".#.+[+~]"),e.querySelectorAll("\\\f"),g.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=T.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&g.push("name"+$+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&g.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&g.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),g.push(",.*:")})),(d.matchesSelector=ee.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),g=g.length&&new RegExp(g.join("|")),s=s.length&&new RegExp(s.join("|")),t=ee.test(a.compareDocumentPosition),v=t||ee.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,t=t&&t.parentNode;return e===t||!(!t||1!==t.nodeType||!(n.contains?n.contains(t):e.compareDocumentPosition&&16&e.compareDocumentPosition(t)))}:function(e,t){if(t)for(;t=t.parentNode;)if(t===e)return!0;return!1},L=t?function(e,t){if(e===t)return u=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==T||e.ownerDocument==y&&v(y,e)?-1:t==T||t.ownerDocument==y&&v(y,t)?1:l?M(l,e)-M(l,t):0:4&n?-1:1)}:function(e,t){if(e===t)return u=!0,0;var n,r=0,o=e.parentNode,i=t.parentNode,a=[e],s=[t];if(!o||!i)return e==T?-1:t==T?1:o?-1:i?1:l?M(l,e)-M(l,t):0;if(o===i)return de(e,t);for(n=e;n=n.parentNode;)a.unshift(n);for(n=t;n=n.parentNode;)s.unshift(n);for(;a[r]===s[r];)r++;return r?de(a[r],s[r]):a[r]==y?-1:s[r]==y?1:0}),T},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(C(e),d.matchesSelector&&E&&!I[t+" "]&&(!s||!s.test(t))&&(!g||!g.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){I(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(re,f),e[3]=(e[3]||e[4]||e[5]||"").replace(re,f),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&Y.test(n)&&(t=p(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(re,f).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=A[e+" "];return t||(t=new RegExp("(^|"+$+")"+e+"("+$+"|$)"))&&A(e,function(e){return t.test("string"==typeof e.className&&e.className||void 0!==e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(t,n,r){return function(e){e=se.attr(e,t);return null==e?"!="===n:!n||(e+="","="===n?e===r:"!="===n?e!==r:"^="===n?r&&0===e.indexOf(r):"*="===n?r&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return b(n)?E.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?E.grep(e,function(e){return e===n!==r}):"string"!=typeof n?E.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(E.fn.init=function(e,t,n){if(!e)return this;if(n=n||L,"string"!=typeof e)return e.nodeType?(this[0]=e,this.length=1,this):b(e)?void 0!==n.ready?n.ready(e):e(E):E.makeArray(e,this);if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:I.exec(e))||!r[1]&&t)return(!t||t.jquery?t||n:this.constructor(t)).find(e);if(r[1]){if(t=t instanceof E?t[0]:t,E.merge(this,E.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:T,!0)),N.test(r[1])&&E.isPlainObject(t))for(var r in t)b(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(e=T.getElementById(r[2]))&&(this[0]=e,this.length=1),this}).prototype=E.fn;var L=E(T),D=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};function H(e,t){for(;(e=e[t])&&1!==e.nodeType;);return e}E.fn.extend({has:function(e){var t=E(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,de=/^$|^module$|\/(?:java|ecma)script/i;f=T.createDocumentFragment().appendChild(T.createElement("div")),(p=T.createElement("input")).setAttribute("type","radio"),p.setAttribute("checked","checked"),p.setAttribute("name","t"),f.appendChild(p),y.checkClone=f.cloneNode(!0).cloneNode(!0).lastChild.checked,f.innerHTML="",y.noCloneChecked=!!f.cloneNode(!0).lastChild.defaultValue,f.innerHTML="",y.option=!!f.lastChild;var pe={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function he(e,t){var n=void 0!==e.getElementsByTagName?e.getElementsByTagName(t||"*"):void 0!==e.querySelectorAll?e.querySelectorAll(t||"*"):[];return void 0===t||t&&A(e,t)?E.merge([e],n):n}function me(e,t){for(var n=0,r=e.length;n",""]);var ge=/<|&#?\w+;/;function ve(e,t,n,r,o){for(var i,a,s,l,u,c=t.createDocumentFragment(),f=[],d=0,p=e.length;d\s*$/g;function Ae(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&E(e).children("tbody")[0]||e}function Ne(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function je(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Ie(e,t){var n,r,o,i;if(1===t.nodeType){if(V.hasData(e)&&(i=V.get(e).events))for(o in V.remove(t,"handle events"),i)for(n=0,r=i[o].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",o=function(e){r.remove(),o=null,e&&t("error"===e.type?404:200,e.type)}),T.head.appendChild(r[0])},abort:function(){o&&o()}}});var Yt=[],Vt=/(=)\?(?=&|$)|\?\?/;E.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Yt.pop()||E.expando+"_"+At.guid++;return this[e]=!0,e}}),E.ajaxPrefilter("json jsonp",function(e,t,n){var r,o,i,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=b(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Nt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return i||E.error(r+" was not called"),i[0]},e.dataTypes[0]="json",o=C[r],C[r]=function(){i=arguments},n.always(function(){void 0===o?E(C).removeProp(r):C[r]=o,e[r]&&(e.jsonpCallback=t.jsonpCallback,Yt.push(r)),i&&b(o)&&o(i[0]),i=o=void 0}),"script"}),y.createHTMLDocument=((f=T.implementation.createHTMLDocument("").body).innerHTML="
",2===f.childNodes.length),E.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=T.implementation.createHTMLDocument("")).createElement("base")).href=T.location.href,t.head.appendChild(r)):t=T),r=!n&&[],(n=N.exec(e))?[t.createElement(n[1])]:(n=ve([e],t,r),r&&r.length&&E(r).remove(),E.merge([],n.childNodes)));var r},E.fn.load=function(e,t,n){var r,o,i,a=this,s=e.indexOf(" ");return-1").append(E.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,i||[e.responseText,t,e])})}),this},E.expr.pseudos.animated=function(t){return E.grep(E.timers,function(e){return t===e.elem}).length},E.offset={setOffset:function(e,t,n){var r,o,i,a,s=E.css(e,"position"),l=E(e),u={};"static"===s&&(e.style.position="relative"),i=l.offset(),r=E.css(e,"top"),a=E.css(e,"left"),a=("absolute"===s||"fixed"===s)&&-1<(r+a).indexOf("auto")?(o=(s=l.position()).top,s.left):(o=parseFloat(r)||0,parseFloat(a)||0),null!=(t=b(t)?t.call(e,n,E.extend({},i)):t).top&&(u.top=t.top-i.top+o),null!=t.left&&(u.left=t.left-i.left+a),"using"in t?t.using.call(e,u):l.css(u)}},E.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){E.offset.setOffset(this,t,e)});var e,n=this[0];return n?n.getClientRects().length?(e=n.getBoundingClientRect(),n=n.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],o={top:0,left:0};if("fixed"===E.css(r,"position"))t=r.getBoundingClientRect();else{for(t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;e&&(e===n.body||e===n.documentElement)&&"static"===E.css(e,"position");)e=e.parentNode;e&&e!==r&&1===e.nodeType&&((o=E(e).offset()).top+=E.css(e,"borderTopWidth",!0),o.left+=E.css(e,"borderLeftWidth",!0))}return{top:t.top-o.top-E.css(r,"marginTop",!0),left:t.left-o.left-E.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){for(var e=this.offsetParent;e&&"static"===E.css(e,"position");)e=e.offsetParent;return e||re})}}),E.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,o){var i="pageYOffset"===o;E.fn[t]=function(e){return F(this,function(e,t,n){var r;return m(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n?r?r[o]:e[t]:void(r?r.scrollTo(i?r.pageXOffset:n,i?n:r.pageYOffset):e[t]=n)},t,e,arguments.length)}}),E.each(["top","left"],function(e,n){E.cssHooks[n]=Ye(y.pixelPosition,function(e,t){if(t)return t=Qe(e,n),Fe.test(t)?E(e).position()[n]+"px":t})}),E.each({Height:"height",Width:"width"},function(a,s){E.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,i){E.fn[i]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),o=r||(!0===e||!0===t?"margin":"border");return F(this,function(e,t,n){var r;return m(e)?0===i.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?E.css(e,t,o):E.style(e,t,n,o)},s,n?e:void 0,n)}})}),E.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){E.fn[t]=function(e){return this.on(t,e)}}),E.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),E.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){E.fn[n]=function(e,t){return 0x

',t.appendChild(n.childNodes[1])),e&&i.extend(o,e),this.each(function(){var e=['iframe[src*="player.vimeo.com"]','iframe[src*="youtube.com"]','iframe[src*="youtube-nocookie.com"]','iframe[src*="kickstarter.com"][src*="video.html"]',"object","embed"];o.customSelector&&e.push(o.customSelector);var r=".fitvidsignore";o.ignore&&(r=r+", "+o.ignore);e=i(this).find(e.join(","));(e=(e=e.not("object object")).not(r)).each(function(e){var t,n=i(this);0
').parent(".fluid-width-video-wrapper").css("padding-top",100*t+"%"),n.removeAttr("height").removeAttr("width"))})})}}(window.jQuery||window.Zepto),$(function(){var n,r,e,o,t=$("nav.greedy-nav .greedy-nav__toggle"),i=$("nav.greedy-nav .visible-links"),a=$("nav.greedy-nav .hidden-links"),s=$("nav.greedy-nav"),l=$("nav.greedy-nav .site-logo"),u=$("nav.greedy-nav .site-logo img"),c=$("nav.greedy-nav .site-title"),f=$("nav.greedy-nav button.search__toggle");function d(){function t(e,t){r+=t,n+=1,o.push(r)}r=n=0,e=1e3,o=[],i.children().outerWidth(t),a.children().each(function(){var e;(e=(e=$(this)).clone()).css("visibility","hidden"),i.append(e),t(0,e.outerWidth()),e.remove()})}d();var p,h,m,g,v=$(window).width(),y=v<768?0:v<1024?1:v<1280?2:3;function b(){var e=(v=$(window).width())<768?0:v<1024?1:v<1280?2:3;e!==y&&d(),y=e,h=i.children().length,p=s.innerWidth()-(0!==l.length?l.outerWidth(!0):0)-c.outerWidth(!0)-(0!==f.length?f.outerWidth(!0):0)-(h!==o.length?t.outerWidth(!0):0),m=o[h-1],po[h]&&(a.children().first().appendTo(i),h+=1,b()),t.attr("count",n-h),h===n?t.addClass("hidden"):t.removeClass("hidden")}$(window).resize(function(){b()}),t.on("click",function(){a.toggleClass("hidden"),$(this).toggleClass("close"),clearTimeout(g)}),a.on("mouseleave",function(){g=setTimeout(function(){a.addClass("hidden")},e)}).on("mouseenter",function(){clearTimeout(g)}),0===u.length||u[0].complete||0!==u[0].naturalWidth?b():u.one("load error",b)}),function(e){"function"==typeof define&&define.amd?define(["jquery"],e):"object"==typeof exports?e(require("jquery")):e(window.jQuery||window.Zepto)}(function(u){function e(){}function c(e,t){h.ev.on("mfp"+e+x,t)}function f(e,t,n,r){var o=document.createElement("div");return o.className="mfp-"+e,n&&(o.innerHTML=n),r?t&&t.appendChild(o):(o=u(o),t&&o.appendTo(t)),o}function d(e,t){h.ev.triggerHandler("mfp"+e,t),h.st.callbacks&&(e=e.charAt(0).toLowerCase()+e.slice(1),h.st.callbacks[e]&&h.st.callbacks[e].apply(h,u.isArray(t)?t:[t]))}function p(e){return e===t&&h.currTemplate.closeBtn||(h.currTemplate.closeBtn=u(h.st.closeMarkup.replace("%title%",h.st.tClose)),t=e),h.currTemplate.closeBtn}function i(){u.magnificPopup.instance||((h=new e).init(),u.magnificPopup.instance=h)}var h,r,m,o,g,t,l="Close",v="BeforeClose",y="MarkupParse",b="Open",x=".mfp",w="mfp-ready",n="mfp-removing",a="mfp-prevent-close",s=!!window.jQuery,C=u(window);e.prototype={constructor:e,init:function(){var e=navigator.appVersion;h.isLowIE=h.isIE8=document.all&&!document.addEventListener,h.isAndroid=/android/gi.test(e),h.isIOS=/iphone|ipad|ipod/gi.test(e),h.supportsTransition=function(){var e=document.createElement("p").style,t=["ms","O","Moz","Webkit"];if(void 0!==e.transition)return!0;for(;t.length;)if(t.pop()+"Transition"in e)return!0;return!1}(),h.probablyMobile=h.isAndroid||h.isIOS||/(Opera Mini)|Kindle|webOS|BlackBerry|(Opera Mobi)|(Windows Phone)|IEMobile/i.test(navigator.userAgent),m=u(document),h.popupsCache={}},open:function(e){if(!1===e.isObj){h.items=e.items.toArray(),h.index=0;for(var t,n=e.items,r=0;r(e||C.height())},_setFocus:function(){(h.st.focus?h.content.find(h.st.focus).eq(0):h.wrap).focus()},_onFocusIn:function(e){if(e.target!==h.wrap[0]&&!u.contains(h.wrap[0],e.target))return h._setFocus(),!1},_parseMarkup:function(o,e,t){var i;t.data&&(e=u.extend(t.data,e)),d(y,[o,e,t]),u.each(e,function(e,t){return void 0===t||!1===t||void(1<(i=e.split("_")).length?0<(n=o.find(x+"-"+i[0])).length&&("replaceWith"===(r=i[1])?n[0]!==t[0]&&n.replaceWith(t):"img"===r?n.is("img")?n.attr("src",t):n.replaceWith(u("").attr("src",t).attr("class",n.attr("class"))):n.attr(i[1],t)):o.find(x+"-"+e).html(t));var n,r})},_getScrollbarSize:function(){var e;return void 0===h.scrollbarSize&&((e=document.createElement("div")).style.cssText="width: 99px; height: 99px; overflow: scroll; position: absolute; top: -9999px;",document.body.appendChild(e),h.scrollbarSize=e.offsetWidth-e.clientWidth,document.body.removeChild(e)),h.scrollbarSize}},u.magnificPopup={instance:null,proto:e.prototype,modules:[],open:function(e,t){return i(),(e=e?u.extend(!0,{},e):{}).isObj=!0,e.index=t||0,this.instance.open(e)},close:function(){return u.magnificPopup.instance&&u.magnificPopup.instance.close()},registerModule:function(e,t){t.options&&(u.magnificPopup.defaults[e]=t.options),u.extend(this.proto,t.proto),this.modules.push(e)},defaults:{disableOn:0,key:null,midClick:!1,mainClass:"",preloader:!0,focus:"",closeOnContentClick:!1,closeOnBgClick:!0,closeBtnInside:!0,showCloseBtn:!0,enableEscapeKey:!0,modal:!1,alignTop:!1,removalDelay:0,prependTo:null,fixedContentPos:"auto",fixedBgPos:"auto",overflowY:"auto",closeMarkup:'',tClose:"Close (Esc)",tLoading:"Loading...",autoFocusLast:!0}},u.fn.magnificPopup=function(e){i();var t,n,r,o=u(this);return"string"==typeof e?"open"===e?(t=s?o.data("magnificPopup"):o[0].magnificPopup,n=parseInt(arguments[1],10)||0,r=t.items?t.items[n]:(r=o,(r=t.delegate?o.find(t.delegate):r).eq(n)),h._openClick({mfpEl:r},o,t)):h.isOpen&&h[e].apply(h,Array.prototype.slice.call(arguments,1)):(e=u.extend(!0,{},e),s?o.data("magnificPopup",e):o[0].magnificPopup=e,h.addGroup(o,e)),o};function T(){k&&(S.after(k.addClass(E)).detach(),k=null)}var E,S,k,A="inline";u.magnificPopup.registerModule(A,{options:{hiddenClass:"hide",markup:"",tNotFound:"Content not found"},proto:{initInline:function(){h.types.push(A),c(l+"."+A,function(){T()})},getInline:function(e,t){if(T(),e.src){var n,r=h.st.inline,o=u(e.src);return o.length?((n=o[0].parentNode)&&n.tagName&&(S||(E=r.hiddenClass,S=f(E),E="mfp-"+E),k=o.after(S).detach().removeClass(E)),h.updateStatus("ready")):(h.updateStatus("error",r.tNotFound),o=u("
")),e.inlineElement=o}return h.updateStatus("ready"),h._parseMarkup(t,{},e),t}}});function N(){I&&u(document.body).removeClass(I)}function j(){N(),h.req&&h.req.abort()}var I,L="ajax";u.magnificPopup.registerModule(L,{options:{settings:null,cursor:"mfp-ajax-cur",tError:'The content could not be loaded.'},proto:{initAjax:function(){h.types.push(L),I=h.st.ajax.cursor,c(l+"."+L,j),c("BeforeChange."+L,j)},getAjax:function(r){I&&u(document.body).addClass(I),h.updateStatus("loading");var e=u.extend({url:r.src,success:function(e,t,n){n={data:e,xhr:n};d("ParseAjax",n),h.appendContent(u(n.data),L),r.finished=!0,N(),h._setFocus(),setTimeout(function(){h.wrap.addClass(w)},16),h.updateStatus("ready"),d("AjaxContentAdded")},error:function(){N(),r.finished=r.loadError=!0,h.updateStatus("error",h.st.ajax.tError.replace("%url%",r.src))}},h.st.ajax.settings);return h.req=u.ajax(e),""}}});var D;u.magnificPopup.registerModule("image",{options:{markup:'
',cursor:"mfp-zoom-out-cur",titleSrc:"title",verticalFit:!0,tError:'The image could not be loaded.'},proto:{initImage:function(){var e=h.st.image,t=".image";h.types.push("image"),c(b+t,function(){"image"===h.currItem.type&&e.cursor&&u(document.body).addClass(e.cursor)}),c(l+t,function(){e.cursor&&u(document.body).removeClass(e.cursor),C.off("resize"+x)}),c("Resize"+t,h.resizeImage),h.isLowIE&&c("AfterChange",h.resizeImage)},resizeImage:function(){var e,t=h.currItem;t&&t.img&&h.st.image.verticalFit&&(e=0,h.isLowIE&&(e=parseInt(t.img.css("padding-top"),10)+parseInt(t.img.css("padding-bottom"),10)),t.img.css("max-height",h.wH-e))},_onImageHasSize:function(e){e.img&&(e.hasSize=!0,D&&clearInterval(D),e.isCheckingImgSize=!1,d("ImageHasSize",e),e.imgHidden&&(h.content&&h.content.removeClass("mfp-loading"),e.imgHidden=!1))},findImageSize:function(t){var n=0,r=t.img[0],o=function(e){D&&clearInterval(D),D=setInterval(function(){0
',srcAction:"iframe_src",patterns:{youtube:{index:"youtube.com",id:"v=",src:"//www.youtube.com/embed/%id%?autoplay=1"},vimeo:{index:"vimeo.com/",id:"/",src:"//player.vimeo.com/video/%id%?autoplay=1"},gmaps:{index:"//maps.google.",src:"%id%&output=embed"}}},proto:{initIframe:function(){h.types.push(P),c("BeforeChange",function(e,t,n){t!==n&&(t===P?H():n===P&&H(!0))}),c(l+"."+P,function(){H()})},getIframe:function(e,t){var n=e.src,r=h.st.iframe;u.each(r.patterns,function(){if(-1',preload:[0,2],navigateByImgClick:!0,arrows:!0,tPrev:"Previous (Left arrow key)",tNext:"Next (Right arrow key)",tCounter:"%curr% of %total%"},proto:{initGallery:function(){var i=h.st.gallery,e=".mfp-gallery";if(h.direction=!0,!i||!i.enabled)return!1;g+=" mfp-gallery",c(b+e,function(){i.navigateByImgClick&&h.wrap.on("click"+e,".mfp-img",function(){if(1=h.index,h.index=e,h.updateItemHTML()},preloadNearbyImages:function(){for(var e=h.st.gallery.preload,t=Math.min(e[0],h.items.length),n=Math.min(e[1],h.items.length),r=1;r<=(h.direction?n:t);r++)h._preloadItem(h.index+r);for(r=1;r<=(h.direction?t:n);r++)h._preloadItem(h.index-r)},_preloadItem:function(e){var t;e=q(e),h.items[e].preloaded||((t=h.items[e]).parsed||(t=h.parseEl(e)),d("LazyLoad",t),"image"===t.type&&(t.img=u('').on("load.mfploader",function(){t.hasSize=!0}).on("error.mfploader",function(){t.hasSize=!0,t.loadError=!0,d("LazyLoadError",t)}).attr("src",t.src)),t.preloaded=!0)}}});var _="retina";u.magnificPopup.registerModule(_,{options:{replaceSrc:function(e){return e.src.replace(/\.\w+$/,function(e){return"@2x"+e})},ratio:1},proto:{initRetina:function(){var n,r;1t.durationMax?t.durationMax:t.durationMin&&e=l)return b.cancelScroll(!0),e=t,n=g,0===(t=r)&&document.body.focus(),n||(t.focus(),document.activeElement!==t&&(t.setAttribute("tabindex","-1"),t.focus(),t.style.outline="none"),x.scrollTo(0,e)),E("scrollStop",m,r,o),!(y=f=null)},h=function(e){var t,n,r;u+=e-(f=f||e),d=i+s*(n=d=1<(d=0===c?0:u/c)?1:d,"easeInQuad"===(t=m).easing&&(r=n*n),"easeOutQuad"===t.easing&&(r=n*(2-n)),"easeInOutQuad"===t.easing&&(r=n<.5?2*n*n:(4-2*n)*n-1),"easeInCubic"===t.easing&&(r=n*n*n),"easeOutCubic"===t.easing&&(r=--n*n*n+1),"easeInOutCubic"===t.easing&&(r=n<.5?4*n*n*n:(n-1)*(2*n-2)*(2*n-2)+1),"easeInQuart"===t.easing&&(r=n*n*n*n),"easeOutQuart"===t.easing&&(r=1- --n*n*n*n),"easeInOutQuart"===t.easing&&(r=n<.5?8*n*n*n*n:1-8*--n*n*n*n),"easeInQuint"===t.easing&&(r=n*n*n*n*n),"easeOutQuint"===t.easing&&(r=1+--n*n*n*n*n),"easeInOutQuint"===t.easing&&(r=n<.5?16*n*n*n*n*n:1+16*--n*n*n*n*n),(r=t.customEasing?t.customEasing(n):r)||n),x.scrollTo(0,Math.floor(d)),p(d,a)||(y=x.requestAnimationFrame(h),f=e)},0===x.pageYOffset&&x.scrollTo(0,0),t=r,e=m,g||history.pushState&&e.updateURL&&history.pushState({smoothScroll:JSON.stringify(e),anchor:t.id},document.title,t===document.documentElement?"#top":"#"+t.id),"matchMedia"in x&&x.matchMedia("(prefers-reduced-motion)").matches?x.scrollTo(0,Math.floor(a)):(E("scrollStart",m,r,o),b.cancelScroll(!0),x.requestAnimationFrame(h)))};function t(e){if(!e.defaultPrevented&&!(0!==e.button||e.metaKey||e.ctrlKey||e.shiftKey)&&"closest"in e.target&&(o=e.target.closest(r))&&"a"===o.tagName.toLowerCase()&&!e.target.closest(v.ignore)&&o.hostname===x.location.hostname&&o.pathname===x.location.pathname&&/#/.test(o.href)){var t,n;try{n=a(decodeURIComponent(o.hash))}catch(e){n=a(o.hash)}if("#"===n){if(!v.topOnEmptyHash)return;t=document.documentElement}else t=document.querySelector(n);(t=t||"#top"!==n?t:document.documentElement)&&(e.preventDefault(),n=v,history.replaceState&&n.updateURL&&!history.state&&(e=(e=x.location.hash)||"",history.replaceState({smoothScroll:JSON.stringify(n),anchor:e||x.pageYOffset},document.title,e||x.location.href)),b.animateScroll(t,o))}}function i(e){var t;null!==history.state&&history.state.smoothScroll&&history.state.smoothScroll===JSON.stringify(v)&&("string"==typeof(t=history.state.anchor)&&t&&!(t=document.querySelector(a(history.state.anchor)))||b.animateScroll(t,null,{updateURL:!1}))}b.destroy=function(){v&&(document.removeEventListener("click",t,!1),x.removeEventListener("popstate",i,!1),b.cancelScroll(),y=n=o=v=null)};return function(){if(!("querySelector"in document&&"addEventListener"in x&&"requestAnimationFrame"in x&&"closest"in x.Element.prototype))throw"Smooth Scroll: This browser does not support the required JavaScript methods and browser APIs.";b.destroy(),v=w(S,e||{}),n=v.header?document.querySelector(v.header):null,document.addEventListener("click",t,!1),v.updateURL&&v.popstate&&x.addEventListener("popstate",i,!1)}(),b}}),function(e,t){"function"==typeof define&&define.amd?define([],function(){return t(e)}):"object"==typeof exports?module.exports=t(e):e.Gumshoe=t(e)}("undefined"!=typeof global?global:"undefined"!=typeof window?window:this,function(c){"use strict";function f(e,t,n){n.settings.events&&(n=new CustomEvent(e,{bubbles:!0,cancelable:!0,detail:n}),t.dispatchEvent(n))}function n(e){var t=0;if(e.offsetParent)for(;e;)t+=e.offsetTop,e=e.offsetParent;return 0<=t?t:0}function d(e){e&&e.sort(function(e,t){return n(e.content)=Math.max(document.body.scrollHeight,document.documentElement.scrollHeight,document.body.offsetHeight,document.documentElement.offsetHeight,document.body.clientHeight,document.documentElement.clientHeight)}function p(e,t){var n,r,o=e[e.length-1];if(n=o,r=t,!(!s()||!a(n.content,r,!0)))return o;for(var i=e.length-1;0<=i;i--)if(a(e[i].content,t))return e[i]}function h(e,t){var n;!e||(n=e.nav.closest("li"))&&(n.classList.remove(t.navClass),e.content.classList.remove(t.contentClass),r(n,t),f("gumshoeDeactivate",n,{link:e.nav,content:e.content,settings:t}))}var m={navClass:"active",contentClass:"active",nested:!1,nestedClass:"active",offset:0,reflow:!1,events:!0},r=function(e,t){!t.nested||(e=e.parentNode.closest("li"))&&(e.classList.remove(t.nestedClass),r(e,t))},g=function(e,t){!t.nested||(e=e.parentNode.closest("li"))&&(e.classList.add(t.nestedClass),g(e,t))};return function(e,t){var n,o,i,r,a,s={setup:function(){n=document.querySelectorAll(e),o=[],Array.prototype.forEach.call(n,function(e){var t=document.getElementById(decodeURIComponent(e.hash.substr(1)));t&&o.push({nav:e,content:t})}),d(o)}};s.detect=function(){var e,t,n,r=p(o,a);r?i&&r.content===i.content||(h(i,a),t=a,!(e=r)||(n=e.nav.closest("li"))&&(n.classList.add(t.navClass),e.content.classList.add(t.contentClass),g(n,t),f("gumshoeActivate",n,{link:e.nav,content:e.content,settings:t})),i=r):i&&(h(i,a),i=null)};function l(e){r&&c.cancelAnimationFrame(r),r=c.requestAnimationFrame(s.detect)}function u(e){r&&c.cancelAnimationFrame(r),r=c.requestAnimationFrame(function(){d(o),s.detect()})}s.destroy=function(){i&&h(i,a),c.removeEventListener("scroll",l,!1),a.reflow&&c.removeEventListener("resize",u,!1),a=r=i=n=o=null};return a=function(){var n={};return Array.prototype.forEach.call(arguments,function(e){for(var t in e){if(!e.hasOwnProperty(t))return;n[t]=e[t]}}),n}(m,t||{}),s.setup(),s.detect(),c.addEventListener("scroll",l,!1),a.reflow&&c.addEventListener("resize",u,!1),s}}),$(function(){$("#main").fitVids();function e(){(0===$(".author__urls-wrapper").find("button").length?1024<$(window).width():!$(".author__urls-wrapper").find("button").is(":visible"))?$(".sidebar").addClass("sticky"):$(".sidebar").removeClass("sticky")}e(),$(window).resize(function(){e()}),$(".author__urls-wrapper").find("button").on("click",function(){$(".author__urls").toggleClass("is--visible"),$(".author__urls-wrapper").find("button").toggleClass("open")}),$(document).keyup(function(e){27===e.keyCode&&$(".initial-content").hasClass("is--hidden")&&($(".search-content").toggleClass("is--visible"),$(".initial-content").toggleClass("is--hidden"))}),$(".search__toggle").on("click",function(){$(".search-content").toggleClass("is--visible"),$(".initial-content").toggleClass("is--hidden"),setTimeout(function(){$(".search-content").find("input").focus()},400)});new SmoothScroll('a[href*="#"]',{offset:20,speed:400,speedAsDuration:!0,durationMax:500});0<$("nav.toc").length&&new Gumshoe("nav.toc a",{navClass:"active",contentClass:"active",nested:!1,nestedClass:"active",offset:20,reflow:!0,events:!0}),$("a[href$='.jpg'],a[href$='.jpeg'],a[href$='.JPG'],a[href$='.png'],a[href$='.gif'],a[href$='.webp']").has("> img").addClass("image-popup"),$(".image-popup").magnificPopup({type:"image",tLoading:"Loading image #%curr%...",gallery:{enabled:!0,navigateByImgClick:!0,preload:[0,1]},image:{tError:'Image #%curr% could not be loaded.'},removalDelay:500,mainClass:"mfp-zoom-in",callbacks:{beforeOpen:function(){this.st.image.markup=this.st.image.markup.replace("mfp-figure","mfp-figure mfp-with-anim")}},closeOnContentClick:!0,midClick:!0}),$(".page__content").find("h1, h2, h3, h4, h5, h6").each(function(){var e,t=$(this).attr("id");t&&((e=document.createElement("a")).className="header-link",e.href="#"+t,e.innerHTML='Permalink',e.title="Permalink",$(this).append(e))})}); diff --git a/assets/js/plugins/gumshoe.js b/assets/js/plugins/gumshoe.js deleted file mode 100644 index 87d6072d..00000000 --- a/assets/js/plugins/gumshoe.js +++ /dev/null @@ -1,484 +0,0 @@ -/*! - * gumshoejs v5.1.1 - * A simple, framework-agnostic scrollspy script. - * (c) 2019 Chris Ferdinandi - * MIT License - * http://github.com/cferdinandi/gumshoe - */ - -(function (root, factory) { - if ( typeof define === 'function' && define.amd ) { - define([], (function () { - return factory(root); - })); - } else if ( typeof exports === 'object' ) { - module.exports = factory(root); - } else { - root.Gumshoe = factory(root); - } -})(typeof global !== 'undefined' ? global : typeof window !== 'undefined' ? window : this, (function (window) { - - 'use strict'; - - // - // Defaults - // - - var defaults = { - - // Active classes - navClass: 'active', - contentClass: 'active', - - // Nested navigation - nested: false, - nestedClass: 'active', - - // Offset & reflow - offset: 0, - reflow: false, - - // Event support - events: true - - }; - - - // - // Methods - // - - /** - * Merge two or more objects together. - * @param {Object} objects The objects to merge together - * @returns {Object} Merged values of defaults and options - */ - var extend = function () { - var merged = {}; - Array.prototype.forEach.call(arguments, (function (obj) { - for (var key in obj) { - if (!obj.hasOwnProperty(key)) return; - merged[key] = obj[key]; - } - })); - return merged; - }; - - /** - * Emit a custom event - * @param {String} type The event type - * @param {Node} elem The element to attach the event to - * @param {Object} detail Any details to pass along with the event - */ - var emitEvent = function (type, elem, detail) { - - // Make sure events are enabled - if (!detail.settings.events) return; - - // Create a new event - var event = new CustomEvent(type, { - bubbles: true, - cancelable: true, - detail: detail - }); - - // Dispatch the event - elem.dispatchEvent(event); - - }; - - /** - * Get an element's distance from the top of the Document. - * @param {Node} elem The element - * @return {Number} Distance from the top in pixels - */ - var getOffsetTop = function (elem) { - var location = 0; - if (elem.offsetParent) { - while (elem) { - location += elem.offsetTop; - elem = elem.offsetParent; - } - } - return location >= 0 ? location : 0; - }; - - /** - * Sort content from first to last in the DOM - * @param {Array} contents The content areas - */ - var sortContents = function (contents) { - if(contents) { - contents.sort((function (item1, item2) { - var offset1 = getOffsetTop(item1.content); - var offset2 = getOffsetTop(item2.content); - if (offset1 < offset2) return -1; - return 1; - })); - } - }; - - /** - * Get the offset to use for calculating position - * @param {Object} settings The settings for this instantiation - * @return {Float} The number of pixels to offset the calculations - */ - var getOffset = function (settings) { - - // if the offset is a function run it - if (typeof settings.offset === 'function') { - return parseFloat(settings.offset()); - } - - // Otherwise, return it as-is - return parseFloat(settings.offset); - - }; - - /** - * Get the document element's height - * @private - * @returns {Number} - */ - var getDocumentHeight = function () { - return Math.max( - document.body.scrollHeight, document.documentElement.scrollHeight, - document.body.offsetHeight, document.documentElement.offsetHeight, - document.body.clientHeight, document.documentElement.clientHeight - ); - }; - - /** - * Determine if an element is in view - * @param {Node} elem The element - * @param {Object} settings The settings for this instantiation - * @param {Boolean} bottom If true, check if element is above bottom of viewport instead - * @return {Boolean} Returns true if element is in the viewport - */ - var isInView = function (elem, settings, bottom) { - var bounds = elem.getBoundingClientRect(); - var offset = getOffset(settings); - if (bottom) { - return parseInt(bounds.bottom, 10) < (window.innerHeight || document.documentElement.clientHeight); - } - return parseInt(bounds.top, 10) <= offset; - }; - - /** - * Check if at the bottom of the viewport - * @return {Boolean} If true, page is at the bottom of the viewport - */ - var isAtBottom = function () { - if (window.innerHeight + window.pageYOffset >= getDocumentHeight()) return true; - return false; - }; - - /** - * Check if the last item should be used (even if not at the top of the page) - * @param {Object} item The last item - * @param {Object} settings The settings for this instantiation - * @return {Boolean} If true, use the last item - */ - var useLastItem = function (item, settings) { - if (isAtBottom() && isInView(item.content, settings, true)) return true; - return false; - }; - - /** - * Get the active content - * @param {Array} contents The content areas - * @param {Object} settings The settings for this instantiation - * @return {Object} The content area and matching navigation link - */ - var getActive = function (contents, settings) { - var last = contents[contents.length-1]; - if (useLastItem(last, settings)) return last; - for (var i = contents.length - 1; i >= 0; i--) { - if (isInView(contents[i].content, settings)) return contents[i]; - } - }; - - /** - * Deactivate parent navs in a nested navigation - * @param {Node} nav The starting navigation element - * @param {Object} settings The settings for this instantiation - */ - var deactivateNested = function (nav, settings) { - - // If nesting isn't activated, bail - if (!settings.nested) return; - - // Get the parent navigation - var li = nav.parentNode.closest('li'); - if (!li) return; - - // Remove the active class - li.classList.remove(settings.nestedClass); - - // Apply recursively to any parent navigation elements - deactivateNested(li, settings); - - }; - - /** - * Deactivate a nav and content area - * @param {Object} items The nav item and content to deactivate - * @param {Object} settings The settings for this instantiation - */ - var deactivate = function (items, settings) { - - // Make sure their are items to deactivate - if (!items) return; - - // Get the parent list item - var li = items.nav.closest('li'); - if (!li) return; - - // Remove the active class from the nav and content - li.classList.remove(settings.navClass); - items.content.classList.remove(settings.contentClass); - - // Deactivate any parent navs in a nested navigation - deactivateNested(li, settings); - - // Emit a custom event - emitEvent('gumshoeDeactivate', li, { - link: items.nav, - content: items.content, - settings: settings - }); - - }; - - - /** - * Activate parent navs in a nested navigation - * @param {Node} nav The starting navigation element - * @param {Object} settings The settings for this instantiation - */ - var activateNested = function (nav, settings) { - - // If nesting isn't activated, bail - if (!settings.nested) return; - - // Get the parent navigation - var li = nav.parentNode.closest('li'); - if (!li) return; - - // Add the active class - li.classList.add(settings.nestedClass); - - // Apply recursively to any parent navigation elements - activateNested(li, settings); - - }; - - /** - * Activate a nav and content area - * @param {Object} items The nav item and content to activate - * @param {Object} settings The settings for this instantiation - */ - var activate = function (items, settings) { - - // Make sure their are items to activate - if (!items) return; - - // Get the parent list item - var li = items.nav.closest('li'); - if (!li) return; - - // Add the active class to the nav and content - li.classList.add(settings.navClass); - items.content.classList.add(settings.contentClass); - - // Activate any parent navs in a nested navigation - activateNested(li, settings); - - // Emit a custom event - emitEvent('gumshoeActivate', li, { - link: items.nav, - content: items.content, - settings: settings - }); - - }; - - /** - * Create the Constructor object - * @param {String} selector The selector to use for navigation items - * @param {Object} options User options and settings - */ - var Constructor = function (selector, options) { - - // - // Variables - // - - var publicAPIs = {}; - var navItems, contents, current, timeout, settings; - - - // - // Methods - // - - /** - * Set variables from DOM elements - */ - publicAPIs.setup = function () { - - // Get all nav items - navItems = document.querySelectorAll(selector); - - // Create contents array - contents = []; - - // Loop through each item, get it's matching content, and push to the array - Array.prototype.forEach.call(navItems, (function (item) { - - // Get the content for the nav item - var content = document.getElementById(decodeURIComponent(item.hash.substr(1))); - if (!content) return; - - // Push to the contents array - contents.push({ - nav: item, - content: content - }); - - })); - - // Sort contents by the order they appear in the DOM - sortContents(contents); - - }; - - /** - * Detect which content is currently active - */ - publicAPIs.detect = function () { - - // Get the active content - var active = getActive(contents, settings); - - // if there's no active content, deactivate and bail - if (!active) { - if (current) { - deactivate(current, settings); - current = null; - } - return; - } - - // If the active content is the one currently active, do nothing - if (current && active.content === current.content) return; - - // Deactivate the current content and activate the new content - deactivate(current, settings); - activate(active, settings); - - // Update the currently active content - current = active; - - }; - - /** - * Detect the active content on scroll - * Debounced for performance - */ - var scrollHandler = function (event) { - - // If there's a timer, cancel it - if (timeout) { - window.cancelAnimationFrame(timeout); - } - - // Setup debounce callback - timeout = window.requestAnimationFrame(publicAPIs.detect); - - }; - - /** - * Update content sorting on resize - * Debounced for performance - */ - var resizeHandler = function (event) { - - // If there's a timer, cancel it - if (timeout) { - window.cancelAnimationFrame(timeout); - } - - // Setup debounce callback - timeout = window.requestAnimationFrame((function () { - sortContents(contents); - publicAPIs.detect(); - })); - - }; - - /** - * Destroy the current instantiation - */ - publicAPIs.destroy = function () { - - // Undo DOM changes - if (current) { - deactivate(current, settings); - } - - // Remove event listeners - window.removeEventListener('scroll', scrollHandler, false); - if (settings.reflow) { - window.removeEventListener('resize', resizeHandler, false); - } - - // Reset variables - contents = null; - navItems = null; - current = null; - timeout = null; - settings = null; - - }; - - /** - * Initialize the current instantiation - */ - var init = function () { - - // Merge user options into defaults - settings = extend(defaults, options || {}); - - // Setup variables based on the current DOM - publicAPIs.setup(); - - // Find the currently active content - publicAPIs.detect(); - - // Setup event listeners - window.addEventListener('scroll', scrollHandler, false); - if (settings.reflow) { - window.addEventListener('resize', resizeHandler, false); - } - - }; - - - // - // Initialize and return the public APIs - // - - init(); - return publicAPIs; - - }; - - - // - // Return the Constructor - // - - return Constructor; - -})); diff --git a/assets/js/plugins/jquery.ba-throttle-debounce.js b/assets/js/plugins/jquery.ba-throttle-debounce.js deleted file mode 100644 index f39717c1..00000000 --- a/assets/js/plugins/jquery.ba-throttle-debounce.js +++ /dev/null @@ -1,252 +0,0 @@ -/*! - * jQuery throttle / debounce - v1.1 - 3/7/2010 - * http://benalman.com/projects/jquery-throttle-debounce-plugin/ - * - * Copyright (c) 2010 "Cowboy" Ben Alman - * Dual licensed under the MIT and GPL licenses. - * http://benalman.com/about/license/ - */ - -// Script: jQuery throttle / debounce: Sometimes, less is more! -// -// *Version: 1.1, Last updated: 3/7/2010* -// -// Project Home - http://benalman.com/projects/jquery-throttle-debounce-plugin/ -// GitHub - http://github.com/cowboy/jquery-throttle-debounce/ -// Source - http://github.com/cowboy/jquery-throttle-debounce/raw/master/jquery.ba-throttle-debounce.js -// (Minified) - http://github.com/cowboy/jquery-throttle-debounce/raw/master/jquery.ba-throttle-debounce.min.js (0.7kb) -// -// About: License -// -// Copyright (c) 2010 "Cowboy" Ben Alman, -// Dual licensed under the MIT and GPL licenses. -// http://benalman.com/about/license/ -// -// About: Examples -// -// These working examples, complete with fully commented code, illustrate a few -// ways in which this plugin can be used. -// -// Throttle - http://benalman.com/code/projects/jquery-throttle-debounce/examples/throttle/ -// Debounce - http://benalman.com/code/projects/jquery-throttle-debounce/examples/debounce/ -// -// About: Support and Testing -// -// Information about what version or versions of jQuery this plugin has been -// tested with, what browsers it has been tested in, and where the unit tests -// reside (so you can test it yourself). -// -// jQuery Versions - none, 1.3.2, 1.4.2 -// Browsers Tested - Internet Explorer 6-8, Firefox 2-3.6, Safari 3-4, Chrome 4-5, Opera 9.6-10.1. -// Unit Tests - http://benalman.com/code/projects/jquery-throttle-debounce/unit/ -// -// About: Release History -// -// 1.1 - (3/7/2010) Fixed a bug in where trailing callbacks -// executed later than they should. Reworked a fair amount of internal -// logic as well. -// 1.0 - (3/6/2010) Initial release as a stand-alone project. Migrated over -// from jquery-misc repo v0.4 to jquery-throttle repo v1.0, added the -// no_trailing throttle parameter and debounce functionality. -// -// Topic: Note for non-jQuery users -// -// jQuery isn't actually required for this plugin, because nothing internal -// uses any jQuery methods or properties. jQuery is just used as a namespace -// under which these methods can exist. -// -// Since jQuery isn't actually required for this plugin, if jQuery doesn't exist -// when this plugin is loaded, the method described below will be created in -// the `Cowboy` namespace. Usage will be exactly the same, but instead of -// $.method() or jQuery.method(), you'll need to use Cowboy.method(). - -(function(window,undefined){ - '$:nomunge'; // Used by YUI compressor. - - // Since jQuery really isn't required for this plugin, use `jQuery` as the - // namespace only if it already exists, otherwise use the `Cowboy` namespace, - // creating it if necessary. - var $ = window.jQuery || window.Cowboy || ( window.Cowboy = {} ), - - // Internal method reference. - jq_throttle; - - // Method: jQuery.throttle - // - // Throttle execution of a function. Especially useful for rate limiting - // execution of handlers on events like resize and scroll. If you want to - // rate-limit execution of a function to a single time, see the - // method. - // - // In this visualization, | is a throttled-function call and X is the actual - // callback execution: - // - // > Throttled with `no_trailing` specified as false or unspecified: - // > ||||||||||||||||||||||||| (pause) ||||||||||||||||||||||||| - // > X X X X X X X X X X X X - // > - // > Throttled with `no_trailing` specified as true: - // > ||||||||||||||||||||||||| (pause) ||||||||||||||||||||||||| - // > X X X X X X X X X X - // - // Usage: - // - // > var throttled = jQuery.throttle( delay, [ no_trailing, ] callback ); - // > - // > jQuery('selector').bind( 'someevent', throttled ); - // > jQuery('selector').unbind( 'someevent', throttled ); - // - // This also works in jQuery 1.4+: - // - // > jQuery('selector').bind( 'someevent', jQuery.throttle( delay, [ no_trailing, ] callback ) ); - // > jQuery('selector').unbind( 'someevent', callback ); - // - // Arguments: - // - // delay - (Number) A zero-or-greater delay in milliseconds. For event - // callbacks, values around 100 or 250 (or even higher) are most useful. - // no_trailing - (Boolean) Optional, defaults to false. If no_trailing is - // true, callback will only execute every `delay` milliseconds while the - // throttled-function is being called. If no_trailing is false or - // unspecified, callback will be executed one final time after the last - // throttled-function call. (After the throttled-function has not been - // called for `delay` milliseconds, the internal counter is reset) - // callback - (Function) A function to be executed after delay milliseconds. - // The `this` context and all arguments are passed through, as-is, to - // `callback` when the throttled-function is executed. - // - // Returns: - // - // (Function) A new, throttled, function. - - $.throttle = jq_throttle = function( delay, no_trailing, callback, debounce_mode ) { - // After wrapper has stopped being called, this timeout ensures that - // `callback` is executed at the proper times in `throttle` and `end` - // debounce modes. - var timeout_id, - - // Keep track of the last time `callback` was executed. - last_exec = 0; - - // `no_trailing` defaults to falsy. - if ( typeof no_trailing !== 'boolean' ) { - debounce_mode = callback; - callback = no_trailing; - no_trailing = undefined; - } - - // The `wrapper` function encapsulates all of the throttling / debouncing - // functionality and when executed will limit the rate at which `callback` - // is executed. - function wrapper() { - var that = this, - elapsed = +new Date() - last_exec, - args = arguments; - - // Execute `callback` and update the `last_exec` timestamp. - function exec() { - last_exec = +new Date(); - callback.apply( that, args ); - }; - - // If `debounce_mode` is true (at_begin) this is used to clear the flag - // to allow future `callback` executions. - function clear() { - timeout_id = undefined; - }; - - if ( debounce_mode && !timeout_id ) { - // Since `wrapper` is being called for the first time and - // `debounce_mode` is true (at_begin), execute `callback`. - exec(); - } - - // Clear any existing timeout. - timeout_id && clearTimeout( timeout_id ); - - if ( debounce_mode === undefined && elapsed > delay ) { - // In throttle mode, if `delay` time has been exceeded, execute - // `callback`. - exec(); - - } else if ( no_trailing !== true ) { - // In trailing throttle mode, since `delay` time has not been - // exceeded, schedule `callback` to execute `delay` ms after most - // recent execution. - // - // If `debounce_mode` is true (at_begin), schedule `clear` to execute - // after `delay` ms. - // - // If `debounce_mode` is false (at end), schedule `callback` to - // execute after `delay` ms. - timeout_id = setTimeout( debounce_mode ? clear : exec, debounce_mode === undefined ? delay - elapsed : delay ); - } - }; - - // Set the guid of `wrapper` function to the same of original callback, so - // it can be removed in jQuery 1.4+ .unbind or .die by using the original - // callback as a reference. - if ( $.guid ) { - wrapper.guid = callback.guid = callback.guid || $.guid++; - } - - // Return the wrapper function. - return wrapper; - }; - - // Method: jQuery.debounce - // - // Debounce execution of a function. Debouncing, unlike throttling, - // guarantees that a function is only executed a single time, either at the - // very beginning of a series of calls, or at the very end. If you want to - // simply rate-limit execution of a function, see the - // method. - // - // In this visualization, | is a debounced-function call and X is the actual - // callback execution: - // - // > Debounced with `at_begin` specified as false or unspecified: - // > ||||||||||||||||||||||||| (pause) ||||||||||||||||||||||||| - // > X X - // > - // > Debounced with `at_begin` specified as true: - // > ||||||||||||||||||||||||| (pause) ||||||||||||||||||||||||| - // > X X - // - // Usage: - // - // > var debounced = jQuery.debounce( delay, [ at_begin, ] callback ); - // > - // > jQuery('selector').bind( 'someevent', debounced ); - // > jQuery('selector').unbind( 'someevent', debounced ); - // - // This also works in jQuery 1.4+: - // - // > jQuery('selector').bind( 'someevent', jQuery.debounce( delay, [ at_begin, ] callback ) ); - // > jQuery('selector').unbind( 'someevent', callback ); - // - // Arguments: - // - // delay - (Number) A zero-or-greater delay in milliseconds. For event - // callbacks, values around 100 or 250 (or even higher) are most useful. - // at_begin - (Boolean) Optional, defaults to false. If at_begin is false or - // unspecified, callback will only be executed `delay` milliseconds after - // the last debounced-function call. If at_begin is true, callback will be - // executed only at the first debounced-function call. (After the - // throttled-function has not been called for `delay` milliseconds, the - // internal counter is reset) - // callback - (Function) A function to be executed after delay milliseconds. - // The `this` context and all arguments are passed through, as-is, to - // `callback` when the debounced-function is executed. - // - // Returns: - // - // (Function) A new, debounced, function. - - $.debounce = function( delay, at_begin, callback ) { - return callback === undefined - ? jq_throttle( delay, at_begin, false ) - : jq_throttle( delay, callback, at_begin !== false ); - }; - -})(this); diff --git a/assets/js/plugins/jquery.fitvids.js b/assets/js/plugins/jquery.fitvids.js deleted file mode 100644 index 3742b4e0..00000000 --- a/assets/js/plugins/jquery.fitvids.js +++ /dev/null @@ -1,82 +0,0 @@ -/*jshint browser:true */ -/*! -* FitVids 1.1 -* -* Copyright 2013, Chris Coyier - http://css-tricks.com + Dave Rupert - http://daverupert.com -* Credit to Thierry Koblentz - http://www.alistapart.com/articles/creating-intrinsic-ratios-for-video/ -* Released under the WTFPL license - http://sam.zoy.org/wtfpl/ -* -*/ - -;(function( $ ){ - - 'use strict'; - - $.fn.fitVids = function( options ) { - var settings = { - customSelector: null, - ignore: null - }; - - if(!document.getElementById('fit-vids-style')) { - // appendStyles: https://github.com/toddmotto/fluidvids/blob/master/dist/fluidvids.js - var head = document.head || document.getElementsByTagName('head')[0]; - var css = '.fluid-width-video-wrapper{width:100%;position:relative;padding:0;}.fluid-width-video-wrapper iframe,.fluid-width-video-wrapper object,.fluid-width-video-wrapper embed {position:absolute;top:0;left:0;width:100%;height:100%;}'; - var div = document.createElement("div"); - div.innerHTML = '

x

'; - head.appendChild(div.childNodes[1]); - } - - if ( options ) { - $.extend( settings, options ); - } - - return this.each(function(){ - var selectors = [ - 'iframe[src*="player.vimeo.com"]', - 'iframe[src*="youtube.com"]', - 'iframe[src*="youtube-nocookie.com"]', - 'iframe[src*="kickstarter.com"][src*="video.html"]', - 'object', - 'embed' - ]; - - if (settings.customSelector) { - selectors.push(settings.customSelector); - } - - var ignoreList = '.fitvidsignore'; - - if(settings.ignore) { - ignoreList = ignoreList + ', ' + settings.ignore; - } - - var $allVideos = $(this).find(selectors.join(',')); - $allVideos = $allVideos.not('object object'); // SwfObj conflict patch - $allVideos = $allVideos.not(ignoreList); // Disable FitVids on this video. - - $allVideos.each(function(count){ - var $this = $(this); - if($this.parents(ignoreList).length > 0) { - return; // Disable FitVids on this video. - } - if (this.tagName.toLowerCase() === 'embed' && $this.parent('object').length || $this.parent('.fluid-width-video-wrapper').length) { return; } - if ((!$this.css('height') && !$this.css('width')) && (isNaN($this.attr('height')) || isNaN($this.attr('width')))) - { - $this.attr('height', 9); - $this.attr('width', 16); - } - var height = ( this.tagName.toLowerCase() === 'object' || ($this.attr('height') && !isNaN(parseInt($this.attr('height'), 10))) ) ? parseInt($this.attr('height'), 10) : $this.height(), - width = !isNaN(parseInt($this.attr('width'), 10)) ? parseInt($this.attr('width'), 10) : $this.width(), - aspectRatio = height / width; - if(!$this.attr('id')){ - var videoID = 'fitvid' + count; - $this.attr('id', videoID); - } - $this.wrap('
').parent('.fluid-width-video-wrapper').css('padding-top', (aspectRatio * 100)+'%'); - $this.removeAttr('height').removeAttr('width'); - }); - }); - }; -// Works with either jQuery or Zepto -})( window.jQuery || window.Zepto ); diff --git a/assets/js/plugins/jquery.greedy-navigation.js b/assets/js/plugins/jquery.greedy-navigation.js deleted file mode 100644 index b29931fa..00000000 --- a/assets/js/plugins/jquery.greedy-navigation.js +++ /dev/null @@ -1,127 +0,0 @@ -/* -GreedyNav.js - https://github.com/lukejacksonn/GreedyNav -Licensed under the MIT license - http://opensource.org/licenses/MIT -Copyright (c) 2015 Luke Jackson http://lukejacksonn.com -*/ - -$(function() { - - var $btn = $("nav.greedy-nav .greedy-nav__toggle"); - var $vlinks = $("nav.greedy-nav .visible-links"); - var $hlinks = $("nav.greedy-nav .hidden-links"); - var $nav = $("nav.greedy-nav"); - var $logo = $('nav.greedy-nav .site-logo'); - var $logoImg = $('nav.greedy-nav .site-logo img'); - var $title = $("nav.greedy-nav .site-title"); - var $search = $('nav.greedy-nav button.search__toggle'); - - var numOfItems, totalSpace, closingTime, breakWidths; - - // This function measures both hidden and visible links and sets the navbar breakpoints - // This is called the first time the script runs and everytime the "check()" function detects a change of window width that reached a different CSS width breakpoint, which affects the size of navbar Items - // Please note that "CSS width breakpoints" (which are only 4) !== "navbar breakpoints" (which are as many as the number of items on the navbar) - function measureLinks(){ - numOfItems = 0; - totalSpace = 0; - closingTime = 1000; - breakWidths = []; - - // Adds the width of a navItem in order to create breakpoints for the navbar - function addWidth(i, w) { - totalSpace += w; - numOfItems += 1; - breakWidths.push(totalSpace); - } - - // Measures the width of hidden links by making a temporary clone of them and positioning under visible links - function hiddenWidth(obj){ - var clone = obj.clone(); - clone.css("visibility","hidden"); - $vlinks.append(clone); - addWidth(0, clone.outerWidth()); - clone.remove(); - } - // Measure both visible and hidden links widths - $vlinks.children().outerWidth(addWidth); - $hlinks.children().each(function(){hiddenWidth($(this))}); - } - // Get initial state - measureLinks(); - - var winWidth = $( window ).width(); - // Set the last measured CSS width breakpoint: 0: <768px, 1: <1024px, 2: < 1280px, 3: >= 1280px. - var lastBreakpoint = winWidth < 768 ? 0 : winWidth < 1024 ? 1 : winWidth < 1280 ? 2 : 3; - - var availableSpace, numOfVisibleItems, requiredSpace, timer; - - function check() { - - winWidth = $( window ).width(); - // Set the current CSS width breakpoint: 0: <768px, 1: <1024px, 2: < 1280px, 3: >= 1280px. - var curBreakpoint = winWidth < 768 ? 0 : winWidth < 1024 ? 1 : winWidth < 1280 ? 2 : 3; - // If current breakpoint is different from last measured breakpoint, measureLinks again - if(curBreakpoint !== lastBreakpoint) measureLinks(); - // Set the last measured CSS width breakpoint with the current breakpoint - lastBreakpoint = curBreakpoint; - - // Get instant state - numOfVisibleItems = $vlinks.children().length; - // Decrease the width of visible elements from the nav innerWidth to find out the available space for navItems - availableSpace = /* nav */ $nav.innerWidth() - - /* logo */ ($logo.length !== 0 ? $logo.outerWidth(true) : 0) - - /* title */ $title.outerWidth(true) - - /* search */ ($search.length !== 0 ? $search.outerWidth(true) : 0) - - /* toggle */ (numOfVisibleItems !== breakWidths.length ? $btn.outerWidth(true) : 0); - requiredSpace = breakWidths[numOfVisibleItems - 1]; - - // There is not enought space - if (requiredSpace > availableSpace) { - $vlinks.children().last().prependTo($hlinks); - numOfVisibleItems -= 1; - check(); - // There is more than enough space. If only one element is hidden, add the toggle width to the available space - } else if (availableSpace + (numOfVisibleItems === breakWidths.length - 1?$btn.outerWidth(true):0) > breakWidths[numOfVisibleItems]) { - $hlinks.children().first().appendTo($vlinks); - numOfVisibleItems += 1; - check(); - } - // Update the button accordingly - $btn.attr("count", numOfItems - numOfVisibleItems); - if (numOfVisibleItems === numOfItems) { - $btn.addClass('hidden'); - } else $btn.removeClass('hidden'); - } - - // Window listeners - $(window).resize(function() { - check(); - }); - - $btn.on('click', function() { - $hlinks.toggleClass('hidden'); - $(this).toggleClass('close'); - clearTimeout(timer); - }); - - $hlinks.on('mouseleave', function() { - // Mouse has left, start the timer - timer = setTimeout(function() { - $hlinks.addClass('hidden'); - }, closingTime); - }).on('mouseenter', function() { - // Mouse is back, cancel the timer - clearTimeout(timer); - }) - - // check if page has a logo - if($logoImg.length !== 0){ - // check if logo is not loaded - if(!($logoImg[0].complete || $logoImg[0].naturalWidth !== 0)){ - // if logo is not loaded wait for logo to load or fail to check - $logoImg.one("load error", check); - // if logo is already loaded just check - } else check(); - // if page does not have a logo just check - } else check(); - -}); diff --git a/assets/js/plugins/jquery.magnific-popup.js b/assets/js/plugins/jquery.magnific-popup.js deleted file mode 100644 index f159f97e..00000000 --- a/assets/js/plugins/jquery.magnific-popup.js +++ /dev/null @@ -1,1860 +0,0 @@ -/*! Magnific Popup - v1.1.0 - 2016-02-20 -* http://dimsemenov.com/plugins/magnific-popup/ -* Copyright (c) 2016 Dmitry Semenov; */ -;(function (factory) { - if (typeof define === 'function' && define.amd) { - // AMD. Register as an anonymous module. - define(['jquery'], factory); - } else if (typeof exports === 'object') { - // Node/CommonJS - factory(require('jquery')); - } else { - // Browser globals - factory(window.jQuery || window.Zepto); - } - }(function($) { - - /*>>core*/ - /** - * - * Magnific Popup Core JS file - * - */ - - - /** - * Private static constants - */ - var CLOSE_EVENT = 'Close', - BEFORE_CLOSE_EVENT = 'BeforeClose', - AFTER_CLOSE_EVENT = 'AfterClose', - BEFORE_APPEND_EVENT = 'BeforeAppend', - MARKUP_PARSE_EVENT = 'MarkupParse', - OPEN_EVENT = 'Open', - CHANGE_EVENT = 'Change', - NS = 'mfp', - EVENT_NS = '.' + NS, - READY_CLASS = 'mfp-ready', - REMOVING_CLASS = 'mfp-removing', - PREVENT_CLOSE_CLASS = 'mfp-prevent-close'; - - - /** - * Private vars - */ - /*jshint -W079 */ - var mfp, // As we have only one instance of MagnificPopup object, we define it locally to not to use 'this' - MagnificPopup = function(){}, - _isJQ = !!(window.jQuery), - _prevStatus, - _window = $(window), - _document, - _prevContentType, - _wrapClasses, - _currPopupType; - - - /** - * Private functions - */ - var _mfpOn = function(name, f) { - mfp.ev.on(NS + name + EVENT_NS, f); - }, - _getEl = function(className, appendTo, html, raw) { - var el = document.createElement('div'); - el.className = 'mfp-'+className; - if(html) { - el.innerHTML = html; - } - if(!raw) { - el = $(el); - if(appendTo) { - el.appendTo(appendTo); - } - } else if(appendTo) { - appendTo.appendChild(el); - } - return el; - }, - _mfpTrigger = function(e, data) { - mfp.ev.triggerHandler(NS + e, data); - - if(mfp.st.callbacks) { - // converts "mfpEventName" to "eventName" callback and triggers it if it's present - e = e.charAt(0).toLowerCase() + e.slice(1); - if(mfp.st.callbacks[e]) { - mfp.st.callbacks[e].apply(mfp, $.isArray(data) ? data : [data]); - } - } - }, - _getCloseBtn = function(type) { - if(type !== _currPopupType || !mfp.currTemplate.closeBtn) { - mfp.currTemplate.closeBtn = $( mfp.st.closeMarkup.replace('%title%', mfp.st.tClose ) ); - _currPopupType = type; - } - return mfp.currTemplate.closeBtn; - }, - // Initialize Magnific Popup only when called at least once - _checkInstance = function() { - if(!$.magnificPopup.instance) { - /*jshint -W020 */ - mfp = new MagnificPopup(); - mfp.init(); - $.magnificPopup.instance = mfp; - } - }, - // CSS transition detection, http://stackoverflow.com/questions/7264899/detect-css-transitions-using-javascript-and-without-modernizr - supportsTransitions = function() { - var s = document.createElement('p').style, // 's' for style. better to create an element if body yet to exist - v = ['ms','O','Moz','Webkit']; // 'v' for vendor - - if( s['transition'] !== undefined ) { - return true; - } - - while( v.length ) { - if( v.pop() + 'Transition' in s ) { - return true; - } - } - - return false; - }; - - - - /** - * Public functions - */ - MagnificPopup.prototype = { - - constructor: MagnificPopup, - - /** - * Initializes Magnific Popup plugin. - * This function is triggered only once when $.fn.magnificPopup or $.magnificPopup is executed - */ - init: function() { - var appVersion = navigator.appVersion; - mfp.isLowIE = mfp.isIE8 = document.all && !document.addEventListener; - mfp.isAndroid = (/android/gi).test(appVersion); - mfp.isIOS = (/iphone|ipad|ipod/gi).test(appVersion); - mfp.supportsTransition = supportsTransitions(); - - // We disable fixed positioned lightbox on devices that don't handle it nicely. - // If you know a better way of detecting this - let me know. - mfp.probablyMobile = (mfp.isAndroid || mfp.isIOS || /(Opera Mini)|Kindle|webOS|BlackBerry|(Opera Mobi)|(Windows Phone)|IEMobile/i.test(navigator.userAgent) ); - _document = $(document); - - mfp.popupsCache = {}; - }, - - /** - * Opens popup - * @param data [description] - */ - open: function(data) { - - var i; - - if(data.isObj === false) { - // convert jQuery collection to array to avoid conflicts later - mfp.items = data.items.toArray(); - - mfp.index = 0; - var items = data.items, - item; - for(i = 0; i < items.length; i++) { - item = items[i]; - if(item.parsed) { - item = item.el[0]; - } - if(item === data.el[0]) { - mfp.index = i; - break; - } - } - } else { - mfp.items = $.isArray(data.items) ? data.items : [data.items]; - mfp.index = data.index || 0; - } - - // if popup is already opened - we just update the content - if(mfp.isOpen) { - mfp.updateItemHTML(); - return; - } - - mfp.types = []; - _wrapClasses = ''; - if(data.mainEl && data.mainEl.length) { - mfp.ev = data.mainEl.eq(0); - } else { - mfp.ev = _document; - } - - if(data.key) { - if(!mfp.popupsCache[data.key]) { - mfp.popupsCache[data.key] = {}; - } - mfp.currTemplate = mfp.popupsCache[data.key]; - } else { - mfp.currTemplate = {}; - } - - - - mfp.st = $.extend(true, {}, $.magnificPopup.defaults, data ); - mfp.fixedContentPos = mfp.st.fixedContentPos === 'auto' ? !mfp.probablyMobile : mfp.st.fixedContentPos; - - if(mfp.st.modal) { - mfp.st.closeOnContentClick = false; - mfp.st.closeOnBgClick = false; - mfp.st.showCloseBtn = false; - mfp.st.enableEscapeKey = false; - } - - - // Building markup - // main containers are created only once - if(!mfp.bgOverlay) { - - // Dark overlay - mfp.bgOverlay = _getEl('bg').on('click'+EVENT_NS, function() { - mfp.close(); - }); - - mfp.wrap = _getEl('wrap').attr('tabindex', -1).on('click'+EVENT_NS, function(e) { - if(mfp._checkIfClose(e.target)) { - mfp.close(); - } - }); - - mfp.container = _getEl('container', mfp.wrap); - } - - mfp.contentContainer = _getEl('content'); - if(mfp.st.preloader) { - mfp.preloader = _getEl('preloader', mfp.container, mfp.st.tLoading); - } - - - // Initializing modules - var modules = $.magnificPopup.modules; - for(i = 0; i < modules.length; i++) { - var n = modules[i]; - n = n.charAt(0).toUpperCase() + n.slice(1); - mfp['init'+n].call(mfp); - } - _mfpTrigger('BeforeOpen'); - - - if(mfp.st.showCloseBtn) { - // Close button - if(!mfp.st.closeBtnInside) { - mfp.wrap.append( _getCloseBtn() ); - } else { - _mfpOn(MARKUP_PARSE_EVENT, function(e, template, values, item) { - values.close_replaceWith = _getCloseBtn(item.type); - }); - _wrapClasses += ' mfp-close-btn-in'; - } - } - - if(mfp.st.alignTop) { - _wrapClasses += ' mfp-align-top'; - } - - - - if(mfp.fixedContentPos) { - mfp.wrap.css({ - overflow: mfp.st.overflowY, - overflowX: 'hidden', - overflowY: mfp.st.overflowY - }); - } else { - mfp.wrap.css({ - top: _window.scrollTop(), - position: 'absolute' - }); - } - if( mfp.st.fixedBgPos === false || (mfp.st.fixedBgPos === 'auto' && !mfp.fixedContentPos) ) { - mfp.bgOverlay.css({ - height: _document.height(), - position: 'absolute' - }); - } - - - - if(mfp.st.enableEscapeKey) { - // Close on ESC key - _document.on('keyup' + EVENT_NS, function(e) { - if(e.keyCode === 27) { - mfp.close(); - } - }); - } - - _window.on('resize' + EVENT_NS, function() { - mfp.updateSize(); - }); - - - if(!mfp.st.closeOnContentClick) { - _wrapClasses += ' mfp-auto-cursor'; - } - - if(_wrapClasses) - mfp.wrap.addClass(_wrapClasses); - - - // this triggers recalculation of layout, so we get it once to not to trigger twice - var windowHeight = mfp.wH = _window.height(); - - - var windowStyles = {}; - - if( mfp.fixedContentPos ) { - if(mfp._hasScrollBar(windowHeight)){ - var s = mfp._getScrollbarSize(); - if(s) { - windowStyles.marginRight = s; - } - } - } - - if(mfp.fixedContentPos) { - if(!mfp.isIE7) { - windowStyles.overflow = 'hidden'; - } else { - // ie7 double-scroll bug - $('body, html').css('overflow', 'hidden'); - } - } - - - - var classesToadd = mfp.st.mainClass; - if(mfp.isIE7) { - classesToadd += ' mfp-ie7'; - } - if(classesToadd) { - mfp._addClassToMFP( classesToadd ); - } - - // add content - mfp.updateItemHTML(); - - _mfpTrigger('BuildControls'); - - // remove scrollbar, add margin e.t.c - $('html').css(windowStyles); - - // add everything to DOM - mfp.bgOverlay.add(mfp.wrap).prependTo( mfp.st.prependTo || $(document.body) ); - - // Save last focused element - mfp._lastFocusedEl = document.activeElement; - - // Wait for next cycle to allow CSS transition - setTimeout(function() { - - if(mfp.content) { - mfp._addClassToMFP(READY_CLASS); - mfp._setFocus(); - } else { - // if content is not defined (not loaded e.t.c) we add class only for BG - mfp.bgOverlay.addClass(READY_CLASS); - } - - // Trap the focus in popup - _document.on('focusin' + EVENT_NS, mfp._onFocusIn); - - }, 16); - - mfp.isOpen = true; - mfp.updateSize(windowHeight); - _mfpTrigger(OPEN_EVENT); - - return data; - }, - - /** - * Closes the popup - */ - close: function() { - if(!mfp.isOpen) return; - _mfpTrigger(BEFORE_CLOSE_EVENT); - - mfp.isOpen = false; - // for CSS3 animation - if(mfp.st.removalDelay && !mfp.isLowIE && mfp.supportsTransition ) { - mfp._addClassToMFP(REMOVING_CLASS); - setTimeout(function() { - mfp._close(); - }, mfp.st.removalDelay); - } else { - mfp._close(); - } - }, - - /** - * Helper for close() function - */ - _close: function() { - _mfpTrigger(CLOSE_EVENT); - - var classesToRemove = REMOVING_CLASS + ' ' + READY_CLASS + ' '; - - mfp.bgOverlay.detach(); - mfp.wrap.detach(); - mfp.container.empty(); - - if(mfp.st.mainClass) { - classesToRemove += mfp.st.mainClass + ' '; - } - - mfp._removeClassFromMFP(classesToRemove); - - if(mfp.fixedContentPos) { - var windowStyles = {marginRight: ''}; - if(mfp.isIE7) { - $('body, html').css('overflow', ''); - } else { - windowStyles.overflow = ''; - } - $('html').css(windowStyles); - } - - _document.off('keyup' + EVENT_NS + ' focusin' + EVENT_NS); - mfp.ev.off(EVENT_NS); - - // clean up DOM elements that aren't removed - mfp.wrap.attr('class', 'mfp-wrap').removeAttr('style'); - mfp.bgOverlay.attr('class', 'mfp-bg'); - mfp.container.attr('class', 'mfp-container'); - - // remove close button from target element - if(mfp.st.showCloseBtn && - (!mfp.st.closeBtnInside || mfp.currTemplate[mfp.currItem.type] === true)) { - if(mfp.currTemplate.closeBtn) - mfp.currTemplate.closeBtn.detach(); - } - - - if(mfp.st.autoFocusLast && mfp._lastFocusedEl) { - $(mfp._lastFocusedEl).focus(); // put tab focus back - } - mfp.currItem = null; - mfp.content = null; - mfp.currTemplate = null; - mfp.prevHeight = 0; - - _mfpTrigger(AFTER_CLOSE_EVENT); - }, - - updateSize: function(winHeight) { - - if(mfp.isIOS) { - // fixes iOS nav bars https://github.com/dimsemenov/Magnific-Popup/issues/2 - var zoomLevel = document.documentElement.clientWidth / window.innerWidth; - var height = window.innerHeight * zoomLevel; - mfp.wrap.css('height', height); - mfp.wH = height; - } else { - mfp.wH = winHeight || _window.height(); - } - // Fixes #84: popup incorrectly positioned with position:relative on body - if(!mfp.fixedContentPos) { - mfp.wrap.css('height', mfp.wH); - } - - _mfpTrigger('Resize'); - - }, - - /** - * Set content of popup based on current index - */ - updateItemHTML: function() { - var item = mfp.items[mfp.index]; - - // Detach and perform modifications - mfp.contentContainer.detach(); - - if(mfp.content) - mfp.content.detach(); - - if(!item.parsed) { - item = mfp.parseEl( mfp.index ); - } - - var type = item.type; - - _mfpTrigger('BeforeChange', [mfp.currItem ? mfp.currItem.type : '', type]); - // BeforeChange event works like so: - // _mfpOn('BeforeChange', function(e, prevType, newType) { }); - - mfp.currItem = item; - - if(!mfp.currTemplate[type]) { - var markup = mfp.st[type] ? mfp.st[type].markup : false; - - // allows to modify markup - _mfpTrigger('FirstMarkupParse', markup); - - if(markup) { - mfp.currTemplate[type] = $(markup); - } else { - // if there is no markup found we just define that template is parsed - mfp.currTemplate[type] = true; - } - } - - if(_prevContentType && _prevContentType !== item.type) { - mfp.container.removeClass('mfp-'+_prevContentType+'-holder'); - } - - var newContent = mfp['get' + type.charAt(0).toUpperCase() + type.slice(1)](item, mfp.currTemplate[type]); - mfp.appendContent(newContent, type); - - item.preloaded = true; - - _mfpTrigger(CHANGE_EVENT, item); - _prevContentType = item.type; - - // Append container back after its content changed - mfp.container.prepend(mfp.contentContainer); - - _mfpTrigger('AfterChange'); - }, - - - /** - * Set HTML content of popup - */ - appendContent: function(newContent, type) { - mfp.content = newContent; - - if(newContent) { - if(mfp.st.showCloseBtn && mfp.st.closeBtnInside && - mfp.currTemplate[type] === true) { - // if there is no markup, we just append close button element inside - if(!mfp.content.find('.mfp-close').length) { - mfp.content.append(_getCloseBtn()); - } - } else { - mfp.content = newContent; - } - } else { - mfp.content = ''; - } - - _mfpTrigger(BEFORE_APPEND_EVENT); - mfp.container.addClass('mfp-'+type+'-holder'); - - mfp.contentContainer.append(mfp.content); - }, - - - /** - * Creates Magnific Popup data object based on given data - * @param {int} index Index of item to parse - */ - parseEl: function(index) { - var item = mfp.items[index], - type; - - if(item.tagName) { - item = { el: $(item) }; - } else { - type = item.type; - item = { data: item, src: item.src }; - } - - if(item.el) { - var types = mfp.types; - - // check for 'mfp-TYPE' class - for(var i = 0; i < types.length; i++) { - if( item.el.hasClass('mfp-'+types[i]) ) { - type = types[i]; - break; - } - } - - item.src = item.el.attr('data-mfp-src'); - if(!item.src) { - item.src = item.el.attr('href'); - } - } - - item.type = type || mfp.st.type || 'inline'; - item.index = index; - item.parsed = true; - mfp.items[index] = item; - _mfpTrigger('ElementParse', item); - - return mfp.items[index]; - }, - - - /** - * Initializes single popup or a group of popups - */ - addGroup: function(el, options) { - var eHandler = function(e) { - e.mfpEl = this; - mfp._openClick(e, el, options); - }; - - if(!options) { - options = {}; - } - - var eName = 'click.magnificPopup'; - options.mainEl = el; - - if(options.items) { - options.isObj = true; - el.off(eName).on(eName, eHandler); - } else { - options.isObj = false; - if(options.delegate) { - el.off(eName).on(eName, options.delegate , eHandler); - } else { - options.items = el; - el.off(eName).on(eName, eHandler); - } - } - }, - _openClick: function(e, el, options) { - var midClick = options.midClick !== undefined ? options.midClick : $.magnificPopup.defaults.midClick; - - - if(!midClick && ( e.which === 2 || e.ctrlKey || e.metaKey || e.altKey || e.shiftKey ) ) { - return; - } - - var disableOn = options.disableOn !== undefined ? options.disableOn : $.magnificPopup.defaults.disableOn; - - if(disableOn) { - if($.isFunction(disableOn)) { - if( !disableOn.call(mfp) ) { - return true; - } - } else { // else it's number - if( _window.width() < disableOn ) { - return true; - } - } - } - - if(e.type) { - e.preventDefault(); - - // This will prevent popup from closing if element is inside and popup is already opened - if(mfp.isOpen) { - e.stopPropagation(); - } - } - - options.el = $(e.mfpEl); - if(options.delegate) { - options.items = el.find(options.delegate); - } - mfp.open(options); - }, - - - /** - * Updates text on preloader - */ - updateStatus: function(status, text) { - - if(mfp.preloader) { - if(_prevStatus !== status) { - mfp.container.removeClass('mfp-s-'+_prevStatus); - } - - if(!text && status === 'loading') { - text = mfp.st.tLoading; - } - - var data = { - status: status, - text: text - }; - // allows to modify status - _mfpTrigger('UpdateStatus', data); - - status = data.status; - text = data.text; - - mfp.preloader.html(text); - - mfp.preloader.find('a').on('click', function(e) { - e.stopImmediatePropagation(); - }); - - mfp.container.addClass('mfp-s-'+status); - _prevStatus = status; - } - }, - - - /* - "Private" helpers that aren't private at all - */ - // Check to close popup or not - // "target" is an element that was clicked - _checkIfClose: function(target) { - - if($(target).hasClass(PREVENT_CLOSE_CLASS)) { - return; - } - - var closeOnContent = mfp.st.closeOnContentClick; - var closeOnBg = mfp.st.closeOnBgClick; - - if(closeOnContent && closeOnBg) { - return true; - } else { - - // We close the popup if click is on close button or on preloader. Or if there is no content. - if(!mfp.content || $(target).hasClass('mfp-close') || (mfp.preloader && target === mfp.preloader[0]) ) { - return true; - } - - // if click is outside the content - if( (target !== mfp.content[0] && !$.contains(mfp.content[0], target)) ) { - if(closeOnBg) { - // last check, if the clicked element is in DOM, (in case it's removed onclick) - if( $.contains(document, target) ) { - return true; - } - } - } else if(closeOnContent) { - return true; - } - - } - return false; - }, - _addClassToMFP: function(cName) { - mfp.bgOverlay.addClass(cName); - mfp.wrap.addClass(cName); - }, - _removeClassFromMFP: function(cName) { - this.bgOverlay.removeClass(cName); - mfp.wrap.removeClass(cName); - }, - _hasScrollBar: function(winHeight) { - return ( (mfp.isIE7 ? _document.height() : document.body.scrollHeight) > (winHeight || _window.height()) ); - }, - _setFocus: function() { - (mfp.st.focus ? mfp.content.find(mfp.st.focus).eq(0) : mfp.wrap).focus(); - }, - _onFocusIn: function(e) { - if( e.target !== mfp.wrap[0] && !$.contains(mfp.wrap[0], e.target) ) { - mfp._setFocus(); - return false; - } - }, - _parseMarkup: function(template, values, item) { - var arr; - if(item.data) { - values = $.extend(item.data, values); - } - _mfpTrigger(MARKUP_PARSE_EVENT, [template, values, item] ); - - $.each(values, function(key, value) { - if(value === undefined || value === false) { - return true; - } - arr = key.split('_'); - if(arr.length > 1) { - var el = template.find(EVENT_NS + '-'+arr[0]); - - if(el.length > 0) { - var attr = arr[1]; - if(attr === 'replaceWith') { - if(el[0] !== value[0]) { - el.replaceWith(value); - } - } else if(attr === 'img') { - if(el.is('img')) { - el.attr('src', value); - } else { - el.replaceWith( $('').attr('src', value).attr('class', el.attr('class')) ); - } - } else { - el.attr(arr[1], value); - } - } - - } else { - template.find(EVENT_NS + '-'+key).html(value); - } - }); - }, - - _getScrollbarSize: function() { - // thx David - if(mfp.scrollbarSize === undefined) { - var scrollDiv = document.createElement("div"); - scrollDiv.style.cssText = 'width: 99px; height: 99px; overflow: scroll; position: absolute; top: -9999px;'; - document.body.appendChild(scrollDiv); - mfp.scrollbarSize = scrollDiv.offsetWidth - scrollDiv.clientWidth; - document.body.removeChild(scrollDiv); - } - return mfp.scrollbarSize; - } - - }; /* MagnificPopup core prototype end */ - - - - - /** - * Public static functions - */ - $.magnificPopup = { - instance: null, - proto: MagnificPopup.prototype, - modules: [], - - open: function(options, index) { - _checkInstance(); - - if(!options) { - options = {}; - } else { - options = $.extend(true, {}, options); - } - - options.isObj = true; - options.index = index || 0; - return this.instance.open(options); - }, - - close: function() { - return $.magnificPopup.instance && $.magnificPopup.instance.close(); - }, - - registerModule: function(name, module) { - if(module.options) { - $.magnificPopup.defaults[name] = module.options; - } - $.extend(this.proto, module.proto); - this.modules.push(name); - }, - - defaults: { - - // Info about options is in docs: - // http://dimsemenov.com/plugins/magnific-popup/documentation.html#options - - disableOn: 0, - - key: null, - - midClick: false, - - mainClass: '', - - preloader: true, - - focus: '', // CSS selector of input to focus after popup is opened - - closeOnContentClick: false, - - closeOnBgClick: true, - - closeBtnInside: true, - - showCloseBtn: true, - - enableEscapeKey: true, - - modal: false, - - alignTop: false, - - removalDelay: 0, - - prependTo: null, - - fixedContentPos: 'auto', - - fixedBgPos: 'auto', - - overflowY: 'auto', - - closeMarkup: '', - - tClose: 'Close (Esc)', - - tLoading: 'Loading...', - - autoFocusLast: true - - } - }; - - - - $.fn.magnificPopup = function(options) { - _checkInstance(); - - var jqEl = $(this); - - // We call some API method of first param is a string - if (typeof options === "string" ) { - - if(options === 'open') { - var items, - itemOpts = _isJQ ? jqEl.data('magnificPopup') : jqEl[0].magnificPopup, - index = parseInt(arguments[1], 10) || 0; - - if(itemOpts.items) { - items = itemOpts.items[index]; - } else { - items = jqEl; - if(itemOpts.delegate) { - items = items.find(itemOpts.delegate); - } - items = items.eq( index ); - } - mfp._openClick({mfpEl:items}, jqEl, itemOpts); - } else { - if(mfp.isOpen) - mfp[options].apply(mfp, Array.prototype.slice.call(arguments, 1)); - } - - } else { - // clone options obj - options = $.extend(true, {}, options); - - /* - * As Zepto doesn't support .data() method for objects - * and it works only in normal browsers - * we assign "options" object directly to the DOM element. FTW! - */ - if(_isJQ) { - jqEl.data('magnificPopup', options); - } else { - jqEl[0].magnificPopup = options; - } - - mfp.addGroup(jqEl, options); - - } - return jqEl; - }; - - /*>>core*/ - - /*>>inline*/ - - var INLINE_NS = 'inline', - _hiddenClass, - _inlinePlaceholder, - _lastInlineElement, - _putInlineElementsBack = function() { - if(_lastInlineElement) { - _inlinePlaceholder.after( _lastInlineElement.addClass(_hiddenClass) ).detach(); - _lastInlineElement = null; - } - }; - - $.magnificPopup.registerModule(INLINE_NS, { - options: { - hiddenClass: 'hide', // will be appended with `mfp-` prefix - markup: '', - tNotFound: 'Content not found' - }, - proto: { - - initInline: function() { - mfp.types.push(INLINE_NS); - - _mfpOn(CLOSE_EVENT+'.'+INLINE_NS, function() { - _putInlineElementsBack(); - }); - }, - - getInline: function(item, template) { - - _putInlineElementsBack(); - - if(item.src) { - var inlineSt = mfp.st.inline, - el = $(item.src); - - if(el.length) { - - // If target element has parent - we replace it with placeholder and put it back after popup is closed - var parent = el[0].parentNode; - if(parent && parent.tagName) { - if(!_inlinePlaceholder) { - _hiddenClass = inlineSt.hiddenClass; - _inlinePlaceholder = _getEl(_hiddenClass); - _hiddenClass = 'mfp-'+_hiddenClass; - } - // replace target inline element with placeholder - _lastInlineElement = el.after(_inlinePlaceholder).detach().removeClass(_hiddenClass); - } - - mfp.updateStatus('ready'); - } else { - mfp.updateStatus('error', inlineSt.tNotFound); - el = $('
'); - } - - item.inlineElement = el; - return el; - } - - mfp.updateStatus('ready'); - mfp._parseMarkup(template, {}, item); - return template; - } - } - }); - - /*>>inline*/ - - /*>>ajax*/ - var AJAX_NS = 'ajax', - _ajaxCur, - _removeAjaxCursor = function() { - if(_ajaxCur) { - $(document.body).removeClass(_ajaxCur); - } - }, - _destroyAjaxRequest = function() { - _removeAjaxCursor(); - if(mfp.req) { - mfp.req.abort(); - } - }; - - $.magnificPopup.registerModule(AJAX_NS, { - - options: { - settings: null, - cursor: 'mfp-ajax-cur', - tError: 'The content could not be loaded.' - }, - - proto: { - initAjax: function() { - mfp.types.push(AJAX_NS); - _ajaxCur = mfp.st.ajax.cursor; - - _mfpOn(CLOSE_EVENT+'.'+AJAX_NS, _destroyAjaxRequest); - _mfpOn('BeforeChange.' + AJAX_NS, _destroyAjaxRequest); - }, - getAjax: function(item) { - - if(_ajaxCur) { - $(document.body).addClass(_ajaxCur); - } - - mfp.updateStatus('loading'); - - var opts = $.extend({ - url: item.src, - success: function(data, textStatus, jqXHR) { - var temp = { - data:data, - xhr:jqXHR - }; - - _mfpTrigger('ParseAjax', temp); - - mfp.appendContent( $(temp.data), AJAX_NS ); - - item.finished = true; - - _removeAjaxCursor(); - - mfp._setFocus(); - - setTimeout(function() { - mfp.wrap.addClass(READY_CLASS); - }, 16); - - mfp.updateStatus('ready'); - - _mfpTrigger('AjaxContentAdded'); - }, - error: function() { - _removeAjaxCursor(); - item.finished = item.loadError = true; - mfp.updateStatus('error', mfp.st.ajax.tError.replace('%url%', item.src)); - } - }, mfp.st.ajax.settings); - - mfp.req = $.ajax(opts); - - return ''; - } - } - }); - - /*>>ajax*/ - - /*>>image*/ - var _imgInterval, - _getTitle = function(item) { - if(item.data && item.data.title !== undefined) - return item.data.title; - - var src = mfp.st.image.titleSrc; - - if(src) { - if($.isFunction(src)) { - return src.call(mfp, item); - } else if(item.el) { - return item.el.attr(src) || ''; - } - } - return ''; - }; - - $.magnificPopup.registerModule('image', { - - options: { - markup: '
'+ - '
'+ - '
'+ - '
'+ - '
'+ - '
'+ - '
'+ - '
'+ - '
'+ - '
'+ - '
'+ - '
', - cursor: 'mfp-zoom-out-cur', - titleSrc: 'title', - verticalFit: true, - tError: 'The image could not be loaded.' - }, - - proto: { - initImage: function() { - var imgSt = mfp.st.image, - ns = '.image'; - - mfp.types.push('image'); - - _mfpOn(OPEN_EVENT+ns, function() { - if(mfp.currItem.type === 'image' && imgSt.cursor) { - $(document.body).addClass(imgSt.cursor); - } - }); - - _mfpOn(CLOSE_EVENT+ns, function() { - if(imgSt.cursor) { - $(document.body).removeClass(imgSt.cursor); - } - _window.off('resize' + EVENT_NS); - }); - - _mfpOn('Resize'+ns, mfp.resizeImage); - if(mfp.isLowIE) { - _mfpOn('AfterChange', mfp.resizeImage); - } - }, - resizeImage: function() { - var item = mfp.currItem; - if(!item || !item.img) return; - - if(mfp.st.image.verticalFit) { - var decr = 0; - // fix box-sizing in ie7/8 - if(mfp.isLowIE) { - decr = parseInt(item.img.css('padding-top'), 10) + parseInt(item.img.css('padding-bottom'),10); - } - item.img.css('max-height', mfp.wH-decr); - } - }, - _onImageHasSize: function(item) { - if(item.img) { - - item.hasSize = true; - - if(_imgInterval) { - clearInterval(_imgInterval); - } - - item.isCheckingImgSize = false; - - _mfpTrigger('ImageHasSize', item); - - if(item.imgHidden) { - if(mfp.content) - mfp.content.removeClass('mfp-loading'); - - item.imgHidden = false; - } - - } - }, - - /** - * Function that loops until the image has size to display elements that rely on it asap - */ - findImageSize: function(item) { - - var counter = 0, - img = item.img[0], - mfpSetInterval = function(delay) { - - if(_imgInterval) { - clearInterval(_imgInterval); - } - // decelerating interval that checks for size of an image - _imgInterval = setInterval(function() { - if(img.naturalWidth > 0) { - mfp._onImageHasSize(item); - return; - } - - if(counter > 200) { - clearInterval(_imgInterval); - } - - counter++; - if(counter === 3) { - mfpSetInterval(10); - } else if(counter === 40) { - mfpSetInterval(50); - } else if(counter === 100) { - mfpSetInterval(500); - } - }, delay); - }; - - mfpSetInterval(1); - }, - - getImage: function(item, template) { - - var guard = 0, - - // image load complete handler - onLoadComplete = function() { - if(item) { - if (item.img[0].complete) { - item.img.off('.mfploader'); - - if(item === mfp.currItem){ - mfp._onImageHasSize(item); - - mfp.updateStatus('ready'); - } - - item.hasSize = true; - item.loaded = true; - - _mfpTrigger('ImageLoadComplete'); - - } - else { - // if image complete check fails 200 times (20 sec), we assume that there was an error. - guard++; - if(guard < 200) { - setTimeout(onLoadComplete,100); - } else { - onLoadError(); - } - } - } - }, - - // image error handler - onLoadError = function() { - if(item) { - item.img.off('.mfploader'); - if(item === mfp.currItem){ - mfp._onImageHasSize(item); - mfp.updateStatus('error', imgSt.tError.replace('%url%', item.src) ); - } - - item.hasSize = true; - item.loaded = true; - item.loadError = true; - } - }, - imgSt = mfp.st.image; - - - var el = template.find('.mfp-img'); - if(el.length) { - var img = document.createElement('img'); - img.className = 'mfp-img'; - if(item.el && item.el.find('img').length) { - img.alt = item.el.find('img').attr('alt'); - } - item.img = $(img).on('load.mfploader', onLoadComplete).on('error.mfploader', onLoadError); - img.src = item.src; - - // without clone() "error" event is not firing when IMG is replaced by new IMG - // TODO: find a way to avoid such cloning - if(el.is('img')) { - item.img = item.img.clone(); - } - - img = item.img[0]; - if(img.naturalWidth > 0) { - item.hasSize = true; - } else if(!img.width) { - item.hasSize = false; - } - } - - mfp._parseMarkup(template, { - title: _getTitle(item), - img_replaceWith: item.img - }, item); - - mfp.resizeImage(); - - if(item.hasSize) { - if(_imgInterval) clearInterval(_imgInterval); - - if(item.loadError) { - template.addClass('mfp-loading'); - mfp.updateStatus('error', imgSt.tError.replace('%url%', item.src) ); - } else { - template.removeClass('mfp-loading'); - mfp.updateStatus('ready'); - } - return template; - } - - mfp.updateStatus('loading'); - item.loading = true; - - if(!item.hasSize) { - item.imgHidden = true; - template.addClass('mfp-loading'); - mfp.findImageSize(item); - } - - return template; - } - } - }); - - /*>>image*/ - - /*>>zoom*/ - var hasMozTransform, - getHasMozTransform = function() { - if(hasMozTransform === undefined) { - hasMozTransform = document.createElement('p').style.MozTransform !== undefined; - } - return hasMozTransform; - }; - - $.magnificPopup.registerModule('zoom', { - - options: { - enabled: false, - easing: 'ease-in-out', - duration: 300, - opener: function(element) { - return element.is('img') ? element : element.find('img'); - } - }, - - proto: { - - initZoom: function() { - var zoomSt = mfp.st.zoom, - ns = '.zoom', - image; - - if(!zoomSt.enabled || !mfp.supportsTransition) { - return; - } - - var duration = zoomSt.duration, - getElToAnimate = function(image) { - var newImg = image.clone().removeAttr('style').removeAttr('class').addClass('mfp-animated-image'), - transition = 'all '+(zoomSt.duration/1000)+'s ' + zoomSt.easing, - cssObj = { - position: 'fixed', - zIndex: 9999, - left: 0, - top: 0, - '-webkit-backface-visibility': 'hidden' - }, - t = 'transition'; - - cssObj['-webkit-'+t] = cssObj['-moz-'+t] = cssObj['-o-'+t] = cssObj[t] = transition; - - newImg.css(cssObj); - return newImg; - }, - showMainContent = function() { - mfp.content.css('visibility', 'visible'); - }, - openTimeout, - animatedImg; - - _mfpOn('BuildControls'+ns, function() { - if(mfp._allowZoom()) { - - clearTimeout(openTimeout); - mfp.content.css('visibility', 'hidden'); - - // Basically, all code below does is clones existing image, puts in on top of the current one and animated it - - image = mfp._getItemToZoom(); - - if(!image) { - showMainContent(); - return; - } - - animatedImg = getElToAnimate(image); - - animatedImg.css( mfp._getOffset() ); - - mfp.wrap.append(animatedImg); - - openTimeout = setTimeout(function() { - animatedImg.css( mfp._getOffset( true ) ); - openTimeout = setTimeout(function() { - - showMainContent(); - - setTimeout(function() { - animatedImg.remove(); - image = animatedImg = null; - _mfpTrigger('ZoomAnimationEnded'); - }, 16); // avoid blink when switching images - - }, duration); // this timeout equals animation duration - - }, 16); // by adding this timeout we avoid short glitch at the beginning of animation - - - // Lots of timeouts... - } - }); - _mfpOn(BEFORE_CLOSE_EVENT+ns, function() { - if(mfp._allowZoom()) { - - clearTimeout(openTimeout); - - mfp.st.removalDelay = duration; - - if(!image) { - image = mfp._getItemToZoom(); - if(!image) { - return; - } - animatedImg = getElToAnimate(image); - } - - animatedImg.css( mfp._getOffset(true) ); - mfp.wrap.append(animatedImg); - mfp.content.css('visibility', 'hidden'); - - setTimeout(function() { - animatedImg.css( mfp._getOffset() ); - }, 16); - } - - }); - - _mfpOn(CLOSE_EVENT+ns, function() { - if(mfp._allowZoom()) { - showMainContent(); - if(animatedImg) { - animatedImg.remove(); - } - image = null; - } - }); - }, - - _allowZoom: function() { - return mfp.currItem.type === 'image'; - }, - - _getItemToZoom: function() { - if(mfp.currItem.hasSize) { - return mfp.currItem.img; - } else { - return false; - } - }, - - // Get element postion relative to viewport - _getOffset: function(isLarge) { - var el; - if(isLarge) { - el = mfp.currItem.img; - } else { - el = mfp.st.zoom.opener(mfp.currItem.el || mfp.currItem); - } - - var offset = el.offset(); - var paddingTop = parseInt(el.css('padding-top'),10); - var paddingBottom = parseInt(el.css('padding-bottom'),10); - offset.top -= ( $(window).scrollTop() - paddingTop ); - - - /* - - Animating left + top + width/height looks glitchy in Firefox, but perfect in Chrome. And vice-versa. - - */ - var obj = { - width: el.width(), - // fix Zepto height+padding issue - height: (_isJQ ? el.innerHeight() : el[0].offsetHeight) - paddingBottom - paddingTop - }; - - // I hate to do this, but there is no another option - if( getHasMozTransform() ) { - obj['-moz-transform'] = obj['transform'] = 'translate(' + offset.left + 'px,' + offset.top + 'px)'; - } else { - obj.left = offset.left; - obj.top = offset.top; - } - return obj; - } - - } - }); - - - - /*>>zoom*/ - - /*>>iframe*/ - - var IFRAME_NS = 'iframe', - _emptyPage = '//about:blank', - - _fixIframeBugs = function(isShowing) { - if(mfp.currTemplate[IFRAME_NS]) { - var el = mfp.currTemplate[IFRAME_NS].find('iframe'); - if(el.length) { - // reset src after the popup is closed to avoid "video keeps playing after popup is closed" bug - if(!isShowing) { - el[0].src = _emptyPage; - } - - // IE8 black screen bug fix - if(mfp.isIE8) { - el.css('display', isShowing ? 'block' : 'none'); - } - } - } - }; - - $.magnificPopup.registerModule(IFRAME_NS, { - - options: { - markup: '
'+ - '
'+ - ''+ - '
', - - srcAction: 'iframe_src', - - // we don't care and support only one default type of URL by default - patterns: { - youtube: { - index: 'youtube.com', - id: 'v=', - src: '//www.youtube.com/embed/%id%?autoplay=1' - }, - vimeo: { - index: 'vimeo.com/', - id: '/', - src: '//player.vimeo.com/video/%id%?autoplay=1' - }, - gmaps: { - index: '//maps.google.', - src: '%id%&output=embed' - } - } - }, - - proto: { - initIframe: function() { - mfp.types.push(IFRAME_NS); - - _mfpOn('BeforeChange', function(e, prevType, newType) { - if(prevType !== newType) { - if(prevType === IFRAME_NS) { - _fixIframeBugs(); // iframe if removed - } else if(newType === IFRAME_NS) { - _fixIframeBugs(true); // iframe is showing - } - }// else { - // iframe source is switched, don't do anything - //} - }); - - _mfpOn(CLOSE_EVENT + '.' + IFRAME_NS, function() { - _fixIframeBugs(); - }); - }, - - getIframe: function(item, template) { - var embedSrc = item.src; - var iframeSt = mfp.st.iframe; - - $.each(iframeSt.patterns, function() { - if(embedSrc.indexOf( this.index ) > -1) { - if(this.id) { - if(typeof this.id === 'string') { - embedSrc = embedSrc.substr(embedSrc.lastIndexOf(this.id)+this.id.length, embedSrc.length); - } else { - embedSrc = this.id.call( this, embedSrc ); - } - } - embedSrc = this.src.replace('%id%', embedSrc ); - return false; // break; - } - }); - - var dataObj = {}; - if(iframeSt.srcAction) { - dataObj[iframeSt.srcAction] = embedSrc; - } - mfp._parseMarkup(template, dataObj, item); - - mfp.updateStatus('ready'); - - return template; - } - } - }); - - - - /*>>iframe*/ - - /*>>gallery*/ - /** - * Get looped index depending on number of slides - */ - var _getLoopedId = function(index) { - var numSlides = mfp.items.length; - if(index > numSlides - 1) { - return index - numSlides; - } else if(index < 0) { - return numSlides + index; - } - return index; - }, - _replaceCurrTotal = function(text, curr, total) { - return text.replace(/%curr%/gi, curr + 1).replace(/%total%/gi, total); - }; - - $.magnificPopup.registerModule('gallery', { - - options: { - enabled: false, - arrowMarkup: '', - preload: [0,2], - navigateByImgClick: true, - arrows: true, - - tPrev: 'Previous (Left arrow key)', - tNext: 'Next (Right arrow key)', - tCounter: '%curr% of %total%' - }, - - proto: { - initGallery: function() { - - var gSt = mfp.st.gallery, - ns = '.mfp-gallery'; - - mfp.direction = true; // true - next, false - prev - - if(!gSt || !gSt.enabled ) return false; - - _wrapClasses += ' mfp-gallery'; - - _mfpOn(OPEN_EVENT+ns, function() { - - if(gSt.navigateByImgClick) { - mfp.wrap.on('click'+ns, '.mfp-img', function() { - if(mfp.items.length > 1) { - mfp.next(); - return false; - } - }); - } - - _document.on('keydown'+ns, function(e) { - if (e.keyCode === 37) { - mfp.prev(); - } else if (e.keyCode === 39) { - mfp.next(); - } - }); - }); - - _mfpOn('UpdateStatus'+ns, function(e, data) { - if(data.text) { - data.text = _replaceCurrTotal(data.text, mfp.currItem.index, mfp.items.length); - } - }); - - _mfpOn(MARKUP_PARSE_EVENT+ns, function(e, element, values, item) { - var l = mfp.items.length; - values.counter = l > 1 ? _replaceCurrTotal(gSt.tCounter, item.index, l) : ''; - }); - - _mfpOn('BuildControls' + ns, function() { - if(mfp.items.length > 1 && gSt.arrows && !mfp.arrowLeft) { - var markup = gSt.arrowMarkup, - arrowLeft = mfp.arrowLeft = $( markup.replace(/%title%/gi, gSt.tPrev).replace(/%dir%/gi, 'left') ).addClass(PREVENT_CLOSE_CLASS), - arrowRight = mfp.arrowRight = $( markup.replace(/%title%/gi, gSt.tNext).replace(/%dir%/gi, 'right') ).addClass(PREVENT_CLOSE_CLASS); - - arrowLeft.click(function() { - mfp.prev(); - }); - arrowRight.click(function() { - mfp.next(); - }); - - mfp.container.append(arrowLeft.add(arrowRight)); - } - }); - - _mfpOn(CHANGE_EVENT+ns, function() { - if(mfp._preloadTimeout) clearTimeout(mfp._preloadTimeout); - - mfp._preloadTimeout = setTimeout(function() { - mfp.preloadNearbyImages(); - mfp._preloadTimeout = null; - }, 16); - }); - - - _mfpOn(CLOSE_EVENT+ns, function() { - _document.off(ns); - mfp.wrap.off('click'+ns); - mfp.arrowRight = mfp.arrowLeft = null; - }); - - }, - next: function() { - mfp.direction = true; - mfp.index = _getLoopedId(mfp.index + 1); - mfp.updateItemHTML(); - }, - prev: function() { - mfp.direction = false; - mfp.index = _getLoopedId(mfp.index - 1); - mfp.updateItemHTML(); - }, - goTo: function(newIndex) { - mfp.direction = (newIndex >= mfp.index); - mfp.index = newIndex; - mfp.updateItemHTML(); - }, - preloadNearbyImages: function() { - var p = mfp.st.gallery.preload, - preloadBefore = Math.min(p[0], mfp.items.length), - preloadAfter = Math.min(p[1], mfp.items.length), - i; - - for(i = 1; i <= (mfp.direction ? preloadAfter : preloadBefore); i++) { - mfp._preloadItem(mfp.index+i); - } - for(i = 1; i <= (mfp.direction ? preloadBefore : preloadAfter); i++) { - mfp._preloadItem(mfp.index-i); - } - }, - _preloadItem: function(index) { - index = _getLoopedId(index); - - if(mfp.items[index].preloaded) { - return; - } - - var item = mfp.items[index]; - if(!item.parsed) { - item = mfp.parseEl( index ); - } - - _mfpTrigger('LazyLoad', item); - - if(item.type === 'image') { - item.img = $('').on('load.mfploader', function() { - item.hasSize = true; - }).on('error.mfploader', function() { - item.hasSize = true; - item.loadError = true; - _mfpTrigger('LazyLoadError', item); - }).attr('src', item.src); - } - - - item.preloaded = true; - } - } - }); - - /*>>gallery*/ - - /*>>retina*/ - - var RETINA_NS = 'retina'; - - $.magnificPopup.registerModule(RETINA_NS, { - options: { - replaceSrc: function(item) { - return item.src.replace(/\.\w+$/, function(m) { return '@2x' + m; }); - }, - ratio: 1 // Function or number. Set to 1 to disable. - }, - proto: { - initRetina: function() { - if(window.devicePixelRatio > 1) { - - var st = mfp.st.retina, - ratio = st.ratio; - - ratio = !isNaN(ratio) ? ratio : ratio(); - - if(ratio > 1) { - _mfpOn('ImageHasSize' + '.' + RETINA_NS, function(e, item) { - item.img.css({ - 'max-width': item.img[0].naturalWidth / ratio, - 'width': '100%' - }); - }); - _mfpOn('ElementParse' + '.' + RETINA_NS, function(e, item) { - item.src = st.replaceSrc(item, ratio); - }); - } - } - - } - } - }); - - /*>>retina*/ - _checkInstance(); })); diff --git a/assets/js/plugins/smooth-scroll.js b/assets/js/plugins/smooth-scroll.js deleted file mode 100644 index c4179a73..00000000 --- a/assets/js/plugins/smooth-scroll.js +++ /dev/null @@ -1,650 +0,0 @@ -/*! - * smooth-scroll v16.1.2 - * Animate scrolling to anchor links - * (c) 2020 Chris Ferdinandi - * MIT License - * http://github.com/cferdinandi/smooth-scroll - */ - -(function (root, factory) { - if (typeof define === 'function' && define.amd) { - define([], (function () { - return factory(root); - })); - } else if (typeof exports === 'object') { - module.exports = factory(root); - } else { - root.SmoothScroll = factory(root); - } -})(typeof global !== 'undefined' ? global : typeof window !== 'undefined' ? window : this, (function (window) { - - 'use strict'; - - // - // Default settings - // - - var defaults = { - - // Selectors - ignore: '[data-scroll-ignore]', - header: null, - topOnEmptyHash: true, - - // Speed & Duration - speed: 500, - speedAsDuration: false, - durationMax: null, - durationMin: null, - clip: true, - offset: 0, - - // Easing - easing: 'easeInOutCubic', - customEasing: null, - - // History - updateURL: true, - popstate: true, - - // Custom Events - emitEvents: true - - }; - - - // - // Utility Methods - // - - /** - * Check if browser supports required methods - * @return {Boolean} Returns true if all required methods are supported - */ - var supports = function () { - return ( - 'querySelector' in document && - 'addEventListener' in window && - 'requestAnimationFrame' in window && - 'closest' in window.Element.prototype - ); - }; - - /** - * Merge two or more objects together. - * @param {Object} objects The objects to merge together - * @returns {Object} Merged values of defaults and options - */ - var extend = function () { - var merged = {}; - Array.prototype.forEach.call(arguments, (function (obj) { - for (var key in obj) { - if (!obj.hasOwnProperty(key)) return; - merged[key] = obj[key]; - } - })); - return merged; - }; - - /** - * Check to see if user prefers reduced motion - * @param {Object} settings Script settings - */ - var reduceMotion = function () { - if ('matchMedia' in window && window.matchMedia('(prefers-reduced-motion)').matches) { - return true; - } - return false; - }; - - /** - * Get the height of an element. - * @param {Node} elem The element to get the height of - * @return {Number} The element's height in pixels - */ - var getHeight = function (elem) { - return parseInt(window.getComputedStyle(elem).height, 10); - }; - - /** - * Escape special characters for use with querySelector - * @author Mathias Bynens - * @link https://github.com/mathiasbynens/CSS.escape - * @param {String} id The anchor ID to escape - */ - var escapeCharacters = function (id) { - - // Remove leading hash - if (id.charAt(0) === '#') { - id = id.substr(1); - } - - var string = String(id); - var length = string.length; - var index = -1; - var codeUnit; - var result = ''; - var firstCodeUnit = string.charCodeAt(0); - while (++index < length) { - codeUnit = string.charCodeAt(index); - // Note: there’s no need to special-case astral symbols, surrogate - // pairs, or lone surrogates. - - // If the character is NULL (U+0000), then throw an - // `InvalidCharacterError` exception and terminate these steps. - if (codeUnit === 0x0000) { - throw new InvalidCharacterError( - 'Invalid character: the input contains U+0000.' - ); - } - - if ( - // If the character is in the range [\1-\1F] (U+0001 to U+001F) or is - // U+007F, […] - (codeUnit >= 0x0001 && codeUnit <= 0x001F) || codeUnit == 0x007F || - // If the character is the first character and is in the range [0-9] - // (U+0030 to U+0039), […] - (index === 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039) || - // If the character is the second character and is in the range [0-9] - // (U+0030 to U+0039) and the first character is a `-` (U+002D), […] - ( - index === 1 && - codeUnit >= 0x0030 && codeUnit <= 0x0039 && - firstCodeUnit === 0x002D - ) - ) { - // http://dev.w3.org/csswg/cssom/#escape-a-character-as-code-point - result += '\\' + codeUnit.toString(16) + ' '; - continue; - } - - // If the character is not handled by one of the above rules and is - // greater than or equal to U+0080, is `-` (U+002D) or `_` (U+005F), or - // is in one of the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to - // U+005A), or [a-z] (U+0061 to U+007A), […] - if ( - codeUnit >= 0x0080 || - codeUnit === 0x002D || - codeUnit === 0x005F || - codeUnit >= 0x0030 && codeUnit <= 0x0039 || - codeUnit >= 0x0041 && codeUnit <= 0x005A || - codeUnit >= 0x0061 && codeUnit <= 0x007A - ) { - // the character itself - result += string.charAt(index); - continue; - } - - // Otherwise, the escaped character. - // http://dev.w3.org/csswg/cssom/#escape-a-character - result += '\\' + string.charAt(index); - - } - - // Return sanitized hash - return '#' + result; - - }; - - /** - * Calculate the easing pattern - * @link https://gist.github.com/gre/1650294 - * @param {String} type Easing pattern - * @param {Number} time Time animation should take to complete - * @returns {Number} - */ - var easingPattern = function (settings, time) { - var pattern; - - // Default Easing Patterns - if (settings.easing === 'easeInQuad') pattern = time * time; // accelerating from zero velocity - if (settings.easing === 'easeOutQuad') pattern = time * (2 - time); // decelerating to zero velocity - if (settings.easing === 'easeInOutQuad') pattern = time < 0.5 ? 2 * time * time : -1 + (4 - 2 * time) * time; // acceleration until halfway, then deceleration - if (settings.easing === 'easeInCubic') pattern = time * time * time; // accelerating from zero velocity - if (settings.easing === 'easeOutCubic') pattern = (--time) * time * time + 1; // decelerating to zero velocity - if (settings.easing === 'easeInOutCubic') pattern = time < 0.5 ? 4 * time * time * time : (time - 1) * (2 * time - 2) * (2 * time - 2) + 1; // acceleration until halfway, then deceleration - if (settings.easing === 'easeInQuart') pattern = time * time * time * time; // accelerating from zero velocity - if (settings.easing === 'easeOutQuart') pattern = 1 - (--time) * time * time * time; // decelerating to zero velocity - if (settings.easing === 'easeInOutQuart') pattern = time < 0.5 ? 8 * time * time * time * time : 1 - 8 * (--time) * time * time * time; // acceleration until halfway, then deceleration - if (settings.easing === 'easeInQuint') pattern = time * time * time * time * time; // accelerating from zero velocity - if (settings.easing === 'easeOutQuint') pattern = 1 + (--time) * time * time * time * time; // decelerating to zero velocity - if (settings.easing === 'easeInOutQuint') pattern = time < 0.5 ? 16 * time * time * time * time * time : 1 + 16 * (--time) * time * time * time * time; // acceleration until halfway, then deceleration - - // Custom Easing Patterns - if (!!settings.customEasing) pattern = settings.customEasing(time); - - return pattern || time; // no easing, no acceleration - }; - - /** - * Determine the document's height - * @returns {Number} - */ - var getDocumentHeight = function () { - return Math.max( - document.body.scrollHeight, document.documentElement.scrollHeight, - document.body.offsetHeight, document.documentElement.offsetHeight, - document.body.clientHeight, document.documentElement.clientHeight - ); - }; - - /** - * Calculate how far to scroll - * Clip support added by robjtede - https://github.com/cferdinandi/smooth-scroll/issues/405 - * @param {Element} anchor The anchor element to scroll to - * @param {Number} headerHeight Height of a fixed header, if any - * @param {Number} offset Number of pixels by which to offset scroll - * @param {Boolean} clip If true, adjust scroll distance to prevent abrupt stops near the bottom of the page - * @returns {Number} - */ - var getEndLocation = function (anchor, headerHeight, offset, clip) { - var location = 0; - if (anchor.offsetParent) { - do { - location += anchor.offsetTop; - anchor = anchor.offsetParent; - } while (anchor); - } - location = Math.max(location - headerHeight - offset, 0); - if (clip) { - location = Math.min(location, getDocumentHeight() - window.innerHeight); - } - return location; - }; - - /** - * Get the height of the fixed header - * @param {Node} header The header - * @return {Number} The height of the header - */ - var getHeaderHeight = function (header) { - return !header ? 0 : (getHeight(header) + header.offsetTop); - }; - - /** - * Calculate the speed to use for the animation - * @param {Number} distance The distance to travel - * @param {Object} settings The plugin settings - * @return {Number} How fast to animate - */ - var getSpeed = function (distance, settings) { - var speed = settings.speedAsDuration ? settings.speed : Math.abs(distance / 1000 * settings.speed); - if (settings.durationMax && speed > settings.durationMax) return settings.durationMax; - if (settings.durationMin && speed < settings.durationMin) return settings.durationMin; - return parseInt(speed, 10); - }; - - var setHistory = function (options) { - - // Make sure this should run - if (!history.replaceState || !options.updateURL || history.state) return; - - // Get the hash to use - var hash = window.location.hash; - hash = hash ? hash : ''; - - // Set a default history - history.replaceState( - { - smoothScroll: JSON.stringify(options), - anchor: hash ? hash : window.pageYOffset - }, - document.title, - hash ? hash : window.location.href - ); - - }; - - /** - * Update the URL - * @param {Node} anchor The anchor that was scrolled to - * @param {Boolean} isNum If true, anchor is a number - * @param {Object} options Settings for Smooth Scroll - */ - var updateURL = function (anchor, isNum, options) { - - // Bail if the anchor is a number - if (isNum) return; - - // Verify that pushState is supported and the updateURL option is enabled - if (!history.pushState || !options.updateURL) return; - - // Update URL - history.pushState( - { - smoothScroll: JSON.stringify(options), - anchor: anchor.id - }, - document.title, - anchor === document.documentElement ? '#top' : '#' + anchor.id - ); - - }; - - /** - * Bring the anchored element into focus - * @param {Node} anchor The anchor element - * @param {Number} endLocation The end location to scroll to - * @param {Boolean} isNum If true, scroll is to a position rather than an element - */ - var adjustFocus = function (anchor, endLocation, isNum) { - - // Is scrolling to top of page, blur - if (anchor === 0) { - document.body.focus(); - } - - // Don't run if scrolling to a number on the page - if (isNum) return; - - // Otherwise, bring anchor element into focus - anchor.focus(); - if (document.activeElement !== anchor) { - anchor.setAttribute('tabindex', '-1'); - anchor.focus(); - anchor.style.outline = 'none'; - } - window.scrollTo(0 , endLocation); - - }; - - /** - * Emit a custom event - * @param {String} type The event type - * @param {Object} options The settings object - * @param {Node} anchor The anchor element - * @param {Node} toggle The toggle element - */ - var emitEvent = function (type, options, anchor, toggle) { - if (!options.emitEvents || typeof window.CustomEvent !== 'function') return; - var event = new CustomEvent(type, { - bubbles: true, - detail: { - anchor: anchor, - toggle: toggle - } - }); - document.dispatchEvent(event); - }; - - - // - // SmoothScroll Constructor - // - - var SmoothScroll = function (selector, options) { - - // - // Variables - // - - var smoothScroll = {}; // Object for public APIs - var settings, anchor, toggle, fixedHeader, eventTimeout, animationInterval; - - - // - // Methods - // - - /** - * Cancel a scroll-in-progress - */ - smoothScroll.cancelScroll = function (noEvent) { - cancelAnimationFrame(animationInterval); - animationInterval = null; - if (noEvent) return; - emitEvent('scrollCancel', settings); - }; - - /** - * Start/stop the scrolling animation - * @param {Node|Number} anchor The element or position to scroll to - * @param {Element} toggle The element that toggled the scroll event - * @param {Object} options - */ - smoothScroll.animateScroll = function (anchor, toggle, options) { - - // Cancel any in progress scrolls - smoothScroll.cancelScroll(); - - // Local settings - var _settings = extend(settings || defaults, options || {}); // Merge user options with defaults - - // Selectors and variables - var isNum = Object.prototype.toString.call(anchor) === '[object Number]' ? true : false; - var anchorElem = isNum || !anchor.tagName ? null : anchor; - if (!isNum && !anchorElem) return; - var startLocation = window.pageYOffset; // Current location on the page - if (_settings.header && !fixedHeader) { - // Get the fixed header if not already set - fixedHeader = document.querySelector(_settings.header); - } - var headerHeight = getHeaderHeight(fixedHeader); - var endLocation = isNum ? anchor : getEndLocation(anchorElem, headerHeight, parseInt((typeof _settings.offset === 'function' ? _settings.offset(anchor, toggle) : _settings.offset), 10), _settings.clip); // Location to scroll to - var distance = endLocation - startLocation; // distance to travel - var documentHeight = getDocumentHeight(); - var timeLapsed = 0; - var speed = getSpeed(distance, _settings); - var start, percentage, position; - - /** - * Stop the scroll animation when it reaches its target (or the bottom/top of page) - * @param {Number} position Current position on the page - * @param {Number} endLocation Scroll to location - * @param {Number} animationInterval How much to scroll on this loop - */ - var stopAnimateScroll = function (position, endLocation) { - - // Get the current location - var currentLocation = window.pageYOffset; - - // Check if the end location has been reached yet (or we've hit the end of the document) - if (position == endLocation || currentLocation == endLocation || ((startLocation < endLocation && window.innerHeight + currentLocation) >= documentHeight)) { - - // Clear the animation timer - smoothScroll.cancelScroll(true); - - // Bring the anchored element into focus - adjustFocus(anchor, endLocation, isNum); - - // Emit a custom event - emitEvent('scrollStop', _settings, anchor, toggle); - - // Reset start - start = null; - animationInterval = null; - - return true; - - } - }; - - /** - * Loop scrolling animation - */ - var loopAnimateScroll = function (timestamp) { - if (!start) { start = timestamp; } - timeLapsed += timestamp - start; - percentage = speed === 0 ? 0 : (timeLapsed / speed); - percentage = (percentage > 1) ? 1 : percentage; - position = startLocation + (distance * easingPattern(_settings, percentage)); - window.scrollTo(0, Math.floor(position)); - if (!stopAnimateScroll(position, endLocation)) { - animationInterval = window.requestAnimationFrame(loopAnimateScroll); - start = timestamp; - } - }; - - /** - * Reset position to fix weird iOS bug - * @link https://github.com/cferdinandi/smooth-scroll/issues/45 - */ - if (window.pageYOffset === 0) { - window.scrollTo(0, 0); - } - - // Update the URL - updateURL(anchor, isNum, _settings); - - // If the user prefers reduced motion, jump to location - if (reduceMotion()) { - window.scrollTo(0, Math.floor(endLocation)); - return; - } - - // Emit a custom event - emitEvent('scrollStart', _settings, anchor, toggle); - - // Start scrolling animation - smoothScroll.cancelScroll(true); - window.requestAnimationFrame(loopAnimateScroll); - - }; - - /** - * If smooth scroll element clicked, animate scroll - */ - var clickHandler = function (event) { - - // Don't run if event was canceled but still bubbled up - // By @mgreter - https://github.com/cferdinandi/smooth-scroll/pull/462/ - if (event.defaultPrevented) return; - - // Don't run if right-click or command/control + click or shift + click - if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey) return; - - // Check if event.target has closest() method - // By @totegi - https://github.com/cferdinandi/smooth-scroll/pull/401/ - if (!('closest' in event.target)) return; - - // Check if a smooth scroll link was clicked - toggle = event.target.closest(selector); - if (!toggle || toggle.tagName.toLowerCase() !== 'a' || event.target.closest(settings.ignore)) return; - - // Only run if link is an anchor and points to the current page - if (toggle.hostname !== window.location.hostname || toggle.pathname !== window.location.pathname || !/#/.test(toggle.href)) return; - - // Get an escaped version of the hash - var hash; - try { - hash = escapeCharacters(decodeURIComponent(toggle.hash)); - } catch(e) { - hash = escapeCharacters(toggle.hash); - } - - // Get the anchored element - var anchor; - if (hash === '#') { - if (!settings.topOnEmptyHash) return; - anchor = document.documentElement; - } else { - anchor = document.querySelector(hash); - } - anchor = !anchor && hash === '#top' ? document.documentElement : anchor; - - // If anchored element exists, scroll to it - if (!anchor) return; - event.preventDefault(); - setHistory(settings); - smoothScroll.animateScroll(anchor, toggle); - - }; - - /** - * Animate scroll on popstate events - */ - var popstateHandler = function (event) { - - // Stop if history.state doesn't exist (ex. if clicking on a broken anchor link). - // fixes `Cannot read property 'smoothScroll' of null` error getting thrown. - if (history.state === null) return; - - // Only run if state is a popstate record for this instantiation - if (!history.state.smoothScroll || history.state.smoothScroll !== JSON.stringify(settings)) return; - - // Only run if state includes an anchor - - // if (!history.state.anchor && history.state.anchor !== 0) return; - - // Get the anchor - var anchor = history.state.anchor; - if (typeof anchor === 'string' && anchor) { - anchor = document.querySelector(escapeCharacters(history.state.anchor)); - if (!anchor) return; - } - - // Animate scroll to anchor link - smoothScroll.animateScroll(anchor, null, {updateURL: false}); - - }; - - /** - * Destroy the current initialization. - */ - smoothScroll.destroy = function () { - - // If plugin isn't already initialized, stop - if (!settings) return; - - // Remove event listeners - document.removeEventListener('click', clickHandler, false); - window.removeEventListener('popstate', popstateHandler, false); - - // Cancel any scrolls-in-progress - smoothScroll.cancelScroll(); - - // Reset variables - settings = null; - anchor = null; - toggle = null; - fixedHeader = null; - eventTimeout = null; - animationInterval = null; - - }; - - /** - * Initialize Smooth Scroll - * @param {Object} options User settings - */ - var init = function () { - - // feature test - if (!supports()) throw 'Smooth Scroll: This browser does not support the required JavaScript methods and browser APIs.'; - - // Destroy any existing initializations - smoothScroll.destroy(); - - // Selectors and variables - settings = extend(defaults, options || {}); // Merge user options with defaults - fixedHeader = settings.header ? document.querySelector(settings.header) : null; // Get the fixed header - - // When a toggle is clicked, run the click handler - document.addEventListener('click', clickHandler, false); - - // If updateURL and popState are enabled, listen for pop events - if (settings.updateURL && settings.popstate) { - window.addEventListener('popstate', popstateHandler, false); - } - - }; - - - // - // Initialize plugin - // - - init(); - - - // - // Public APIs - // - - return smoothScroll; - - }; - - return SmoothScroll; - -})); diff --git a/assets/js/vendor/jquery/jquery-3.6.0.js b/assets/js/vendor/jquery/jquery-3.6.0.js deleted file mode 100644 index fc6c299b..00000000 --- a/assets/js/vendor/jquery/jquery-3.6.0.js +++ /dev/null @@ -1,10881 +0,0 @@ -/*! - * jQuery JavaScript Library v3.6.0 - * https://jquery.com/ - * - * Includes Sizzle.js - * https://sizzlejs.com/ - * - * Copyright OpenJS Foundation and other contributors - * Released under the MIT license - * https://jquery.org/license - * - * Date: 2021-03-02T17:08Z - */ -( function( global, factory ) { - - "use strict"; - - if ( typeof module === "object" && typeof module.exports === "object" ) { - - // For CommonJS and CommonJS-like environments where a proper `window` - // is present, execute the factory and get jQuery. - // For environments that do not have a `window` with a `document` - // (such as Node.js), expose a factory as module.exports. - // This accentuates the need for the creation of a real `window`. - // e.g. var jQuery = require("jquery")(window); - // See ticket #14549 for more info. - module.exports = global.document ? - factory( global, true ) : - function( w ) { - if ( !w.document ) { - throw new Error( "jQuery requires a window with a document" ); - } - return factory( w ); - }; - } else { - factory( global ); - } - -// Pass this if window is not defined yet -} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { - -// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 -// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode -// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common -// enough that all such attempts are guarded in a try block. -"use strict"; - -var arr = []; - -var getProto = Object.getPrototypeOf; - -var slice = arr.slice; - -var flat = arr.flat ? function( array ) { - return arr.flat.call( array ); -} : function( array ) { - return arr.concat.apply( [], array ); -}; - - -var push = arr.push; - -var indexOf = arr.indexOf; - -var class2type = {}; - -var toString = class2type.toString; - -var hasOwn = class2type.hasOwnProperty; - -var fnToString = hasOwn.toString; - -var ObjectFunctionString = fnToString.call( Object ); - -var support = {}; - -var isFunction = function isFunction( obj ) { - - // Support: Chrome <=57, Firefox <=52 - // In some browsers, typeof returns "function" for HTML elements - // (i.e., `typeof document.createElement( "object" ) === "function"`). - // We don't want to classify *any* DOM node as a function. - // Support: QtWeb <=3.8.5, WebKit <=534.34, wkhtmltopdf tool <=0.12.5 - // Plus for old WebKit, typeof returns "function" for HTML collections - // (e.g., `typeof document.getElementsByTagName("div") === "function"`). (gh-4756) - return typeof obj === "function" && typeof obj.nodeType !== "number" && - typeof obj.item !== "function"; - }; - - -var isWindow = function isWindow( obj ) { - return obj != null && obj === obj.window; - }; - - -var document = window.document; - - - - var preservedScriptAttributes = { - type: true, - src: true, - nonce: true, - noModule: true - }; - - function DOMEval( code, node, doc ) { - doc = doc || document; - - var i, val, - script = doc.createElement( "script" ); - - script.text = code; - if ( node ) { - for ( i in preservedScriptAttributes ) { - - // Support: Firefox 64+, Edge 18+ - // Some browsers don't support the "nonce" property on scripts. - // On the other hand, just using `getAttribute` is not enough as - // the `nonce` attribute is reset to an empty string whenever it - // becomes browsing-context connected. - // See https://github.com/whatwg/html/issues/2369 - // See https://html.spec.whatwg.org/#nonce-attributes - // The `node.getAttribute` check was added for the sake of - // `jQuery.globalEval` so that it can fake a nonce-containing node - // via an object. - val = node[ i ] || node.getAttribute && node.getAttribute( i ); - if ( val ) { - script.setAttribute( i, val ); - } - } - } - doc.head.appendChild( script ).parentNode.removeChild( script ); - } - - -function toType( obj ) { - if ( obj == null ) { - return obj + ""; - } - - // Support: Android <=2.3 only (functionish RegExp) - return typeof obj === "object" || typeof obj === "function" ? - class2type[ toString.call( obj ) ] || "object" : - typeof obj; -} -/* global Symbol */ -// Defining this global in .eslintrc.json would create a danger of using the global -// unguarded in another place, it seems safer to define global only for this module - - - -var - version = "3.6.0", - - // Define a local copy of jQuery - jQuery = function( selector, context ) { - - // The jQuery object is actually just the init constructor 'enhanced' - // Need init if jQuery is called (just allow error to be thrown if not included) - return new jQuery.fn.init( selector, context ); - }; - -jQuery.fn = jQuery.prototype = { - - // The current version of jQuery being used - jquery: version, - - constructor: jQuery, - - // The default length of a jQuery object is 0 - length: 0, - - toArray: function() { - return slice.call( this ); - }, - - // Get the Nth element in the matched element set OR - // Get the whole matched element set as a clean array - get: function( num ) { - - // Return all the elements in a clean array - if ( num == null ) { - return slice.call( this ); - } - - // Return just the one element from the set - return num < 0 ? this[ num + this.length ] : this[ num ]; - }, - - // Take an array of elements and push it onto the stack - // (returning the new matched element set) - pushStack: function( elems ) { - - // Build a new jQuery matched element set - var ret = jQuery.merge( this.constructor(), elems ); - - // Add the old object onto the stack (as a reference) - ret.prevObject = this; - - // Return the newly-formed element set - return ret; - }, - - // Execute a callback for every element in the matched set. - each: function( callback ) { - return jQuery.each( this, callback ); - }, - - map: function( callback ) { - return this.pushStack( jQuery.map( this, function( elem, i ) { - return callback.call( elem, i, elem ); - } ) ); - }, - - slice: function() { - return this.pushStack( slice.apply( this, arguments ) ); - }, - - first: function() { - return this.eq( 0 ); - }, - - last: function() { - return this.eq( -1 ); - }, - - even: function() { - return this.pushStack( jQuery.grep( this, function( _elem, i ) { - return ( i + 1 ) % 2; - } ) ); - }, - - odd: function() { - return this.pushStack( jQuery.grep( this, function( _elem, i ) { - return i % 2; - } ) ); - }, - - eq: function( i ) { - var len = this.length, - j = +i + ( i < 0 ? len : 0 ); - return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); - }, - - end: function() { - return this.prevObject || this.constructor(); - }, - - // For internal use only. - // Behaves like an Array's method, not like a jQuery method. - push: push, - sort: arr.sort, - splice: arr.splice -}; - -jQuery.extend = jQuery.fn.extend = function() { - var options, name, src, copy, copyIsArray, clone, - target = arguments[ 0 ] || {}, - i = 1, - length = arguments.length, - deep = false; - - // Handle a deep copy situation - if ( typeof target === "boolean" ) { - deep = target; - - // Skip the boolean and the target - target = arguments[ i ] || {}; - i++; - } - - // Handle case when target is a string or something (possible in deep copy) - if ( typeof target !== "object" && !isFunction( target ) ) { - target = {}; - } - - // Extend jQuery itself if only one argument is passed - if ( i === length ) { - target = this; - i--; - } - - for ( ; i < length; i++ ) { - - // Only deal with non-null/undefined values - if ( ( options = arguments[ i ] ) != null ) { - - // Extend the base object - for ( name in options ) { - copy = options[ name ]; - - // Prevent Object.prototype pollution - // Prevent never-ending loop - if ( name === "__proto__" || target === copy ) { - continue; - } - - // Recurse if we're merging plain objects or arrays - if ( deep && copy && ( jQuery.isPlainObject( copy ) || - ( copyIsArray = Array.isArray( copy ) ) ) ) { - src = target[ name ]; - - // Ensure proper type for the source value - if ( copyIsArray && !Array.isArray( src ) ) { - clone = []; - } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { - clone = {}; - } else { - clone = src; - } - copyIsArray = false; - - // Never move original objects, clone them - target[ name ] = jQuery.extend( deep, clone, copy ); - - // Don't bring in undefined values - } else if ( copy !== undefined ) { - target[ name ] = copy; - } - } - } - } - - // Return the modified object - return target; -}; - -jQuery.extend( { - - // Unique for each copy of jQuery on the page - expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), - - // Assume jQuery is ready without the ready module - isReady: true, - - error: function( msg ) { - throw new Error( msg ); - }, - - noop: function() {}, - - isPlainObject: function( obj ) { - var proto, Ctor; - - // Detect obvious negatives - // Use toString instead of jQuery.type to catch host objects - if ( !obj || toString.call( obj ) !== "[object Object]" ) { - return false; - } - - proto = getProto( obj ); - - // Objects with no prototype (e.g., `Object.create( null )`) are plain - if ( !proto ) { - return true; - } - - // Objects with prototype are plain iff they were constructed by a global Object function - Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; - return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; - }, - - isEmptyObject: function( obj ) { - var name; - - for ( name in obj ) { - return false; - } - return true; - }, - - // Evaluates a script in a provided context; falls back to the global one - // if not specified. - globalEval: function( code, options, doc ) { - DOMEval( code, { nonce: options && options.nonce }, doc ); - }, - - each: function( obj, callback ) { - var length, i = 0; - - if ( isArrayLike( obj ) ) { - length = obj.length; - for ( ; i < length; i++ ) { - if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { - break; - } - } - } else { - for ( i in obj ) { - if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { - break; - } - } - } - - return obj; - }, - - // results is for internal usage only - makeArray: function( arr, results ) { - var ret = results || []; - - if ( arr != null ) { - if ( isArrayLike( Object( arr ) ) ) { - jQuery.merge( ret, - typeof arr === "string" ? - [ arr ] : arr - ); - } else { - push.call( ret, arr ); - } - } - - return ret; - }, - - inArray: function( elem, arr, i ) { - return arr == null ? -1 : indexOf.call( arr, elem, i ); - }, - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - merge: function( first, second ) { - var len = +second.length, - j = 0, - i = first.length; - - for ( ; j < len; j++ ) { - first[ i++ ] = second[ j ]; - } - - first.length = i; - - return first; - }, - - grep: function( elems, callback, invert ) { - var callbackInverse, - matches = [], - i = 0, - length = elems.length, - callbackExpect = !invert; - - // Go through the array, only saving the items - // that pass the validator function - for ( ; i < length; i++ ) { - callbackInverse = !callback( elems[ i ], i ); - if ( callbackInverse !== callbackExpect ) { - matches.push( elems[ i ] ); - } - } - - return matches; - }, - - // arg is for internal usage only - map: function( elems, callback, arg ) { - var length, value, - i = 0, - ret = []; - - // Go through the array, translating each of the items to their new values - if ( isArrayLike( elems ) ) { - length = elems.length; - for ( ; i < length; i++ ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - - // Go through every key on the object, - } else { - for ( i in elems ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - } - - // Flatten any nested arrays - return flat( ret ); - }, - - // A global GUID counter for objects - guid: 1, - - // jQuery.support is not used in Core but other projects attach their - // properties to it so it needs to exist. - support: support -} ); - -if ( typeof Symbol === "function" ) { - jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; -} - -// Populate the class2type map -jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), - function( _i, name ) { - class2type[ "[object " + name + "]" ] = name.toLowerCase(); - } ); - -function isArrayLike( obj ) { - - // Support: real iOS 8.2 only (not reproducible in simulator) - // `in` check used to prevent JIT error (gh-2145) - // hasOwn isn't used here due to false negatives - // regarding Nodelist length in IE - var length = !!obj && "length" in obj && obj.length, - type = toType( obj ); - - if ( isFunction( obj ) || isWindow( obj ) ) { - return false; - } - - return type === "array" || length === 0 || - typeof length === "number" && length > 0 && ( length - 1 ) in obj; -} -var Sizzle = -/*! - * Sizzle CSS Selector Engine v2.3.6 - * https://sizzlejs.com/ - * - * Copyright JS Foundation and other contributors - * Released under the MIT license - * https://js.foundation/ - * - * Date: 2021-02-16 - */ -( function( window ) { -var i, - support, - Expr, - getText, - isXML, - tokenize, - compile, - select, - outermostContext, - sortInput, - hasDuplicate, - - // Local document vars - setDocument, - document, - docElem, - documentIsHTML, - rbuggyQSA, - rbuggyMatches, - matches, - contains, - - // Instance-specific data - expando = "sizzle" + 1 * new Date(), - preferredDoc = window.document, - dirruns = 0, - done = 0, - classCache = createCache(), - tokenCache = createCache(), - compilerCache = createCache(), - nonnativeSelectorCache = createCache(), - sortOrder = function( a, b ) { - if ( a === b ) { - hasDuplicate = true; - } - return 0; - }, - - // Instance methods - hasOwn = ( {} ).hasOwnProperty, - arr = [], - pop = arr.pop, - pushNative = arr.push, - push = arr.push, - slice = arr.slice, - - // Use a stripped-down indexOf as it's faster than native - // https://jsperf.com/thor-indexof-vs-for/5 - indexOf = function( list, elem ) { - var i = 0, - len = list.length; - for ( ; i < len; i++ ) { - if ( list[ i ] === elem ) { - return i; - } - } - return -1; - }, - - booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" + - "ismap|loop|multiple|open|readonly|required|scoped", - - // Regular expressions - - // http://www.w3.org/TR/css3-selectors/#whitespace - whitespace = "[\\x20\\t\\r\\n\\f]", - - // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram - identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + - "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", - - // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors - attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + - - // Operator (capture 2) - "*([*^$|!~]?=)" + whitespace + - - // "Attribute values must be CSS identifiers [capture 5] - // or strings [capture 3 or capture 4]" - "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + - whitespace + "*\\]", - - pseudos = ":(" + identifier + ")(?:\\((" + - - // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: - // 1. quoted (capture 3; capture 4 or capture 5) - "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + - - // 2. simple (capture 6) - "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + - - // 3. anything else (capture 2) - ".*" + - ")\\)|)", - - // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter - rwhitespace = new RegExp( whitespace + "+", "g" ), - rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + - whitespace + "+$", "g" ), - - rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), - rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + - "*" ), - rdescend = new RegExp( whitespace + "|>" ), - - rpseudo = new RegExp( pseudos ), - ridentifier = new RegExp( "^" + identifier + "$" ), - - matchExpr = { - "ID": new RegExp( "^#(" + identifier + ")" ), - "CLASS": new RegExp( "^\\.(" + identifier + ")" ), - "TAG": new RegExp( "^(" + identifier + "|[*])" ), - "ATTR": new RegExp( "^" + attributes ), - "PSEUDO": new RegExp( "^" + pseudos ), - "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + - whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + - whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), - "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), - - // For use in libraries implementing .is() - // We use this for POS matching in `select` - "needsContext": new RegExp( "^" + whitespace + - "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + - "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) - }, - - rhtml = /HTML$/i, - rinputs = /^(?:input|select|textarea|button)$/i, - rheader = /^h\d$/i, - - rnative = /^[^{]+\{\s*\[native \w/, - - // Easily-parseable/retrievable ID or TAG or CLASS selectors - rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, - - rsibling = /[+~]/, - - // CSS escapes - // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters - runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ), - funescape = function( escape, nonHex ) { - var high = "0x" + escape.slice( 1 ) - 0x10000; - - return nonHex ? - - // Strip the backslash prefix from a non-hex escape sequence - nonHex : - - // Replace a hexadecimal escape sequence with the encoded Unicode code point - // Support: IE <=11+ - // For values outside the Basic Multilingual Plane (BMP), manually construct a - // surrogate pair - high < 0 ? - String.fromCharCode( high + 0x10000 ) : - String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); - }, - - // CSS string/identifier serialization - // https://drafts.csswg.org/cssom/#common-serializing-idioms - rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, - fcssescape = function( ch, asCodePoint ) { - if ( asCodePoint ) { - - // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER - if ( ch === "\0" ) { - return "\uFFFD"; - } - - // Control characters and (dependent upon position) numbers get escaped as code points - return ch.slice( 0, -1 ) + "\\" + - ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; - } - - // Other potentially-special ASCII characters get backslash-escaped - return "\\" + ch; - }, - - // Used for iframes - // See setDocument() - // Removing the function wrapper causes a "Permission Denied" - // error in IE - unloadHandler = function() { - setDocument(); - }, - - inDisabledFieldset = addCombinator( - function( elem ) { - return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; - }, - { dir: "parentNode", next: "legend" } - ); - -// Optimize for push.apply( _, NodeList ) -try { - push.apply( - ( arr = slice.call( preferredDoc.childNodes ) ), - preferredDoc.childNodes - ); - - // Support: Android<4.0 - // Detect silently failing push.apply - // eslint-disable-next-line no-unused-expressions - arr[ preferredDoc.childNodes.length ].nodeType; -} catch ( e ) { - push = { apply: arr.length ? - - // Leverage slice if possible - function( target, els ) { - pushNative.apply( target, slice.call( els ) ); - } : - - // Support: IE<9 - // Otherwise append directly - function( target, els ) { - var j = target.length, - i = 0; - - // Can't trust NodeList.length - while ( ( target[ j++ ] = els[ i++ ] ) ) {} - target.length = j - 1; - } - }; -} - -function Sizzle( selector, context, results, seed ) { - var m, i, elem, nid, match, groups, newSelector, - newContext = context && context.ownerDocument, - - // nodeType defaults to 9, since context defaults to document - nodeType = context ? context.nodeType : 9; - - results = results || []; - - // Return early from calls with invalid selector or context - if ( typeof selector !== "string" || !selector || - nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { - - return results; - } - - // Try to shortcut find operations (as opposed to filters) in HTML documents - if ( !seed ) { - setDocument( context ); - context = context || document; - - if ( documentIsHTML ) { - - // If the selector is sufficiently simple, try using a "get*By*" DOM method - // (excepting DocumentFragment context, where the methods don't exist) - if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) { - - // ID selector - if ( ( m = match[ 1 ] ) ) { - - // Document context - if ( nodeType === 9 ) { - if ( ( elem = context.getElementById( m ) ) ) { - - // Support: IE, Opera, Webkit - // TODO: identify versions - // getElementById can match elements by name instead of ID - if ( elem.id === m ) { - results.push( elem ); - return results; - } - } else { - return results; - } - - // Element context - } else { - - // Support: IE, Opera, Webkit - // TODO: identify versions - // getElementById can match elements by name instead of ID - if ( newContext && ( elem = newContext.getElementById( m ) ) && - contains( context, elem ) && - elem.id === m ) { - - results.push( elem ); - return results; - } - } - - // Type selector - } else if ( match[ 2 ] ) { - push.apply( results, context.getElementsByTagName( selector ) ); - return results; - - // Class selector - } else if ( ( m = match[ 3 ] ) && support.getElementsByClassName && - context.getElementsByClassName ) { - - push.apply( results, context.getElementsByClassName( m ) ); - return results; - } - } - - // Take advantage of querySelectorAll - if ( support.qsa && - !nonnativeSelectorCache[ selector + " " ] && - ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) && - - // Support: IE 8 only - // Exclude object elements - ( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) { - - newSelector = selector; - newContext = context; - - // qSA considers elements outside a scoping root when evaluating child or - // descendant combinators, which is not what we want. - // In such cases, we work around the behavior by prefixing every selector in the - // list with an ID selector referencing the scope context. - // The technique has to be used as well when a leading combinator is used - // as such selectors are not recognized by querySelectorAll. - // Thanks to Andrew Dupont for this technique. - if ( nodeType === 1 && - ( rdescend.test( selector ) || rcombinators.test( selector ) ) ) { - - // Expand context for sibling selectors - newContext = rsibling.test( selector ) && testContext( context.parentNode ) || - context; - - // We can use :scope instead of the ID hack if the browser - // supports it & if we're not changing the context. - if ( newContext !== context || !support.scope ) { - - // Capture the context ID, setting it first if necessary - if ( ( nid = context.getAttribute( "id" ) ) ) { - nid = nid.replace( rcssescape, fcssescape ); - } else { - context.setAttribute( "id", ( nid = expando ) ); - } - } - - // Prefix every selector in the list - groups = tokenize( selector ); - i = groups.length; - while ( i-- ) { - groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + - toSelector( groups[ i ] ); - } - newSelector = groups.join( "," ); - } - - try { - push.apply( results, - newContext.querySelectorAll( newSelector ) - ); - return results; - } catch ( qsaError ) { - nonnativeSelectorCache( selector, true ); - } finally { - if ( nid === expando ) { - context.removeAttribute( "id" ); - } - } - } - } - } - - // All others - return select( selector.replace( rtrim, "$1" ), context, results, seed ); -} - -/** - * Create key-value caches of limited size - * @returns {function(string, object)} Returns the Object data after storing it on itself with - * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) - * deleting the oldest entry - */ -function createCache() { - var keys = []; - - function cache( key, value ) { - - // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) - if ( keys.push( key + " " ) > Expr.cacheLength ) { - - // Only keep the most recent entries - delete cache[ keys.shift() ]; - } - return ( cache[ key + " " ] = value ); - } - return cache; -} - -/** - * Mark a function for special use by Sizzle - * @param {Function} fn The function to mark - */ -function markFunction( fn ) { - fn[ expando ] = true; - return fn; -} - -/** - * Support testing using an element - * @param {Function} fn Passed the created element and returns a boolean result - */ -function assert( fn ) { - var el = document.createElement( "fieldset" ); - - try { - return !!fn( el ); - } catch ( e ) { - return false; - } finally { - - // Remove from its parent by default - if ( el.parentNode ) { - el.parentNode.removeChild( el ); - } - - // release memory in IE - el = null; - } -} - -/** - * Adds the same handler for all of the specified attrs - * @param {String} attrs Pipe-separated list of attributes - * @param {Function} handler The method that will be applied - */ -function addHandle( attrs, handler ) { - var arr = attrs.split( "|" ), - i = arr.length; - - while ( i-- ) { - Expr.attrHandle[ arr[ i ] ] = handler; - } -} - -/** - * Checks document order of two siblings - * @param {Element} a - * @param {Element} b - * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b - */ -function siblingCheck( a, b ) { - var cur = b && a, - diff = cur && a.nodeType === 1 && b.nodeType === 1 && - a.sourceIndex - b.sourceIndex; - - // Use IE sourceIndex if available on both nodes - if ( diff ) { - return diff; - } - - // Check if b follows a - if ( cur ) { - while ( ( cur = cur.nextSibling ) ) { - if ( cur === b ) { - return -1; - } - } - } - - return a ? 1 : -1; -} - -/** - * Returns a function to use in pseudos for input types - * @param {String} type - */ -function createInputPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for buttons - * @param {String} type - */ -function createButtonPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return ( name === "input" || name === "button" ) && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for :enabled/:disabled - * @param {Boolean} disabled true for :disabled; false for :enabled - */ -function createDisabledPseudo( disabled ) { - - // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable - return function( elem ) { - - // Only certain elements can match :enabled or :disabled - // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled - // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled - if ( "form" in elem ) { - - // Check for inherited disabledness on relevant non-disabled elements: - // * listed form-associated elements in a disabled fieldset - // https://html.spec.whatwg.org/multipage/forms.html#category-listed - // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled - // * option elements in a disabled optgroup - // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled - // All such elements have a "form" property. - if ( elem.parentNode && elem.disabled === false ) { - - // Option elements defer to a parent optgroup if present - if ( "label" in elem ) { - if ( "label" in elem.parentNode ) { - return elem.parentNode.disabled === disabled; - } else { - return elem.disabled === disabled; - } - } - - // Support: IE 6 - 11 - // Use the isDisabled shortcut property to check for disabled fieldset ancestors - return elem.isDisabled === disabled || - - // Where there is no isDisabled, check manually - /* jshint -W018 */ - elem.isDisabled !== !disabled && - inDisabledFieldset( elem ) === disabled; - } - - return elem.disabled === disabled; - - // Try to winnow out elements that can't be disabled before trusting the disabled property. - // Some victims get caught in our net (label, legend, menu, track), but it shouldn't - // even exist on them, let alone have a boolean value. - } else if ( "label" in elem ) { - return elem.disabled === disabled; - } - - // Remaining elements are neither :enabled nor :disabled - return false; - }; -} - -/** - * Returns a function to use in pseudos for positionals - * @param {Function} fn - */ -function createPositionalPseudo( fn ) { - return markFunction( function( argument ) { - argument = +argument; - return markFunction( function( seed, matches ) { - var j, - matchIndexes = fn( [], seed.length, argument ), - i = matchIndexes.length; - - // Match elements found at the specified indexes - while ( i-- ) { - if ( seed[ ( j = matchIndexes[ i ] ) ] ) { - seed[ j ] = !( matches[ j ] = seed[ j ] ); - } - } - } ); - } ); -} - -/** - * Checks a node for validity as a Sizzle context - * @param {Element|Object=} context - * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value - */ -function testContext( context ) { - return context && typeof context.getElementsByTagName !== "undefined" && context; -} - -// Expose support vars for convenience -support = Sizzle.support = {}; - -/** - * Detects XML nodes - * @param {Element|Object} elem An element or a document - * @returns {Boolean} True iff elem is a non-HTML XML node - */ -isXML = Sizzle.isXML = function( elem ) { - var namespace = elem && elem.namespaceURI, - docElem = elem && ( elem.ownerDocument || elem ).documentElement; - - // Support: IE <=8 - // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes - // https://bugs.jquery.com/ticket/4833 - return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); -}; - -/** - * Sets document-related variables once based on the current document - * @param {Element|Object} [doc] An element or document object to use to set the document - * @returns {Object} Returns the current document - */ -setDocument = Sizzle.setDocument = function( node ) { - var hasCompare, subWindow, - doc = node ? node.ownerDocument || node : preferredDoc; - - // Return early if doc is invalid or already selected - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) { - return document; - } - - // Update global variables - document = doc; - docElem = document.documentElement; - documentIsHTML = !isXML( document ); - - // Support: IE 9 - 11+, Edge 12 - 18+ - // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( preferredDoc != document && - ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { - - // Support: IE 11, Edge - if ( subWindow.addEventListener ) { - subWindow.addEventListener( "unload", unloadHandler, false ); - - // Support: IE 9 - 10 only - } else if ( subWindow.attachEvent ) { - subWindow.attachEvent( "onunload", unloadHandler ); - } - } - - // Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only, - // Safari 4 - 5 only, Opera <=11.6 - 12.x only - // IE/Edge & older browsers don't support the :scope pseudo-class. - // Support: Safari 6.0 only - // Safari 6.0 supports :scope but it's an alias of :root there. - support.scope = assert( function( el ) { - docElem.appendChild( el ).appendChild( document.createElement( "div" ) ); - return typeof el.querySelectorAll !== "undefined" && - !el.querySelectorAll( ":scope fieldset div" ).length; - } ); - - /* Attributes - ---------------------------------------------------------------------- */ - - // Support: IE<8 - // Verify that getAttribute really returns attributes and not properties - // (excepting IE8 booleans) - support.attributes = assert( function( el ) { - el.className = "i"; - return !el.getAttribute( "className" ); - } ); - - /* getElement(s)By* - ---------------------------------------------------------------------- */ - - // Check if getElementsByTagName("*") returns only elements - support.getElementsByTagName = assert( function( el ) { - el.appendChild( document.createComment( "" ) ); - return !el.getElementsByTagName( "*" ).length; - } ); - - // Support: IE<9 - support.getElementsByClassName = rnative.test( document.getElementsByClassName ); - - // Support: IE<10 - // Check if getElementById returns elements by name - // The broken getElementById methods don't pick up programmatically-set names, - // so use a roundabout getElementsByName test - support.getById = assert( function( el ) { - docElem.appendChild( el ).id = expando; - return !document.getElementsByName || !document.getElementsByName( expando ).length; - } ); - - // ID filter and find - if ( support.getById ) { - Expr.filter[ "ID" ] = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - return elem.getAttribute( "id" ) === attrId; - }; - }; - Expr.find[ "ID" ] = function( id, context ) { - if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { - var elem = context.getElementById( id ); - return elem ? [ elem ] : []; - } - }; - } else { - Expr.filter[ "ID" ] = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - var node = typeof elem.getAttributeNode !== "undefined" && - elem.getAttributeNode( "id" ); - return node && node.value === attrId; - }; - }; - - // Support: IE 6 - 7 only - // getElementById is not reliable as a find shortcut - Expr.find[ "ID" ] = function( id, context ) { - if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { - var node, i, elems, - elem = context.getElementById( id ); - - if ( elem ) { - - // Verify the id attribute - node = elem.getAttributeNode( "id" ); - if ( node && node.value === id ) { - return [ elem ]; - } - - // Fall back on getElementsByName - elems = context.getElementsByName( id ); - i = 0; - while ( ( elem = elems[ i++ ] ) ) { - node = elem.getAttributeNode( "id" ); - if ( node && node.value === id ) { - return [ elem ]; - } - } - } - - return []; - } - }; - } - - // Tag - Expr.find[ "TAG" ] = support.getElementsByTagName ? - function( tag, context ) { - if ( typeof context.getElementsByTagName !== "undefined" ) { - return context.getElementsByTagName( tag ); - - // DocumentFragment nodes don't have gEBTN - } else if ( support.qsa ) { - return context.querySelectorAll( tag ); - } - } : - - function( tag, context ) { - var elem, - tmp = [], - i = 0, - - // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too - results = context.getElementsByTagName( tag ); - - // Filter out possible comments - if ( tag === "*" ) { - while ( ( elem = results[ i++ ] ) ) { - if ( elem.nodeType === 1 ) { - tmp.push( elem ); - } - } - - return tmp; - } - return results; - }; - - // Class - Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) { - if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { - return context.getElementsByClassName( className ); - } - }; - - /* QSA/matchesSelector - ---------------------------------------------------------------------- */ - - // QSA and matchesSelector support - - // matchesSelector(:active) reports false when true (IE9/Opera 11.5) - rbuggyMatches = []; - - // qSa(:focus) reports false when true (Chrome 21) - // We allow this because of a bug in IE8/9 that throws an error - // whenever `document.activeElement` is accessed on an iframe - // So, we allow :focus to pass through QSA all the time to avoid the IE error - // See https://bugs.jquery.com/ticket/13378 - rbuggyQSA = []; - - if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) { - - // Build QSA regex - // Regex strategy adopted from Diego Perini - assert( function( el ) { - - var input; - - // Select is set to empty string on purpose - // This is to test IE's treatment of not explicitly - // setting a boolean content attribute, - // since its presence should be enough - // https://bugs.jquery.com/ticket/12359 - docElem.appendChild( el ).innerHTML = "" + - ""; - - // Support: IE8, Opera 11-12.16 - // Nothing should be selected when empty strings follow ^= or $= or *= - // The test attribute must be unknown in Opera but "safe" for WinRT - // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section - if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) { - rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); - } - - // Support: IE8 - // Boolean attributes and "value" are not treated correctly - if ( !el.querySelectorAll( "[selected]" ).length ) { - rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); - } - - // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ - if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { - rbuggyQSA.push( "~=" ); - } - - // Support: IE 11+, Edge 15 - 18+ - // IE 11/Edge don't find elements on a `[name='']` query in some cases. - // Adding a temporary attribute to the document before the selection works - // around the issue. - // Interestingly, IE 10 & older don't seem to have the issue. - input = document.createElement( "input" ); - input.setAttribute( "name", "" ); - el.appendChild( input ); - if ( !el.querySelectorAll( "[name='']" ).length ) { - rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + - whitespace + "*(?:''|\"\")" ); - } - - // Webkit/Opera - :checked should return selected option elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - // IE8 throws error here and will not see later tests - if ( !el.querySelectorAll( ":checked" ).length ) { - rbuggyQSA.push( ":checked" ); - } - - // Support: Safari 8+, iOS 8+ - // https://bugs.webkit.org/show_bug.cgi?id=136851 - // In-page `selector#id sibling-combinator selector` fails - if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { - rbuggyQSA.push( ".#.+[+~]" ); - } - - // Support: Firefox <=3.6 - 5 only - // Old Firefox doesn't throw on a badly-escaped identifier. - el.querySelectorAll( "\\\f" ); - rbuggyQSA.push( "[\\r\\n\\f]" ); - } ); - - assert( function( el ) { - el.innerHTML = "" + - ""; - - // Support: Windows 8 Native Apps - // The type and name attributes are restricted during .innerHTML assignment - var input = document.createElement( "input" ); - input.setAttribute( "type", "hidden" ); - el.appendChild( input ).setAttribute( "name", "D" ); - - // Support: IE8 - // Enforce case-sensitivity of name attribute - if ( el.querySelectorAll( "[name=d]" ).length ) { - rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); - } - - // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) - // IE8 throws error here and will not see later tests - if ( el.querySelectorAll( ":enabled" ).length !== 2 ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Support: IE9-11+ - // IE's :disabled selector does not pick up the children of disabled fieldsets - docElem.appendChild( el ).disabled = true; - if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Support: Opera 10 - 11 only - // Opera 10-11 does not throw on post-comma invalid pseudos - el.querySelectorAll( "*,:x" ); - rbuggyQSA.push( ",.*:" ); - } ); - } - - if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches || - docElem.webkitMatchesSelector || - docElem.mozMatchesSelector || - docElem.oMatchesSelector || - docElem.msMatchesSelector ) ) ) ) { - - assert( function( el ) { - - // Check to see if it's possible to do matchesSelector - // on a disconnected node (IE 9) - support.disconnectedMatch = matches.call( el, "*" ); - - // This should fail with an exception - // Gecko does not error, returns false instead - matches.call( el, "[s!='']:x" ); - rbuggyMatches.push( "!=", pseudos ); - } ); - } - - rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); - rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) ); - - /* Contains - ---------------------------------------------------------------------- */ - hasCompare = rnative.test( docElem.compareDocumentPosition ); - - // Element contains another - // Purposefully self-exclusive - // As in, an element does not contain itself - contains = hasCompare || rnative.test( docElem.contains ) ? - function( a, b ) { - var adown = a.nodeType === 9 ? a.documentElement : a, - bup = b && b.parentNode; - return a === bup || !!( bup && bup.nodeType === 1 && ( - adown.contains ? - adown.contains( bup ) : - a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 - ) ); - } : - function( a, b ) { - if ( b ) { - while ( ( b = b.parentNode ) ) { - if ( b === a ) { - return true; - } - } - } - return false; - }; - - /* Sorting - ---------------------------------------------------------------------- */ - - // Document order sorting - sortOrder = hasCompare ? - function( a, b ) { - - // Flag for duplicate removal - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - // Sort on method existence if only one input has compareDocumentPosition - var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; - if ( compare ) { - return compare; - } - - // Calculate position if both inputs belong to the same document - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ? - a.compareDocumentPosition( b ) : - - // Otherwise we know they are disconnected - 1; - - // Disconnected nodes - if ( compare & 1 || - ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) { - - // Choose the first element that is related to our preferred document - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( a == document || a.ownerDocument == preferredDoc && - contains( preferredDoc, a ) ) { - return -1; - } - - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( b == document || b.ownerDocument == preferredDoc && - contains( preferredDoc, b ) ) { - return 1; - } - - // Maintain original order - return sortInput ? - ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : - 0; - } - - return compare & 4 ? -1 : 1; - } : - function( a, b ) { - - // Exit early if the nodes are identical - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - var cur, - i = 0, - aup = a.parentNode, - bup = b.parentNode, - ap = [ a ], - bp = [ b ]; - - // Parentless nodes are either documents or disconnected - if ( !aup || !bup ) { - - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - /* eslint-disable eqeqeq */ - return a == document ? -1 : - b == document ? 1 : - /* eslint-enable eqeqeq */ - aup ? -1 : - bup ? 1 : - sortInput ? - ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : - 0; - - // If the nodes are siblings, we can do a quick check - } else if ( aup === bup ) { - return siblingCheck( a, b ); - } - - // Otherwise we need full lists of their ancestors for comparison - cur = a; - while ( ( cur = cur.parentNode ) ) { - ap.unshift( cur ); - } - cur = b; - while ( ( cur = cur.parentNode ) ) { - bp.unshift( cur ); - } - - // Walk down the tree looking for a discrepancy - while ( ap[ i ] === bp[ i ] ) { - i++; - } - - return i ? - - // Do a sibling check if the nodes have a common ancestor - siblingCheck( ap[ i ], bp[ i ] ) : - - // Otherwise nodes in our document sort first - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - /* eslint-disable eqeqeq */ - ap[ i ] == preferredDoc ? -1 : - bp[ i ] == preferredDoc ? 1 : - /* eslint-enable eqeqeq */ - 0; - }; - - return document; -}; - -Sizzle.matches = function( expr, elements ) { - return Sizzle( expr, null, null, elements ); -}; - -Sizzle.matchesSelector = function( elem, expr ) { - setDocument( elem ); - - if ( support.matchesSelector && documentIsHTML && - !nonnativeSelectorCache[ expr + " " ] && - ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && - ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { - - try { - var ret = matches.call( elem, expr ); - - // IE 9's matchesSelector returns false on disconnected nodes - if ( ret || support.disconnectedMatch || - - // As well, disconnected nodes are said to be in a document - // fragment in IE 9 - elem.document && elem.document.nodeType !== 11 ) { - return ret; - } - } catch ( e ) { - nonnativeSelectorCache( expr, true ); - } - } - - return Sizzle( expr, document, null, [ elem ] ).length > 0; -}; - -Sizzle.contains = function( context, elem ) { - - // Set document vars if needed - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( ( context.ownerDocument || context ) != document ) { - setDocument( context ); - } - return contains( context, elem ); -}; - -Sizzle.attr = function( elem, name ) { - - // Set document vars if needed - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( ( elem.ownerDocument || elem ) != document ) { - setDocument( elem ); - } - - var fn = Expr.attrHandle[ name.toLowerCase() ], - - // Don't get fooled by Object.prototype properties (jQuery #13807) - val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? - fn( elem, name, !documentIsHTML ) : - undefined; - - return val !== undefined ? - val : - support.attributes || !documentIsHTML ? - elem.getAttribute( name ) : - ( val = elem.getAttributeNode( name ) ) && val.specified ? - val.value : - null; -}; - -Sizzle.escape = function( sel ) { - return ( sel + "" ).replace( rcssescape, fcssescape ); -}; - -Sizzle.error = function( msg ) { - throw new Error( "Syntax error, unrecognized expression: " + msg ); -}; - -/** - * Document sorting and removing duplicates - * @param {ArrayLike} results - */ -Sizzle.uniqueSort = function( results ) { - var elem, - duplicates = [], - j = 0, - i = 0; - - // Unless we *know* we can detect duplicates, assume their presence - hasDuplicate = !support.detectDuplicates; - sortInput = !support.sortStable && results.slice( 0 ); - results.sort( sortOrder ); - - if ( hasDuplicate ) { - while ( ( elem = results[ i++ ] ) ) { - if ( elem === results[ i ] ) { - j = duplicates.push( i ); - } - } - while ( j-- ) { - results.splice( duplicates[ j ], 1 ); - } - } - - // Clear input after sorting to release objects - // See https://github.com/jquery/sizzle/pull/225 - sortInput = null; - - return results; -}; - -/** - * Utility function for retrieving the text value of an array of DOM nodes - * @param {Array|Element} elem - */ -getText = Sizzle.getText = function( elem ) { - var node, - ret = "", - i = 0, - nodeType = elem.nodeType; - - if ( !nodeType ) { - - // If no nodeType, this is expected to be an array - while ( ( node = elem[ i++ ] ) ) { - - // Do not traverse comment nodes - ret += getText( node ); - } - } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { - - // Use textContent for elements - // innerText usage removed for consistency of new lines (jQuery #11153) - if ( typeof elem.textContent === "string" ) { - return elem.textContent; - } else { - - // Traverse its children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - ret += getText( elem ); - } - } - } else if ( nodeType === 3 || nodeType === 4 ) { - return elem.nodeValue; - } - - // Do not include comment or processing instruction nodes - - return ret; -}; - -Expr = Sizzle.selectors = { - - // Can be adjusted by the user - cacheLength: 50, - - createPseudo: markFunction, - - match: matchExpr, - - attrHandle: {}, - - find: {}, - - relative: { - ">": { dir: "parentNode", first: true }, - " ": { dir: "parentNode" }, - "+": { dir: "previousSibling", first: true }, - "~": { dir: "previousSibling" } - }, - - preFilter: { - "ATTR": function( match ) { - match[ 1 ] = match[ 1 ].replace( runescape, funescape ); - - // Move the given value to match[3] whether quoted or unquoted - match[ 3 ] = ( match[ 3 ] || match[ 4 ] || - match[ 5 ] || "" ).replace( runescape, funescape ); - - if ( match[ 2 ] === "~=" ) { - match[ 3 ] = " " + match[ 3 ] + " "; - } - - return match.slice( 0, 4 ); - }, - - "CHILD": function( match ) { - - /* matches from matchExpr["CHILD"] - 1 type (only|nth|...) - 2 what (child|of-type) - 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) - 4 xn-component of xn+y argument ([+-]?\d*n|) - 5 sign of xn-component - 6 x of xn-component - 7 sign of y-component - 8 y of y-component - */ - match[ 1 ] = match[ 1 ].toLowerCase(); - - if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { - - // nth-* requires argument - if ( !match[ 3 ] ) { - Sizzle.error( match[ 0 ] ); - } - - // numeric x and y parameters for Expr.filter.CHILD - // remember that false/true cast respectively to 0/1 - match[ 4 ] = +( match[ 4 ] ? - match[ 5 ] + ( match[ 6 ] || 1 ) : - 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) ); - match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); - - // other types prohibit arguments - } else if ( match[ 3 ] ) { - Sizzle.error( match[ 0 ] ); - } - - return match; - }, - - "PSEUDO": function( match ) { - var excess, - unquoted = !match[ 6 ] && match[ 2 ]; - - if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) { - return null; - } - - // Accept quoted arguments as-is - if ( match[ 3 ] ) { - match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; - - // Strip excess characters from unquoted arguments - } else if ( unquoted && rpseudo.test( unquoted ) && - - // Get excess from tokenize (recursively) - ( excess = tokenize( unquoted, true ) ) && - - // advance to the next closing parenthesis - ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { - - // excess is a negative index - match[ 0 ] = match[ 0 ].slice( 0, excess ); - match[ 2 ] = unquoted.slice( 0, excess ); - } - - // Return only captures needed by the pseudo filter method (type and argument) - return match.slice( 0, 3 ); - } - }, - - filter: { - - "TAG": function( nodeNameSelector ) { - var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); - return nodeNameSelector === "*" ? - function() { - return true; - } : - function( elem ) { - return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; - }; - }, - - "CLASS": function( className ) { - var pattern = classCache[ className + " " ]; - - return pattern || - ( pattern = new RegExp( "(^|" + whitespace + - ")" + className + "(" + whitespace + "|$)" ) ) && classCache( - className, function( elem ) { - return pattern.test( - typeof elem.className === "string" && elem.className || - typeof elem.getAttribute !== "undefined" && - elem.getAttribute( "class" ) || - "" - ); - } ); - }, - - "ATTR": function( name, operator, check ) { - return function( elem ) { - var result = Sizzle.attr( elem, name ); - - if ( result == null ) { - return operator === "!="; - } - if ( !operator ) { - return true; - } - - result += ""; - - /* eslint-disable max-len */ - - return operator === "=" ? result === check : - operator === "!=" ? result !== check : - operator === "^=" ? check && result.indexOf( check ) === 0 : - operator === "*=" ? check && result.indexOf( check ) > -1 : - operator === "$=" ? check && result.slice( -check.length ) === check : - operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : - operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : - false; - /* eslint-enable max-len */ - - }; - }, - - "CHILD": function( type, what, _argument, first, last ) { - var simple = type.slice( 0, 3 ) !== "nth", - forward = type.slice( -4 ) !== "last", - ofType = what === "of-type"; - - return first === 1 && last === 0 ? - - // Shortcut for :nth-*(n) - function( elem ) { - return !!elem.parentNode; - } : - - function( elem, _context, xml ) { - var cache, uniqueCache, outerCache, node, nodeIndex, start, - dir = simple !== forward ? "nextSibling" : "previousSibling", - parent = elem.parentNode, - name = ofType && elem.nodeName.toLowerCase(), - useCache = !xml && !ofType, - diff = false; - - if ( parent ) { - - // :(first|last|only)-(child|of-type) - if ( simple ) { - while ( dir ) { - node = elem; - while ( ( node = node[ dir ] ) ) { - if ( ofType ? - node.nodeName.toLowerCase() === name : - node.nodeType === 1 ) { - - return false; - } - } - - // Reverse direction for :only-* (if we haven't yet done so) - start = dir = type === "only" && !start && "nextSibling"; - } - return true; - } - - start = [ forward ? parent.firstChild : parent.lastChild ]; - - // non-xml :nth-child(...) stores cache data on `parent` - if ( forward && useCache ) { - - // Seek `elem` from a previously-cached index - - // ...in a gzip-friendly way - node = parent; - outerCache = node[ expando ] || ( node[ expando ] = {} ); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - ( outerCache[ node.uniqueID ] = {} ); - - cache = uniqueCache[ type ] || []; - nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; - diff = nodeIndex && cache[ 2 ]; - node = nodeIndex && parent.childNodes[ nodeIndex ]; - - while ( ( node = ++nodeIndex && node && node[ dir ] || - - // Fallback to seeking `elem` from the start - ( diff = nodeIndex = 0 ) || start.pop() ) ) { - - // When found, cache indexes on `parent` and break - if ( node.nodeType === 1 && ++diff && node === elem ) { - uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; - break; - } - } - - } else { - - // Use previously-cached element index if available - if ( useCache ) { - - // ...in a gzip-friendly way - node = elem; - outerCache = node[ expando ] || ( node[ expando ] = {} ); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - ( outerCache[ node.uniqueID ] = {} ); - - cache = uniqueCache[ type ] || []; - nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; - diff = nodeIndex; - } - - // xml :nth-child(...) - // or :nth-last-child(...) or :nth(-last)?-of-type(...) - if ( diff === false ) { - - // Use the same loop as above to seek `elem` from the start - while ( ( node = ++nodeIndex && node && node[ dir ] || - ( diff = nodeIndex = 0 ) || start.pop() ) ) { - - if ( ( ofType ? - node.nodeName.toLowerCase() === name : - node.nodeType === 1 ) && - ++diff ) { - - // Cache the index of each encountered element - if ( useCache ) { - outerCache = node[ expando ] || - ( node[ expando ] = {} ); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - ( outerCache[ node.uniqueID ] = {} ); - - uniqueCache[ type ] = [ dirruns, diff ]; - } - - if ( node === elem ) { - break; - } - } - } - } - } - - // Incorporate the offset, then check against cycle size - diff -= last; - return diff === first || ( diff % first === 0 && diff / first >= 0 ); - } - }; - }, - - "PSEUDO": function( pseudo, argument ) { - - // pseudo-class names are case-insensitive - // http://www.w3.org/TR/selectors/#pseudo-classes - // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters - // Remember that setFilters inherits from pseudos - var args, - fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || - Sizzle.error( "unsupported pseudo: " + pseudo ); - - // The user may use createPseudo to indicate that - // arguments are needed to create the filter function - // just as Sizzle does - if ( fn[ expando ] ) { - return fn( argument ); - } - - // But maintain support for old signatures - if ( fn.length > 1 ) { - args = [ pseudo, pseudo, "", argument ]; - return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? - markFunction( function( seed, matches ) { - var idx, - matched = fn( seed, argument ), - i = matched.length; - while ( i-- ) { - idx = indexOf( seed, matched[ i ] ); - seed[ idx ] = !( matches[ idx ] = matched[ i ] ); - } - } ) : - function( elem ) { - return fn( elem, 0, args ); - }; - } - - return fn; - } - }, - - pseudos: { - - // Potentially complex pseudos - "not": markFunction( function( selector ) { - - // Trim the selector passed to compile - // to avoid treating leading and trailing - // spaces as combinators - var input = [], - results = [], - matcher = compile( selector.replace( rtrim, "$1" ) ); - - return matcher[ expando ] ? - markFunction( function( seed, matches, _context, xml ) { - var elem, - unmatched = matcher( seed, null, xml, [] ), - i = seed.length; - - // Match elements unmatched by `matcher` - while ( i-- ) { - if ( ( elem = unmatched[ i ] ) ) { - seed[ i ] = !( matches[ i ] = elem ); - } - } - } ) : - function( elem, _context, xml ) { - input[ 0 ] = elem; - matcher( input, null, xml, results ); - - // Don't keep the element (issue #299) - input[ 0 ] = null; - return !results.pop(); - }; - } ), - - "has": markFunction( function( selector ) { - return function( elem ) { - return Sizzle( selector, elem ).length > 0; - }; - } ), - - "contains": markFunction( function( text ) { - text = text.replace( runescape, funescape ); - return function( elem ) { - return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1; - }; - } ), - - // "Whether an element is represented by a :lang() selector - // is based solely on the element's language value - // being equal to the identifier C, - // or beginning with the identifier C immediately followed by "-". - // The matching of C against the element's language value is performed case-insensitively. - // The identifier C does not have to be a valid language name." - // http://www.w3.org/TR/selectors/#lang-pseudo - "lang": markFunction( function( lang ) { - - // lang value must be a valid identifier - if ( !ridentifier.test( lang || "" ) ) { - Sizzle.error( "unsupported lang: " + lang ); - } - lang = lang.replace( runescape, funescape ).toLowerCase(); - return function( elem ) { - var elemLang; - do { - if ( ( elemLang = documentIsHTML ? - elem.lang : - elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { - - elemLang = elemLang.toLowerCase(); - return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; - } - } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); - return false; - }; - } ), - - // Miscellaneous - "target": function( elem ) { - var hash = window.location && window.location.hash; - return hash && hash.slice( 1 ) === elem.id; - }, - - "root": function( elem ) { - return elem === docElem; - }, - - "focus": function( elem ) { - return elem === document.activeElement && - ( !document.hasFocus || document.hasFocus() ) && - !!( elem.type || elem.href || ~elem.tabIndex ); - }, - - // Boolean properties - "enabled": createDisabledPseudo( false ), - "disabled": createDisabledPseudo( true ), - - "checked": function( elem ) { - - // In CSS3, :checked should return both checked and selected elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - var nodeName = elem.nodeName.toLowerCase(); - return ( nodeName === "input" && !!elem.checked ) || - ( nodeName === "option" && !!elem.selected ); - }, - - "selected": function( elem ) { - - // Accessing this property makes selected-by-default - // options in Safari work properly - if ( elem.parentNode ) { - // eslint-disable-next-line no-unused-expressions - elem.parentNode.selectedIndex; - } - - return elem.selected === true; - }, - - // Contents - "empty": function( elem ) { - - // http://www.w3.org/TR/selectors/#empty-pseudo - // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), - // but not by others (comment: 8; processing instruction: 7; etc.) - // nodeType < 6 works because attributes (2) do not appear as children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - if ( elem.nodeType < 6 ) { - return false; - } - } - return true; - }, - - "parent": function( elem ) { - return !Expr.pseudos[ "empty" ]( elem ); - }, - - // Element/input types - "header": function( elem ) { - return rheader.test( elem.nodeName ); - }, - - "input": function( elem ) { - return rinputs.test( elem.nodeName ); - }, - - "button": function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === "button" || name === "button"; - }, - - "text": function( elem ) { - var attr; - return elem.nodeName.toLowerCase() === "input" && - elem.type === "text" && - - // Support: IE<8 - // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" - ( ( attr = elem.getAttribute( "type" ) ) == null || - attr.toLowerCase() === "text" ); - }, - - // Position-in-collection - "first": createPositionalPseudo( function() { - return [ 0 ]; - } ), - - "last": createPositionalPseudo( function( _matchIndexes, length ) { - return [ length - 1 ]; - } ), - - "eq": createPositionalPseudo( function( _matchIndexes, length, argument ) { - return [ argument < 0 ? argument + length : argument ]; - } ), - - "even": createPositionalPseudo( function( matchIndexes, length ) { - var i = 0; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - } ), - - "odd": createPositionalPseudo( function( matchIndexes, length ) { - var i = 1; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - } ), - - "lt": createPositionalPseudo( function( matchIndexes, length, argument ) { - var i = argument < 0 ? - argument + length : - argument > length ? - length : - argument; - for ( ; --i >= 0; ) { - matchIndexes.push( i ); - } - return matchIndexes; - } ), - - "gt": createPositionalPseudo( function( matchIndexes, length, argument ) { - var i = argument < 0 ? argument + length : argument; - for ( ; ++i < length; ) { - matchIndexes.push( i ); - } - return matchIndexes; - } ) - } -}; - -Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ]; - -// Add button/input type pseudos -for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { - Expr.pseudos[ i ] = createInputPseudo( i ); -} -for ( i in { submit: true, reset: true } ) { - Expr.pseudos[ i ] = createButtonPseudo( i ); -} - -// Easy API for creating new setFilters -function setFilters() {} -setFilters.prototype = Expr.filters = Expr.pseudos; -Expr.setFilters = new setFilters(); - -tokenize = Sizzle.tokenize = function( selector, parseOnly ) { - var matched, match, tokens, type, - soFar, groups, preFilters, - cached = tokenCache[ selector + " " ]; - - if ( cached ) { - return parseOnly ? 0 : cached.slice( 0 ); - } - - soFar = selector; - groups = []; - preFilters = Expr.preFilter; - - while ( soFar ) { - - // Comma and first run - if ( !matched || ( match = rcomma.exec( soFar ) ) ) { - if ( match ) { - - // Don't consume trailing commas as valid - soFar = soFar.slice( match[ 0 ].length ) || soFar; - } - groups.push( ( tokens = [] ) ); - } - - matched = false; - - // Combinators - if ( ( match = rcombinators.exec( soFar ) ) ) { - matched = match.shift(); - tokens.push( { - value: matched, - - // Cast descendant combinators to space - type: match[ 0 ].replace( rtrim, " " ) - } ); - soFar = soFar.slice( matched.length ); - } - - // Filters - for ( type in Expr.filter ) { - if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || - ( match = preFilters[ type ]( match ) ) ) ) { - matched = match.shift(); - tokens.push( { - value: matched, - type: type, - matches: match - } ); - soFar = soFar.slice( matched.length ); - } - } - - if ( !matched ) { - break; - } - } - - // Return the length of the invalid excess - // if we're just parsing - // Otherwise, throw an error or return tokens - return parseOnly ? - soFar.length : - soFar ? - Sizzle.error( selector ) : - - // Cache the tokens - tokenCache( selector, groups ).slice( 0 ); -}; - -function toSelector( tokens ) { - var i = 0, - len = tokens.length, - selector = ""; - for ( ; i < len; i++ ) { - selector += tokens[ i ].value; - } - return selector; -} - -function addCombinator( matcher, combinator, base ) { - var dir = combinator.dir, - skip = combinator.next, - key = skip || dir, - checkNonElements = base && key === "parentNode", - doneName = done++; - - return combinator.first ? - - // Check against closest ancestor/preceding element - function( elem, context, xml ) { - while ( ( elem = elem[ dir ] ) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - return matcher( elem, context, xml ); - } - } - return false; - } : - - // Check against all ancestor/preceding elements - function( elem, context, xml ) { - var oldCache, uniqueCache, outerCache, - newCache = [ dirruns, doneName ]; - - // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching - if ( xml ) { - while ( ( elem = elem[ dir ] ) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - if ( matcher( elem, context, xml ) ) { - return true; - } - } - } - } else { - while ( ( elem = elem[ dir ] ) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - outerCache = elem[ expando ] || ( elem[ expando ] = {} ); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ elem.uniqueID ] || - ( outerCache[ elem.uniqueID ] = {} ); - - if ( skip && skip === elem.nodeName.toLowerCase() ) { - elem = elem[ dir ] || elem; - } else if ( ( oldCache = uniqueCache[ key ] ) && - oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { - - // Assign to newCache so results back-propagate to previous elements - return ( newCache[ 2 ] = oldCache[ 2 ] ); - } else { - - // Reuse newcache so results back-propagate to previous elements - uniqueCache[ key ] = newCache; - - // A match means we're done; a fail means we have to keep checking - if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { - return true; - } - } - } - } - } - return false; - }; -} - -function elementMatcher( matchers ) { - return matchers.length > 1 ? - function( elem, context, xml ) { - var i = matchers.length; - while ( i-- ) { - if ( !matchers[ i ]( elem, context, xml ) ) { - return false; - } - } - return true; - } : - matchers[ 0 ]; -} - -function multipleContexts( selector, contexts, results ) { - var i = 0, - len = contexts.length; - for ( ; i < len; i++ ) { - Sizzle( selector, contexts[ i ], results ); - } - return results; -} - -function condense( unmatched, map, filter, context, xml ) { - var elem, - newUnmatched = [], - i = 0, - len = unmatched.length, - mapped = map != null; - - for ( ; i < len; i++ ) { - if ( ( elem = unmatched[ i ] ) ) { - if ( !filter || filter( elem, context, xml ) ) { - newUnmatched.push( elem ); - if ( mapped ) { - map.push( i ); - } - } - } - } - - return newUnmatched; -} - -function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { - if ( postFilter && !postFilter[ expando ] ) { - postFilter = setMatcher( postFilter ); - } - if ( postFinder && !postFinder[ expando ] ) { - postFinder = setMatcher( postFinder, postSelector ); - } - return markFunction( function( seed, results, context, xml ) { - var temp, i, elem, - preMap = [], - postMap = [], - preexisting = results.length, - - // Get initial elements from seed or context - elems = seed || multipleContexts( - selector || "*", - context.nodeType ? [ context ] : context, - [] - ), - - // Prefilter to get matcher input, preserving a map for seed-results synchronization - matcherIn = preFilter && ( seed || !selector ) ? - condense( elems, preMap, preFilter, context, xml ) : - elems, - - matcherOut = matcher ? - - // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, - postFinder || ( seed ? preFilter : preexisting || postFilter ) ? - - // ...intermediate processing is necessary - [] : - - // ...otherwise use results directly - results : - matcherIn; - - // Find primary matches - if ( matcher ) { - matcher( matcherIn, matcherOut, context, xml ); - } - - // Apply postFilter - if ( postFilter ) { - temp = condense( matcherOut, postMap ); - postFilter( temp, [], context, xml ); - - // Un-match failing elements by moving them back to matcherIn - i = temp.length; - while ( i-- ) { - if ( ( elem = temp[ i ] ) ) { - matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); - } - } - } - - if ( seed ) { - if ( postFinder || preFilter ) { - if ( postFinder ) { - - // Get the final matcherOut by condensing this intermediate into postFinder contexts - temp = []; - i = matcherOut.length; - while ( i-- ) { - if ( ( elem = matcherOut[ i ] ) ) { - - // Restore matcherIn since elem is not yet a final match - temp.push( ( matcherIn[ i ] = elem ) ); - } - } - postFinder( null, ( matcherOut = [] ), temp, xml ); - } - - // Move matched elements from seed to results to keep them synchronized - i = matcherOut.length; - while ( i-- ) { - if ( ( elem = matcherOut[ i ] ) && - ( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) { - - seed[ temp ] = !( results[ temp ] = elem ); - } - } - } - - // Add elements to results, through postFinder if defined - } else { - matcherOut = condense( - matcherOut === results ? - matcherOut.splice( preexisting, matcherOut.length ) : - matcherOut - ); - if ( postFinder ) { - postFinder( null, results, matcherOut, xml ); - } else { - push.apply( results, matcherOut ); - } - } - } ); -} - -function matcherFromTokens( tokens ) { - var checkContext, matcher, j, - len = tokens.length, - leadingRelative = Expr.relative[ tokens[ 0 ].type ], - implicitRelative = leadingRelative || Expr.relative[ " " ], - i = leadingRelative ? 1 : 0, - - // The foundational matcher ensures that elements are reachable from top-level context(s) - matchContext = addCombinator( function( elem ) { - return elem === checkContext; - }, implicitRelative, true ), - matchAnyContext = addCombinator( function( elem ) { - return indexOf( checkContext, elem ) > -1; - }, implicitRelative, true ), - matchers = [ function( elem, context, xml ) { - var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( - ( checkContext = context ).nodeType ? - matchContext( elem, context, xml ) : - matchAnyContext( elem, context, xml ) ); - - // Avoid hanging onto element (issue #299) - checkContext = null; - return ret; - } ]; - - for ( ; i < len; i++ ) { - if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { - matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; - } else { - matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); - - // Return special upon seeing a positional matcher - if ( matcher[ expando ] ) { - - // Find the next relative operator (if any) for proper handling - j = ++i; - for ( ; j < len; j++ ) { - if ( Expr.relative[ tokens[ j ].type ] ) { - break; - } - } - return setMatcher( - i > 1 && elementMatcher( matchers ), - i > 1 && toSelector( - - // If the preceding token was a descendant combinator, insert an implicit any-element `*` - tokens - .slice( 0, i - 1 ) - .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) - ).replace( rtrim, "$1" ), - matcher, - i < j && matcherFromTokens( tokens.slice( i, j ) ), - j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), - j < len && toSelector( tokens ) - ); - } - matchers.push( matcher ); - } - } - - return elementMatcher( matchers ); -} - -function matcherFromGroupMatchers( elementMatchers, setMatchers ) { - var bySet = setMatchers.length > 0, - byElement = elementMatchers.length > 0, - superMatcher = function( seed, context, xml, results, outermost ) { - var elem, j, matcher, - matchedCount = 0, - i = "0", - unmatched = seed && [], - setMatched = [], - contextBackup = outermostContext, - - // We must always have either seed elements or outermost context - elems = seed || byElement && Expr.find[ "TAG" ]( "*", outermost ), - - // Use integer dirruns iff this is the outermost matcher - dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ), - len = elems.length; - - if ( outermost ) { - - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - outermostContext = context == document || context || outermost; - } - - // Add elements passing elementMatchers directly to results - // Support: IE<9, Safari - // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id - for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) { - if ( byElement && elem ) { - j = 0; - - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( !context && elem.ownerDocument != document ) { - setDocument( elem ); - xml = !documentIsHTML; - } - while ( ( matcher = elementMatchers[ j++ ] ) ) { - if ( matcher( elem, context || document, xml ) ) { - results.push( elem ); - break; - } - } - if ( outermost ) { - dirruns = dirrunsUnique; - } - } - - // Track unmatched elements for set filters - if ( bySet ) { - - // They will have gone through all possible matchers - if ( ( elem = !matcher && elem ) ) { - matchedCount--; - } - - // Lengthen the array for every element, matched or not - if ( seed ) { - unmatched.push( elem ); - } - } - } - - // `i` is now the count of elements visited above, and adding it to `matchedCount` - // makes the latter nonnegative. - matchedCount += i; - - // Apply set filters to unmatched elements - // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` - // equals `i`), unless we didn't visit _any_ elements in the above loop because we have - // no element matchers and no seed. - // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that - // case, which will result in a "00" `matchedCount` that differs from `i` but is also - // numerically zero. - if ( bySet && i !== matchedCount ) { - j = 0; - while ( ( matcher = setMatchers[ j++ ] ) ) { - matcher( unmatched, setMatched, context, xml ); - } - - if ( seed ) { - - // Reintegrate element matches to eliminate the need for sorting - if ( matchedCount > 0 ) { - while ( i-- ) { - if ( !( unmatched[ i ] || setMatched[ i ] ) ) { - setMatched[ i ] = pop.call( results ); - } - } - } - - // Discard index placeholder values to get only actual matches - setMatched = condense( setMatched ); - } - - // Add matches to results - push.apply( results, setMatched ); - - // Seedless set matches succeeding multiple successful matchers stipulate sorting - if ( outermost && !seed && setMatched.length > 0 && - ( matchedCount + setMatchers.length ) > 1 ) { - - Sizzle.uniqueSort( results ); - } - } - - // Override manipulation of globals by nested matchers - if ( outermost ) { - dirruns = dirrunsUnique; - outermostContext = contextBackup; - } - - return unmatched; - }; - - return bySet ? - markFunction( superMatcher ) : - superMatcher; -} - -compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { - var i, - setMatchers = [], - elementMatchers = [], - cached = compilerCache[ selector + " " ]; - - if ( !cached ) { - - // Generate a function of recursive functions that can be used to check each element - if ( !match ) { - match = tokenize( selector ); - } - i = match.length; - while ( i-- ) { - cached = matcherFromTokens( match[ i ] ); - if ( cached[ expando ] ) { - setMatchers.push( cached ); - } else { - elementMatchers.push( cached ); - } - } - - // Cache the compiled function - cached = compilerCache( - selector, - matcherFromGroupMatchers( elementMatchers, setMatchers ) - ); - - // Save selector and tokenization - cached.selector = selector; - } - return cached; -}; - -/** - * A low-level selection function that works with Sizzle's compiled - * selector functions - * @param {String|Function} selector A selector or a pre-compiled - * selector function built with Sizzle.compile - * @param {Element} context - * @param {Array} [results] - * @param {Array} [seed] A set of elements to match against - */ -select = Sizzle.select = function( selector, context, results, seed ) { - var i, tokens, token, type, find, - compiled = typeof selector === "function" && selector, - match = !seed && tokenize( ( selector = compiled.selector || selector ) ); - - results = results || []; - - // Try to minimize operations if there is only one selector in the list and no seed - // (the latter of which guarantees us context) - if ( match.length === 1 ) { - - // Reduce context if the leading compound selector is an ID - tokens = match[ 0 ] = match[ 0 ].slice( 0 ); - if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && - context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { - - context = ( Expr.find[ "ID" ]( token.matches[ 0 ] - .replace( runescape, funescape ), context ) || [] )[ 0 ]; - if ( !context ) { - return results; - - // Precompiled matchers will still verify ancestry, so step up a level - } else if ( compiled ) { - context = context.parentNode; - } - - selector = selector.slice( tokens.shift().value.length ); - } - - // Fetch a seed set for right-to-left matching - i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length; - while ( i-- ) { - token = tokens[ i ]; - - // Abort if we hit a combinator - if ( Expr.relative[ ( type = token.type ) ] ) { - break; - } - if ( ( find = Expr.find[ type ] ) ) { - - // Search, expanding context for leading sibling combinators - if ( ( seed = find( - token.matches[ 0 ].replace( runescape, funescape ), - rsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) || - context - ) ) ) { - - // If seed is empty or no tokens remain, we can return early - tokens.splice( i, 1 ); - selector = seed.length && toSelector( tokens ); - if ( !selector ) { - push.apply( results, seed ); - return results; - } - - break; - } - } - } - } - - // Compile and execute a filtering function if one is not provided - // Provide `match` to avoid retokenization if we modified the selector above - ( compiled || compile( selector, match ) )( - seed, - context, - !documentIsHTML, - results, - !context || rsibling.test( selector ) && testContext( context.parentNode ) || context - ); - return results; -}; - -// One-time assignments - -// Sort stability -support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando; - -// Support: Chrome 14-35+ -// Always assume duplicates if they aren't passed to the comparison function -support.detectDuplicates = !!hasDuplicate; - -// Initialize against the default document -setDocument(); - -// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) -// Detached nodes confoundingly follow *each other* -support.sortDetached = assert( function( el ) { - - // Should return 1, but returns 4 (following) - return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1; -} ); - -// Support: IE<8 -// Prevent attribute/property "interpolation" -// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx -if ( !assert( function( el ) { - el.innerHTML = ""; - return el.firstChild.getAttribute( "href" ) === "#"; -} ) ) { - addHandle( "type|href|height|width", function( elem, name, isXML ) { - if ( !isXML ) { - return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); - } - } ); -} - -// Support: IE<9 -// Use defaultValue in place of getAttribute("value") -if ( !support.attributes || !assert( function( el ) { - el.innerHTML = ""; - el.firstChild.setAttribute( "value", "" ); - return el.firstChild.getAttribute( "value" ) === ""; -} ) ) { - addHandle( "value", function( elem, _name, isXML ) { - if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { - return elem.defaultValue; - } - } ); -} - -// Support: IE<9 -// Use getAttributeNode to fetch booleans when getAttribute lies -if ( !assert( function( el ) { - return el.getAttribute( "disabled" ) == null; -} ) ) { - addHandle( booleans, function( elem, name, isXML ) { - var val; - if ( !isXML ) { - return elem[ name ] === true ? name.toLowerCase() : - ( val = elem.getAttributeNode( name ) ) && val.specified ? - val.value : - null; - } - } ); -} - -return Sizzle; - -} )( window ); - - - -jQuery.find = Sizzle; -jQuery.expr = Sizzle.selectors; - -// Deprecated -jQuery.expr[ ":" ] = jQuery.expr.pseudos; -jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; -jQuery.text = Sizzle.getText; -jQuery.isXMLDoc = Sizzle.isXML; -jQuery.contains = Sizzle.contains; -jQuery.escapeSelector = Sizzle.escape; - - - - -var dir = function( elem, dir, until ) { - var matched = [], - truncate = until !== undefined; - - while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { - if ( elem.nodeType === 1 ) { - if ( truncate && jQuery( elem ).is( until ) ) { - break; - } - matched.push( elem ); - } - } - return matched; -}; - - -var siblings = function( n, elem ) { - var matched = []; - - for ( ; n; n = n.nextSibling ) { - if ( n.nodeType === 1 && n !== elem ) { - matched.push( n ); - } - } - - return matched; -}; - - -var rneedsContext = jQuery.expr.match.needsContext; - - - -function nodeName( elem, name ) { - - return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); - -} -var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); - - - -// Implement the identical functionality for filter and not -function winnow( elements, qualifier, not ) { - if ( isFunction( qualifier ) ) { - return jQuery.grep( elements, function( elem, i ) { - return !!qualifier.call( elem, i, elem ) !== not; - } ); - } - - // Single element - if ( qualifier.nodeType ) { - return jQuery.grep( elements, function( elem ) { - return ( elem === qualifier ) !== not; - } ); - } - - // Arraylike of elements (jQuery, arguments, Array) - if ( typeof qualifier !== "string" ) { - return jQuery.grep( elements, function( elem ) { - return ( indexOf.call( qualifier, elem ) > -1 ) !== not; - } ); - } - - // Filtered directly for both simple and complex selectors - return jQuery.filter( qualifier, elements, not ); -} - -jQuery.filter = function( expr, elems, not ) { - var elem = elems[ 0 ]; - - if ( not ) { - expr = ":not(" + expr + ")"; - } - - if ( elems.length === 1 && elem.nodeType === 1 ) { - return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; - } - - return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { - return elem.nodeType === 1; - } ) ); -}; - -jQuery.fn.extend( { - find: function( selector ) { - var i, ret, - len = this.length, - self = this; - - if ( typeof selector !== "string" ) { - return this.pushStack( jQuery( selector ).filter( function() { - for ( i = 0; i < len; i++ ) { - if ( jQuery.contains( self[ i ], this ) ) { - return true; - } - } - } ) ); - } - - ret = this.pushStack( [] ); - - for ( i = 0; i < len; i++ ) { - jQuery.find( selector, self[ i ], ret ); - } - - return len > 1 ? jQuery.uniqueSort( ret ) : ret; - }, - filter: function( selector ) { - return this.pushStack( winnow( this, selector || [], false ) ); - }, - not: function( selector ) { - return this.pushStack( winnow( this, selector || [], true ) ); - }, - is: function( selector ) { - return !!winnow( - this, - - // If this is a positional/relative selector, check membership in the returned set - // so $("p:first").is("p:last") won't return true for a doc with two "p". - typeof selector === "string" && rneedsContext.test( selector ) ? - jQuery( selector ) : - selector || [], - false - ).length; - } -} ); - - -// Initialize a jQuery object - - -// A central reference to the root jQuery(document) -var rootjQuery, - - // A simple way to check for HTML strings - // Prioritize #id over to avoid XSS via location.hash (#9521) - // Strict HTML recognition (#11290: must start with <) - // Shortcut simple #id case for speed - rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, - - init = jQuery.fn.init = function( selector, context, root ) { - var match, elem; - - // HANDLE: $(""), $(null), $(undefined), $(false) - if ( !selector ) { - return this; - } - - // Method init() accepts an alternate rootjQuery - // so migrate can support jQuery.sub (gh-2101) - root = root || rootjQuery; - - // Handle HTML strings - if ( typeof selector === "string" ) { - if ( selector[ 0 ] === "<" && - selector[ selector.length - 1 ] === ">" && - selector.length >= 3 ) { - - // Assume that strings that start and end with <> are HTML and skip the regex check - match = [ null, selector, null ]; - - } else { - match = rquickExpr.exec( selector ); - } - - // Match html or make sure no context is specified for #id - if ( match && ( match[ 1 ] || !context ) ) { - - // HANDLE: $(html) -> $(array) - if ( match[ 1 ] ) { - context = context instanceof jQuery ? context[ 0 ] : context; - - // Option to run scripts is true for back-compat - // Intentionally let the error be thrown if parseHTML is not present - jQuery.merge( this, jQuery.parseHTML( - match[ 1 ], - context && context.nodeType ? context.ownerDocument || context : document, - true - ) ); - - // HANDLE: $(html, props) - if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { - for ( match in context ) { - - // Properties of context are called as methods if possible - if ( isFunction( this[ match ] ) ) { - this[ match ]( context[ match ] ); - - // ...and otherwise set as attributes - } else { - this.attr( match, context[ match ] ); - } - } - } - - return this; - - // HANDLE: $(#id) - } else { - elem = document.getElementById( match[ 2 ] ); - - if ( elem ) { - - // Inject the element directly into the jQuery object - this[ 0 ] = elem; - this.length = 1; - } - return this; - } - - // HANDLE: $(expr, $(...)) - } else if ( !context || context.jquery ) { - return ( context || root ).find( selector ); - - // HANDLE: $(expr, context) - // (which is just equivalent to: $(context).find(expr) - } else { - return this.constructor( context ).find( selector ); - } - - // HANDLE: $(DOMElement) - } else if ( selector.nodeType ) { - this[ 0 ] = selector; - this.length = 1; - return this; - - // HANDLE: $(function) - // Shortcut for document ready - } else if ( isFunction( selector ) ) { - return root.ready !== undefined ? - root.ready( selector ) : - - // Execute immediately if ready is not present - selector( jQuery ); - } - - return jQuery.makeArray( selector, this ); - }; - -// Give the init function the jQuery prototype for later instantiation -init.prototype = jQuery.fn; - -// Initialize central reference -rootjQuery = jQuery( document ); - - -var rparentsprev = /^(?:parents|prev(?:Until|All))/, - - // Methods guaranteed to produce a unique set when starting from a unique set - guaranteedUnique = { - children: true, - contents: true, - next: true, - prev: true - }; - -jQuery.fn.extend( { - has: function( target ) { - var targets = jQuery( target, this ), - l = targets.length; - - return this.filter( function() { - var i = 0; - for ( ; i < l; i++ ) { - if ( jQuery.contains( this, targets[ i ] ) ) { - return true; - } - } - } ); - }, - - closest: function( selectors, context ) { - var cur, - i = 0, - l = this.length, - matched = [], - targets = typeof selectors !== "string" && jQuery( selectors ); - - // Positional selectors never match, since there's no _selection_ context - if ( !rneedsContext.test( selectors ) ) { - for ( ; i < l; i++ ) { - for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { - - // Always skip document fragments - if ( cur.nodeType < 11 && ( targets ? - targets.index( cur ) > -1 : - - // Don't pass non-elements to Sizzle - cur.nodeType === 1 && - jQuery.find.matchesSelector( cur, selectors ) ) ) { - - matched.push( cur ); - break; - } - } - } - } - - return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); - }, - - // Determine the position of an element within the set - index: function( elem ) { - - // No argument, return index in parent - if ( !elem ) { - return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; - } - - // Index in selector - if ( typeof elem === "string" ) { - return indexOf.call( jQuery( elem ), this[ 0 ] ); - } - - // Locate the position of the desired element - return indexOf.call( this, - - // If it receives a jQuery object, the first element is used - elem.jquery ? elem[ 0 ] : elem - ); - }, - - add: function( selector, context ) { - return this.pushStack( - jQuery.uniqueSort( - jQuery.merge( this.get(), jQuery( selector, context ) ) - ) - ); - }, - - addBack: function( selector ) { - return this.add( selector == null ? - this.prevObject : this.prevObject.filter( selector ) - ); - } -} ); - -function sibling( cur, dir ) { - while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} - return cur; -} - -jQuery.each( { - parent: function( elem ) { - var parent = elem.parentNode; - return parent && parent.nodeType !== 11 ? parent : null; - }, - parents: function( elem ) { - return dir( elem, "parentNode" ); - }, - parentsUntil: function( elem, _i, until ) { - return dir( elem, "parentNode", until ); - }, - next: function( elem ) { - return sibling( elem, "nextSibling" ); - }, - prev: function( elem ) { - return sibling( elem, "previousSibling" ); - }, - nextAll: function( elem ) { - return dir( elem, "nextSibling" ); - }, - prevAll: function( elem ) { - return dir( elem, "previousSibling" ); - }, - nextUntil: function( elem, _i, until ) { - return dir( elem, "nextSibling", until ); - }, - prevUntil: function( elem, _i, until ) { - return dir( elem, "previousSibling", until ); - }, - siblings: function( elem ) { - return siblings( ( elem.parentNode || {} ).firstChild, elem ); - }, - children: function( elem ) { - return siblings( elem.firstChild ); - }, - contents: function( elem ) { - if ( elem.contentDocument != null && - - // Support: IE 11+ - // elements with no `data` attribute has an object - // `contentDocument` with a `null` prototype. - getProto( elem.contentDocument ) ) { - - return elem.contentDocument; - } - - // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only - // Treat the template element as a regular one in browsers that - // don't support it. - if ( nodeName( elem, "template" ) ) { - elem = elem.content || elem; - } - - return jQuery.merge( [], elem.childNodes ); - } -}, function( name, fn ) { - jQuery.fn[ name ] = function( until, selector ) { - var matched = jQuery.map( this, fn, until ); - - if ( name.slice( -5 ) !== "Until" ) { - selector = until; - } - - if ( selector && typeof selector === "string" ) { - matched = jQuery.filter( selector, matched ); - } - - if ( this.length > 1 ) { - - // Remove duplicates - if ( !guaranteedUnique[ name ] ) { - jQuery.uniqueSort( matched ); - } - - // Reverse order for parents* and prev-derivatives - if ( rparentsprev.test( name ) ) { - matched.reverse(); - } - } - - return this.pushStack( matched ); - }; -} ); -var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); - - - -// Convert String-formatted options into Object-formatted ones -function createOptions( options ) { - var object = {}; - jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { - object[ flag ] = true; - } ); - return object; -} - -/* - * Create a callback list using the following parameters: - * - * options: an optional list of space-separated options that will change how - * the callback list behaves or a more traditional option object - * - * By default a callback list will act like an event callback list and can be - * "fired" multiple times. - * - * Possible options: - * - * once: will ensure the callback list can only be fired once (like a Deferred) - * - * memory: will keep track of previous values and will call any callback added - * after the list has been fired right away with the latest "memorized" - * values (like a Deferred) - * - * unique: will ensure a callback can only be added once (no duplicate in the list) - * - * stopOnFalse: interrupt callings when a callback returns false - * - */ -jQuery.Callbacks = function( options ) { - - // Convert options from String-formatted to Object-formatted if needed - // (we check in cache first) - options = typeof options === "string" ? - createOptions( options ) : - jQuery.extend( {}, options ); - - var // Flag to know if list is currently firing - firing, - - // Last fire value for non-forgettable lists - memory, - - // Flag to know if list was already fired - fired, - - // Flag to prevent firing - locked, - - // Actual callback list - list = [], - - // Queue of execution data for repeatable lists - queue = [], - - // Index of currently firing callback (modified by add/remove as needed) - firingIndex = -1, - - // Fire callbacks - fire = function() { - - // Enforce single-firing - locked = locked || options.once; - - // Execute callbacks for all pending executions, - // respecting firingIndex overrides and runtime changes - fired = firing = true; - for ( ; queue.length; firingIndex = -1 ) { - memory = queue.shift(); - while ( ++firingIndex < list.length ) { - - // Run callback and check for early termination - if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && - options.stopOnFalse ) { - - // Jump to end and forget the data so .add doesn't re-fire - firingIndex = list.length; - memory = false; - } - } - } - - // Forget the data if we're done with it - if ( !options.memory ) { - memory = false; - } - - firing = false; - - // Clean up if we're done firing for good - if ( locked ) { - - // Keep an empty list if we have data for future add calls - if ( memory ) { - list = []; - - // Otherwise, this object is spent - } else { - list = ""; - } - } - }, - - // Actual Callbacks object - self = { - - // Add a callback or a collection of callbacks to the list - add: function() { - if ( list ) { - - // If we have memory from a past run, we should fire after adding - if ( memory && !firing ) { - firingIndex = list.length - 1; - queue.push( memory ); - } - - ( function add( args ) { - jQuery.each( args, function( _, arg ) { - if ( isFunction( arg ) ) { - if ( !options.unique || !self.has( arg ) ) { - list.push( arg ); - } - } else if ( arg && arg.length && toType( arg ) !== "string" ) { - - // Inspect recursively - add( arg ); - } - } ); - } )( arguments ); - - if ( memory && !firing ) { - fire(); - } - } - return this; - }, - - // Remove a callback from the list - remove: function() { - jQuery.each( arguments, function( _, arg ) { - var index; - while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { - list.splice( index, 1 ); - - // Handle firing indexes - if ( index <= firingIndex ) { - firingIndex--; - } - } - } ); - return this; - }, - - // Check if a given callback is in the list. - // If no argument is given, return whether or not list has callbacks attached. - has: function( fn ) { - return fn ? - jQuery.inArray( fn, list ) > -1 : - list.length > 0; - }, - - // Remove all callbacks from the list - empty: function() { - if ( list ) { - list = []; - } - return this; - }, - - // Disable .fire and .add - // Abort any current/pending executions - // Clear all callbacks and values - disable: function() { - locked = queue = []; - list = memory = ""; - return this; - }, - disabled: function() { - return !list; - }, - - // Disable .fire - // Also disable .add unless we have memory (since it would have no effect) - // Abort any pending executions - lock: function() { - locked = queue = []; - if ( !memory && !firing ) { - list = memory = ""; - } - return this; - }, - locked: function() { - return !!locked; - }, - - // Call all callbacks with the given context and arguments - fireWith: function( context, args ) { - if ( !locked ) { - args = args || []; - args = [ context, args.slice ? args.slice() : args ]; - queue.push( args ); - if ( !firing ) { - fire(); - } - } - return this; - }, - - // Call all the callbacks with the given arguments - fire: function() { - self.fireWith( this, arguments ); - return this; - }, - - // To know if the callbacks have already been called at least once - fired: function() { - return !!fired; - } - }; - - return self; -}; - - -function Identity( v ) { - return v; -} -function Thrower( ex ) { - throw ex; -} - -function adoptValue( value, resolve, reject, noValue ) { - var method; - - try { - - // Check for promise aspect first to privilege synchronous behavior - if ( value && isFunction( ( method = value.promise ) ) ) { - method.call( value ).done( resolve ).fail( reject ); - - // Other thenables - } else if ( value && isFunction( ( method = value.then ) ) ) { - method.call( value, resolve, reject ); - - // Other non-thenables - } else { - - // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: - // * false: [ value ].slice( 0 ) => resolve( value ) - // * true: [ value ].slice( 1 ) => resolve() - resolve.apply( undefined, [ value ].slice( noValue ) ); - } - - // For Promises/A+, convert exceptions into rejections - // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in - // Deferred#then to conditionally suppress rejection. - } catch ( value ) { - - // Support: Android 4.0 only - // Strict mode functions invoked without .call/.apply get global-object context - reject.apply( undefined, [ value ] ); - } -} - -jQuery.extend( { - - Deferred: function( func ) { - var tuples = [ - - // action, add listener, callbacks, - // ... .then handlers, argument index, [final state] - [ "notify", "progress", jQuery.Callbacks( "memory" ), - jQuery.Callbacks( "memory" ), 2 ], - [ "resolve", "done", jQuery.Callbacks( "once memory" ), - jQuery.Callbacks( "once memory" ), 0, "resolved" ], - [ "reject", "fail", jQuery.Callbacks( "once memory" ), - jQuery.Callbacks( "once memory" ), 1, "rejected" ] - ], - state = "pending", - promise = { - state: function() { - return state; - }, - always: function() { - deferred.done( arguments ).fail( arguments ); - return this; - }, - "catch": function( fn ) { - return promise.then( null, fn ); - }, - - // Keep pipe for back-compat - pipe: function( /* fnDone, fnFail, fnProgress */ ) { - var fns = arguments; - - return jQuery.Deferred( function( newDefer ) { - jQuery.each( tuples, function( _i, tuple ) { - - // Map tuples (progress, done, fail) to arguments (done, fail, progress) - var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; - - // deferred.progress(function() { bind to newDefer or newDefer.notify }) - // deferred.done(function() { bind to newDefer or newDefer.resolve }) - // deferred.fail(function() { bind to newDefer or newDefer.reject }) - deferred[ tuple[ 1 ] ]( function() { - var returned = fn && fn.apply( this, arguments ); - if ( returned && isFunction( returned.promise ) ) { - returned.promise() - .progress( newDefer.notify ) - .done( newDefer.resolve ) - .fail( newDefer.reject ); - } else { - newDefer[ tuple[ 0 ] + "With" ]( - this, - fn ? [ returned ] : arguments - ); - } - } ); - } ); - fns = null; - } ).promise(); - }, - then: function( onFulfilled, onRejected, onProgress ) { - var maxDepth = 0; - function resolve( depth, deferred, handler, special ) { - return function() { - var that = this, - args = arguments, - mightThrow = function() { - var returned, then; - - // Support: Promises/A+ section 2.3.3.3.3 - // https://promisesaplus.com/#point-59 - // Ignore double-resolution attempts - if ( depth < maxDepth ) { - return; - } - - returned = handler.apply( that, args ); - - // Support: Promises/A+ section 2.3.1 - // https://promisesaplus.com/#point-48 - if ( returned === deferred.promise() ) { - throw new TypeError( "Thenable self-resolution" ); - } - - // Support: Promises/A+ sections 2.3.3.1, 3.5 - // https://promisesaplus.com/#point-54 - // https://promisesaplus.com/#point-75 - // Retrieve `then` only once - then = returned && - - // Support: Promises/A+ section 2.3.4 - // https://promisesaplus.com/#point-64 - // Only check objects and functions for thenability - ( typeof returned === "object" || - typeof returned === "function" ) && - returned.then; - - // Handle a returned thenable - if ( isFunction( then ) ) { - - // Special processors (notify) just wait for resolution - if ( special ) { - then.call( - returned, - resolve( maxDepth, deferred, Identity, special ), - resolve( maxDepth, deferred, Thrower, special ) - ); - - // Normal processors (resolve) also hook into progress - } else { - - // ...and disregard older resolution values - maxDepth++; - - then.call( - returned, - resolve( maxDepth, deferred, Identity, special ), - resolve( maxDepth, deferred, Thrower, special ), - resolve( maxDepth, deferred, Identity, - deferred.notifyWith ) - ); - } - - // Handle all other returned values - } else { - - // Only substitute handlers pass on context - // and multiple values (non-spec behavior) - if ( handler !== Identity ) { - that = undefined; - args = [ returned ]; - } - - // Process the value(s) - // Default process is resolve - ( special || deferred.resolveWith )( that, args ); - } - }, - - // Only normal processors (resolve) catch and reject exceptions - process = special ? - mightThrow : - function() { - try { - mightThrow(); - } catch ( e ) { - - if ( jQuery.Deferred.exceptionHook ) { - jQuery.Deferred.exceptionHook( e, - process.stackTrace ); - } - - // Support: Promises/A+ section 2.3.3.3.4.1 - // https://promisesaplus.com/#point-61 - // Ignore post-resolution exceptions - if ( depth + 1 >= maxDepth ) { - - // Only substitute handlers pass on context - // and multiple values (non-spec behavior) - if ( handler !== Thrower ) { - that = undefined; - args = [ e ]; - } - - deferred.rejectWith( that, args ); - } - } - }; - - // Support: Promises/A+ section 2.3.3.3.1 - // https://promisesaplus.com/#point-57 - // Re-resolve promises immediately to dodge false rejection from - // subsequent errors - if ( depth ) { - process(); - } else { - - // Call an optional hook to record the stack, in case of exception - // since it's otherwise lost when execution goes async - if ( jQuery.Deferred.getStackHook ) { - process.stackTrace = jQuery.Deferred.getStackHook(); - } - window.setTimeout( process ); - } - }; - } - - return jQuery.Deferred( function( newDefer ) { - - // progress_handlers.add( ... ) - tuples[ 0 ][ 3 ].add( - resolve( - 0, - newDefer, - isFunction( onProgress ) ? - onProgress : - Identity, - newDefer.notifyWith - ) - ); - - // fulfilled_handlers.add( ... ) - tuples[ 1 ][ 3 ].add( - resolve( - 0, - newDefer, - isFunction( onFulfilled ) ? - onFulfilled : - Identity - ) - ); - - // rejected_handlers.add( ... ) - tuples[ 2 ][ 3 ].add( - resolve( - 0, - newDefer, - isFunction( onRejected ) ? - onRejected : - Thrower - ) - ); - } ).promise(); - }, - - // Get a promise for this deferred - // If obj is provided, the promise aspect is added to the object - promise: function( obj ) { - return obj != null ? jQuery.extend( obj, promise ) : promise; - } - }, - deferred = {}; - - // Add list-specific methods - jQuery.each( tuples, function( i, tuple ) { - var list = tuple[ 2 ], - stateString = tuple[ 5 ]; - - // promise.progress = list.add - // promise.done = list.add - // promise.fail = list.add - promise[ tuple[ 1 ] ] = list.add; - - // Handle state - if ( stateString ) { - list.add( - function() { - - // state = "resolved" (i.e., fulfilled) - // state = "rejected" - state = stateString; - }, - - // rejected_callbacks.disable - // fulfilled_callbacks.disable - tuples[ 3 - i ][ 2 ].disable, - - // rejected_handlers.disable - // fulfilled_handlers.disable - tuples[ 3 - i ][ 3 ].disable, - - // progress_callbacks.lock - tuples[ 0 ][ 2 ].lock, - - // progress_handlers.lock - tuples[ 0 ][ 3 ].lock - ); - } - - // progress_handlers.fire - // fulfilled_handlers.fire - // rejected_handlers.fire - list.add( tuple[ 3 ].fire ); - - // deferred.notify = function() { deferred.notifyWith(...) } - // deferred.resolve = function() { deferred.resolveWith(...) } - // deferred.reject = function() { deferred.rejectWith(...) } - deferred[ tuple[ 0 ] ] = function() { - deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); - return this; - }; - - // deferred.notifyWith = list.fireWith - // deferred.resolveWith = list.fireWith - // deferred.rejectWith = list.fireWith - deferred[ tuple[ 0 ] + "With" ] = list.fireWith; - } ); - - // Make the deferred a promise - promise.promise( deferred ); - - // Call given func if any - if ( func ) { - func.call( deferred, deferred ); - } - - // All done! - return deferred; - }, - - // Deferred helper - when: function( singleValue ) { - var - - // count of uncompleted subordinates - remaining = arguments.length, - - // count of unprocessed arguments - i = remaining, - - // subordinate fulfillment data - resolveContexts = Array( i ), - resolveValues = slice.call( arguments ), - - // the primary Deferred - primary = jQuery.Deferred(), - - // subordinate callback factory - updateFunc = function( i ) { - return function( value ) { - resolveContexts[ i ] = this; - resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; - if ( !( --remaining ) ) { - primary.resolveWith( resolveContexts, resolveValues ); - } - }; - }; - - // Single- and empty arguments are adopted like Promise.resolve - if ( remaining <= 1 ) { - adoptValue( singleValue, primary.done( updateFunc( i ) ).resolve, primary.reject, - !remaining ); - - // Use .then() to unwrap secondary thenables (cf. gh-3000) - if ( primary.state() === "pending" || - isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { - - return primary.then(); - } - } - - // Multiple arguments are aggregated like Promise.all array elements - while ( i-- ) { - adoptValue( resolveValues[ i ], updateFunc( i ), primary.reject ); - } - - return primary.promise(); - } -} ); - - -// These usually indicate a programmer mistake during development, -// warn about them ASAP rather than swallowing them by default. -var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; - -jQuery.Deferred.exceptionHook = function( error, stack ) { - - // Support: IE 8 - 9 only - // Console exists when dev tools are open, which can happen at any time - if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { - window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); - } -}; - - - - -jQuery.readyException = function( error ) { - window.setTimeout( function() { - throw error; - } ); -}; - - - - -// The deferred used on DOM ready -var readyList = jQuery.Deferred(); - -jQuery.fn.ready = function( fn ) { - - readyList - .then( fn ) - - // Wrap jQuery.readyException in a function so that the lookup - // happens at the time of error handling instead of callback - // registration. - .catch( function( error ) { - jQuery.readyException( error ); - } ); - - return this; -}; - -jQuery.extend( { - - // Is the DOM ready to be used? Set to true once it occurs. - isReady: false, - - // A counter to track how many items to wait for before - // the ready event fires. See #6781 - readyWait: 1, - - // Handle when the DOM is ready - ready: function( wait ) { - - // Abort if there are pending holds or we're already ready - if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { - return; - } - - // Remember that the DOM is ready - jQuery.isReady = true; - - // If a normal DOM Ready event fired, decrement, and wait if need be - if ( wait !== true && --jQuery.readyWait > 0 ) { - return; - } - - // If there are functions bound, to execute - readyList.resolveWith( document, [ jQuery ] ); - } -} ); - -jQuery.ready.then = readyList.then; - -// The ready event handler and self cleanup method -function completed() { - document.removeEventListener( "DOMContentLoaded", completed ); - window.removeEventListener( "load", completed ); - jQuery.ready(); -} - -// Catch cases where $(document).ready() is called -// after the browser event has already occurred. -// Support: IE <=9 - 10 only -// Older IE sometimes signals "interactive" too soon -if ( document.readyState === "complete" || - ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { - - // Handle it asynchronously to allow scripts the opportunity to delay ready - window.setTimeout( jQuery.ready ); - -} else { - - // Use the handy event callback - document.addEventListener( "DOMContentLoaded", completed ); - - // A fallback to window.onload, that will always work - window.addEventListener( "load", completed ); -} - - - - -// Multifunctional method to get and set values of a collection -// The value/s can optionally be executed if it's a function -var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { - var i = 0, - len = elems.length, - bulk = key == null; - - // Sets many values - if ( toType( key ) === "object" ) { - chainable = true; - for ( i in key ) { - access( elems, fn, i, key[ i ], true, emptyGet, raw ); - } - - // Sets one value - } else if ( value !== undefined ) { - chainable = true; - - if ( !isFunction( value ) ) { - raw = true; - } - - if ( bulk ) { - - // Bulk operations run against the entire set - if ( raw ) { - fn.call( elems, value ); - fn = null; - - // ...except when executing function values - } else { - bulk = fn; - fn = function( elem, _key, value ) { - return bulk.call( jQuery( elem ), value ); - }; - } - } - - if ( fn ) { - for ( ; i < len; i++ ) { - fn( - elems[ i ], key, raw ? - value : - value.call( elems[ i ], i, fn( elems[ i ], key ) ) - ); - } - } - } - - if ( chainable ) { - return elems; - } - - // Gets - if ( bulk ) { - return fn.call( elems ); - } - - return len ? fn( elems[ 0 ], key ) : emptyGet; -}; - - -// Matches dashed string for camelizing -var rmsPrefix = /^-ms-/, - rdashAlpha = /-([a-z])/g; - -// Used by camelCase as callback to replace() -function fcamelCase( _all, letter ) { - return letter.toUpperCase(); -} - -// Convert dashed to camelCase; used by the css and data modules -// Support: IE <=9 - 11, Edge 12 - 15 -// Microsoft forgot to hump their vendor prefix (#9572) -function camelCase( string ) { - return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); -} -var acceptData = function( owner ) { - - // Accepts only: - // - Node - // - Node.ELEMENT_NODE - // - Node.DOCUMENT_NODE - // - Object - // - Any - return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); -}; - - - - -function Data() { - this.expando = jQuery.expando + Data.uid++; -} - -Data.uid = 1; - -Data.prototype = { - - cache: function( owner ) { - - // Check if the owner object already has a cache - var value = owner[ this.expando ]; - - // If not, create one - if ( !value ) { - value = {}; - - // We can accept data for non-element nodes in modern browsers, - // but we should not, see #8335. - // Always return an empty object. - if ( acceptData( owner ) ) { - - // If it is a node unlikely to be stringify-ed or looped over - // use plain assignment - if ( owner.nodeType ) { - owner[ this.expando ] = value; - - // Otherwise secure it in a non-enumerable property - // configurable must be true to allow the property to be - // deleted when data is removed - } else { - Object.defineProperty( owner, this.expando, { - value: value, - configurable: true - } ); - } - } - } - - return value; - }, - set: function( owner, data, value ) { - var prop, - cache = this.cache( owner ); - - // Handle: [ owner, key, value ] args - // Always use camelCase key (gh-2257) - if ( typeof data === "string" ) { - cache[ camelCase( data ) ] = value; - - // Handle: [ owner, { properties } ] args - } else { - - // Copy the properties one-by-one to the cache object - for ( prop in data ) { - cache[ camelCase( prop ) ] = data[ prop ]; - } - } - return cache; - }, - get: function( owner, key ) { - return key === undefined ? - this.cache( owner ) : - - // Always use camelCase key (gh-2257) - owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; - }, - access: function( owner, key, value ) { - - // In cases where either: - // - // 1. No key was specified - // 2. A string key was specified, but no value provided - // - // Take the "read" path and allow the get method to determine - // which value to return, respectively either: - // - // 1. The entire cache object - // 2. The data stored at the key - // - if ( key === undefined || - ( ( key && typeof key === "string" ) && value === undefined ) ) { - - return this.get( owner, key ); - } - - // When the key is not a string, or both a key and value - // are specified, set or extend (existing objects) with either: - // - // 1. An object of properties - // 2. A key and value - // - this.set( owner, key, value ); - - // Since the "set" path can have two possible entry points - // return the expected data based on which path was taken[*] - return value !== undefined ? value : key; - }, - remove: function( owner, key ) { - var i, - cache = owner[ this.expando ]; - - if ( cache === undefined ) { - return; - } - - if ( key !== undefined ) { - - // Support array or space separated string of keys - if ( Array.isArray( key ) ) { - - // If key is an array of keys... - // We always set camelCase keys, so remove that. - key = key.map( camelCase ); - } else { - key = camelCase( key ); - - // If a key with the spaces exists, use it. - // Otherwise, create an array by matching non-whitespace - key = key in cache ? - [ key ] : - ( key.match( rnothtmlwhite ) || [] ); - } - - i = key.length; - - while ( i-- ) { - delete cache[ key[ i ] ]; - } - } - - // Remove the expando if there's no more data - if ( key === undefined || jQuery.isEmptyObject( cache ) ) { - - // Support: Chrome <=35 - 45 - // Webkit & Blink performance suffers when deleting properties - // from DOM nodes, so set to undefined instead - // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) - if ( owner.nodeType ) { - owner[ this.expando ] = undefined; - } else { - delete owner[ this.expando ]; - } - } - }, - hasData: function( owner ) { - var cache = owner[ this.expando ]; - return cache !== undefined && !jQuery.isEmptyObject( cache ); - } -}; -var dataPriv = new Data(); - -var dataUser = new Data(); - - - -// Implementation Summary -// -// 1. Enforce API surface and semantic compatibility with 1.9.x branch -// 2. Improve the module's maintainability by reducing the storage -// paths to a single mechanism. -// 3. Use the same single mechanism to support "private" and "user" data. -// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) -// 5. Avoid exposing implementation details on user objects (eg. expando properties) -// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 - -var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, - rmultiDash = /[A-Z]/g; - -function getData( data ) { - if ( data === "true" ) { - return true; - } - - if ( data === "false" ) { - return false; - } - - if ( data === "null" ) { - return null; - } - - // Only convert to a number if it doesn't change the string - if ( data === +data + "" ) { - return +data; - } - - if ( rbrace.test( data ) ) { - return JSON.parse( data ); - } - - return data; -} - -function dataAttr( elem, key, data ) { - var name; - - // If nothing was found internally, try to fetch any - // data from the HTML5 data-* attribute - if ( data === undefined && elem.nodeType === 1 ) { - name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); - data = elem.getAttribute( name ); - - if ( typeof data === "string" ) { - try { - data = getData( data ); - } catch ( e ) {} - - // Make sure we set the data so it isn't changed later - dataUser.set( elem, key, data ); - } else { - data = undefined; - } - } - return data; -} - -jQuery.extend( { - hasData: function( elem ) { - return dataUser.hasData( elem ) || dataPriv.hasData( elem ); - }, - - data: function( elem, name, data ) { - return dataUser.access( elem, name, data ); - }, - - removeData: function( elem, name ) { - dataUser.remove( elem, name ); - }, - - // TODO: Now that all calls to _data and _removeData have been replaced - // with direct calls to dataPriv methods, these can be deprecated. - _data: function( elem, name, data ) { - return dataPriv.access( elem, name, data ); - }, - - _removeData: function( elem, name ) { - dataPriv.remove( elem, name ); - } -} ); - -jQuery.fn.extend( { - data: function( key, value ) { - var i, name, data, - elem = this[ 0 ], - attrs = elem && elem.attributes; - - // Gets all values - if ( key === undefined ) { - if ( this.length ) { - data = dataUser.get( elem ); - - if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { - i = attrs.length; - while ( i-- ) { - - // Support: IE 11 only - // The attrs elements can be null (#14894) - if ( attrs[ i ] ) { - name = attrs[ i ].name; - if ( name.indexOf( "data-" ) === 0 ) { - name = camelCase( name.slice( 5 ) ); - dataAttr( elem, name, data[ name ] ); - } - } - } - dataPriv.set( elem, "hasDataAttrs", true ); - } - } - - return data; - } - - // Sets multiple values - if ( typeof key === "object" ) { - return this.each( function() { - dataUser.set( this, key ); - } ); - } - - return access( this, function( value ) { - var data; - - // The calling jQuery object (element matches) is not empty - // (and therefore has an element appears at this[ 0 ]) and the - // `value` parameter was not undefined. An empty jQuery object - // will result in `undefined` for elem = this[ 0 ] which will - // throw an exception if an attempt to read a data cache is made. - if ( elem && value === undefined ) { - - // Attempt to get data from the cache - // The key will always be camelCased in Data - data = dataUser.get( elem, key ); - if ( data !== undefined ) { - return data; - } - - // Attempt to "discover" the data in - // HTML5 custom data-* attrs - data = dataAttr( elem, key ); - if ( data !== undefined ) { - return data; - } - - // We tried really hard, but the data doesn't exist. - return; - } - - // Set the data... - this.each( function() { - - // We always store the camelCased key - dataUser.set( this, key, value ); - } ); - }, null, value, arguments.length > 1, null, true ); - }, - - removeData: function( key ) { - return this.each( function() { - dataUser.remove( this, key ); - } ); - } -} ); - - -jQuery.extend( { - queue: function( elem, type, data ) { - var queue; - - if ( elem ) { - type = ( type || "fx" ) + "queue"; - queue = dataPriv.get( elem, type ); - - // Speed up dequeue by getting out quickly if this is just a lookup - if ( data ) { - if ( !queue || Array.isArray( data ) ) { - queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); - } else { - queue.push( data ); - } - } - return queue || []; - } - }, - - dequeue: function( elem, type ) { - type = type || "fx"; - - var queue = jQuery.queue( elem, type ), - startLength = queue.length, - fn = queue.shift(), - hooks = jQuery._queueHooks( elem, type ), - next = function() { - jQuery.dequeue( elem, type ); - }; - - // If the fx queue is dequeued, always remove the progress sentinel - if ( fn === "inprogress" ) { - fn = queue.shift(); - startLength--; - } - - if ( fn ) { - - // Add a progress sentinel to prevent the fx queue from being - // automatically dequeued - if ( type === "fx" ) { - queue.unshift( "inprogress" ); - } - - // Clear up the last queue stop function - delete hooks.stop; - fn.call( elem, next, hooks ); - } - - if ( !startLength && hooks ) { - hooks.empty.fire(); - } - }, - - // Not public - generate a queueHooks object, or return the current one - _queueHooks: function( elem, type ) { - var key = type + "queueHooks"; - return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { - empty: jQuery.Callbacks( "once memory" ).add( function() { - dataPriv.remove( elem, [ type + "queue", key ] ); - } ) - } ); - } -} ); - -jQuery.fn.extend( { - queue: function( type, data ) { - var setter = 2; - - if ( typeof type !== "string" ) { - data = type; - type = "fx"; - setter--; - } - - if ( arguments.length < setter ) { - return jQuery.queue( this[ 0 ], type ); - } - - return data === undefined ? - this : - this.each( function() { - var queue = jQuery.queue( this, type, data ); - - // Ensure a hooks for this queue - jQuery._queueHooks( this, type ); - - if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { - jQuery.dequeue( this, type ); - } - } ); - }, - dequeue: function( type ) { - return this.each( function() { - jQuery.dequeue( this, type ); - } ); - }, - clearQueue: function( type ) { - return this.queue( type || "fx", [] ); - }, - - // Get a promise resolved when queues of a certain type - // are emptied (fx is the type by default) - promise: function( type, obj ) { - var tmp, - count = 1, - defer = jQuery.Deferred(), - elements = this, - i = this.length, - resolve = function() { - if ( !( --count ) ) { - defer.resolveWith( elements, [ elements ] ); - } - }; - - if ( typeof type !== "string" ) { - obj = type; - type = undefined; - } - type = type || "fx"; - - while ( i-- ) { - tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); - if ( tmp && tmp.empty ) { - count++; - tmp.empty.add( resolve ); - } - } - resolve(); - return defer.promise( obj ); - } -} ); -var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; - -var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); - - -var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; - -var documentElement = document.documentElement; - - - - var isAttached = function( elem ) { - return jQuery.contains( elem.ownerDocument, elem ); - }, - composed = { composed: true }; - - // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only - // Check attachment across shadow DOM boundaries when possible (gh-3504) - // Support: iOS 10.0-10.2 only - // Early iOS 10 versions support `attachShadow` but not `getRootNode`, - // leading to errors. We need to check for `getRootNode`. - if ( documentElement.getRootNode ) { - isAttached = function( elem ) { - return jQuery.contains( elem.ownerDocument, elem ) || - elem.getRootNode( composed ) === elem.ownerDocument; - }; - } -var isHiddenWithinTree = function( elem, el ) { - - // isHiddenWithinTree might be called from jQuery#filter function; - // in that case, element will be second argument - elem = el || elem; - - // Inline style trumps all - return elem.style.display === "none" || - elem.style.display === "" && - - // Otherwise, check computed style - // Support: Firefox <=43 - 45 - // Disconnected elements can have computed display: none, so first confirm that elem is - // in the document. - isAttached( elem ) && - - jQuery.css( elem, "display" ) === "none"; - }; - - - -function adjustCSS( elem, prop, valueParts, tween ) { - var adjusted, scale, - maxIterations = 20, - currentValue = tween ? - function() { - return tween.cur(); - } : - function() { - return jQuery.css( elem, prop, "" ); - }, - initial = currentValue(), - unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), - - // Starting value computation is required for potential unit mismatches - initialInUnit = elem.nodeType && - ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && - rcssNum.exec( jQuery.css( elem, prop ) ); - - if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { - - // Support: Firefox <=54 - // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) - initial = initial / 2; - - // Trust units reported by jQuery.css - unit = unit || initialInUnit[ 3 ]; - - // Iteratively approximate from a nonzero starting point - initialInUnit = +initial || 1; - - while ( maxIterations-- ) { - - // Evaluate and update our best guess (doubling guesses that zero out). - // Finish if the scale equals or crosses 1 (making the old*new product non-positive). - jQuery.style( elem, prop, initialInUnit + unit ); - if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { - maxIterations = 0; - } - initialInUnit = initialInUnit / scale; - - } - - initialInUnit = initialInUnit * 2; - jQuery.style( elem, prop, initialInUnit + unit ); - - // Make sure we update the tween properties later on - valueParts = valueParts || []; - } - - if ( valueParts ) { - initialInUnit = +initialInUnit || +initial || 0; - - // Apply relative offset (+=/-=) if specified - adjusted = valueParts[ 1 ] ? - initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : - +valueParts[ 2 ]; - if ( tween ) { - tween.unit = unit; - tween.start = initialInUnit; - tween.end = adjusted; - } - } - return adjusted; -} - - -var defaultDisplayMap = {}; - -function getDefaultDisplay( elem ) { - var temp, - doc = elem.ownerDocument, - nodeName = elem.nodeName, - display = defaultDisplayMap[ nodeName ]; - - if ( display ) { - return display; - } - - temp = doc.body.appendChild( doc.createElement( nodeName ) ); - display = jQuery.css( temp, "display" ); - - temp.parentNode.removeChild( temp ); - - if ( display === "none" ) { - display = "block"; - } - defaultDisplayMap[ nodeName ] = display; - - return display; -} - -function showHide( elements, show ) { - var display, elem, - values = [], - index = 0, - length = elements.length; - - // Determine new display value for elements that need to change - for ( ; index < length; index++ ) { - elem = elements[ index ]; - if ( !elem.style ) { - continue; - } - - display = elem.style.display; - if ( show ) { - - // Since we force visibility upon cascade-hidden elements, an immediate (and slow) - // check is required in this first loop unless we have a nonempty display value (either - // inline or about-to-be-restored) - if ( display === "none" ) { - values[ index ] = dataPriv.get( elem, "display" ) || null; - if ( !values[ index ] ) { - elem.style.display = ""; - } - } - if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { - values[ index ] = getDefaultDisplay( elem ); - } - } else { - if ( display !== "none" ) { - values[ index ] = "none"; - - // Remember what we're overwriting - dataPriv.set( elem, "display", display ); - } - } - } - - // Set the display of the elements in a second loop to avoid constant reflow - for ( index = 0; index < length; index++ ) { - if ( values[ index ] != null ) { - elements[ index ].style.display = values[ index ]; - } - } - - return elements; -} - -jQuery.fn.extend( { - show: function() { - return showHide( this, true ); - }, - hide: function() { - return showHide( this ); - }, - toggle: function( state ) { - if ( typeof state === "boolean" ) { - return state ? this.show() : this.hide(); - } - - return this.each( function() { - if ( isHiddenWithinTree( this ) ) { - jQuery( this ).show(); - } else { - jQuery( this ).hide(); - } - } ); - } -} ); -var rcheckableType = ( /^(?:checkbox|radio)$/i ); - -var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); - -var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); - - - -( function() { - var fragment = document.createDocumentFragment(), - div = fragment.appendChild( document.createElement( "div" ) ), - input = document.createElement( "input" ); - - // Support: Android 4.0 - 4.3 only - // Check state lost if the name is set (#11217) - // Support: Windows Web Apps (WWA) - // `name` and `type` must use .setAttribute for WWA (#14901) - input.setAttribute( "type", "radio" ); - input.setAttribute( "checked", "checked" ); - input.setAttribute( "name", "t" ); - - div.appendChild( input ); - - // Support: Android <=4.1 only - // Older WebKit doesn't clone checked state correctly in fragments - support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; - - // Support: IE <=11 only - // Make sure textarea (and checkbox) defaultValue is properly cloned - div.innerHTML = ""; - support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; - - // Support: IE <=9 only - // IE <=9 replaces "; - support.option = !!div.lastChild; -} )(); - - -// We have to close these tags to support XHTML (#13200) -var wrapMap = { - - // XHTML parsers do not magically insert elements in the - // same way that tag soup parsers do. So we cannot shorten - // this by omitting or other required elements. - thead: [ 1, "", "
" ], - col: [ 2, "", "
" ], - tr: [ 2, "", "
" ], - td: [ 3, "", "
" ], - - _default: [ 0, "", "" ] -}; - -wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; -wrapMap.th = wrapMap.td; - -// Support: IE <=9 only -if ( !support.option ) { - wrapMap.optgroup = wrapMap.option = [ 1, "" ]; -} - - -function getAll( context, tag ) { - - // Support: IE <=9 - 11 only - // Use typeof to avoid zero-argument method invocation on host objects (#15151) - var ret; - - if ( typeof context.getElementsByTagName !== "undefined" ) { - ret = context.getElementsByTagName( tag || "*" ); - - } else if ( typeof context.querySelectorAll !== "undefined" ) { - ret = context.querySelectorAll( tag || "*" ); - - } else { - ret = []; - } - - if ( tag === undefined || tag && nodeName( context, tag ) ) { - return jQuery.merge( [ context ], ret ); - } - - return ret; -} - - -// Mark scripts as having already been evaluated -function setGlobalEval( elems, refElements ) { - var i = 0, - l = elems.length; - - for ( ; i < l; i++ ) { - dataPriv.set( - elems[ i ], - "globalEval", - !refElements || dataPriv.get( refElements[ i ], "globalEval" ) - ); - } -} - - -var rhtml = /<|&#?\w+;/; - -function buildFragment( elems, context, scripts, selection, ignored ) { - var elem, tmp, tag, wrap, attached, j, - fragment = context.createDocumentFragment(), - nodes = [], - i = 0, - l = elems.length; - - for ( ; i < l; i++ ) { - elem = elems[ i ]; - - if ( elem || elem === 0 ) { - - // Add nodes directly - if ( toType( elem ) === "object" ) { - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); - - // Convert non-html into a text node - } else if ( !rhtml.test( elem ) ) { - nodes.push( context.createTextNode( elem ) ); - - // Convert html into DOM nodes - } else { - tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); - - // Deserialize a standard representation - tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); - wrap = wrapMap[ tag ] || wrapMap._default; - tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; - - // Descend through wrappers to the right content - j = wrap[ 0 ]; - while ( j-- ) { - tmp = tmp.lastChild; - } - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( nodes, tmp.childNodes ); - - // Remember the top-level container - tmp = fragment.firstChild; - - // Ensure the created nodes are orphaned (#12392) - tmp.textContent = ""; - } - } - } - - // Remove wrapper from fragment - fragment.textContent = ""; - - i = 0; - while ( ( elem = nodes[ i++ ] ) ) { - - // Skip elements already in the context collection (trac-4087) - if ( selection && jQuery.inArray( elem, selection ) > -1 ) { - if ( ignored ) { - ignored.push( elem ); - } - continue; - } - - attached = isAttached( elem ); - - // Append to fragment - tmp = getAll( fragment.appendChild( elem ), "script" ); - - // Preserve script evaluation history - if ( attached ) { - setGlobalEval( tmp ); - } - - // Capture executables - if ( scripts ) { - j = 0; - while ( ( elem = tmp[ j++ ] ) ) { - if ( rscriptType.test( elem.type || "" ) ) { - scripts.push( elem ); - } - } - } - } - - return fragment; -} - - -var rtypenamespace = /^([^.]*)(?:\.(.+)|)/; - -function returnTrue() { - return true; -} - -function returnFalse() { - return false; -} - -// Support: IE <=9 - 11+ -// focus() and blur() are asynchronous, except when they are no-op. -// So expect focus to be synchronous when the element is already active, -// and blur to be synchronous when the element is not already active. -// (focus and blur are always synchronous in other supported browsers, -// this just defines when we can count on it). -function expectSync( elem, type ) { - return ( elem === safeActiveElement() ) === ( type === "focus" ); -} - -// Support: IE <=9 only -// Accessing document.activeElement can throw unexpectedly -// https://bugs.jquery.com/ticket/13393 -function safeActiveElement() { - try { - return document.activeElement; - } catch ( err ) { } -} - -function on( elem, types, selector, data, fn, one ) { - var origFn, type; - - // Types can be a map of types/handlers - if ( typeof types === "object" ) { - - // ( types-Object, selector, data ) - if ( typeof selector !== "string" ) { - - // ( types-Object, data ) - data = data || selector; - selector = undefined; - } - for ( type in types ) { - on( elem, type, selector, data, types[ type ], one ); - } - return elem; - } - - if ( data == null && fn == null ) { - - // ( types, fn ) - fn = selector; - data = selector = undefined; - } else if ( fn == null ) { - if ( typeof selector === "string" ) { - - // ( types, selector, fn ) - fn = data; - data = undefined; - } else { - - // ( types, data, fn ) - fn = data; - data = selector; - selector = undefined; - } - } - if ( fn === false ) { - fn = returnFalse; - } else if ( !fn ) { - return elem; - } - - if ( one === 1 ) { - origFn = fn; - fn = function( event ) { - - // Can use an empty set, since event contains the info - jQuery().off( event ); - return origFn.apply( this, arguments ); - }; - - // Use same guid so caller can remove using origFn - fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); - } - return elem.each( function() { - jQuery.event.add( this, types, fn, data, selector ); - } ); -} - -/* - * Helper functions for managing events -- not part of the public interface. - * Props to Dean Edwards' addEvent library for many of the ideas. - */ -jQuery.event = { - - global: {}, - - add: function( elem, types, handler, data, selector ) { - - var handleObjIn, eventHandle, tmp, - events, t, handleObj, - special, handlers, type, namespaces, origType, - elemData = dataPriv.get( elem ); - - // Only attach events to objects that accept data - if ( !acceptData( elem ) ) { - return; - } - - // Caller can pass in an object of custom data in lieu of the handler - if ( handler.handler ) { - handleObjIn = handler; - handler = handleObjIn.handler; - selector = handleObjIn.selector; - } - - // Ensure that invalid selectors throw exceptions at attach time - // Evaluate against documentElement in case elem is a non-element node (e.g., document) - if ( selector ) { - jQuery.find.matchesSelector( documentElement, selector ); - } - - // Make sure that the handler has a unique ID, used to find/remove it later - if ( !handler.guid ) { - handler.guid = jQuery.guid++; - } - - // Init the element's event structure and main handler, if this is the first - if ( !( events = elemData.events ) ) { - events = elemData.events = Object.create( null ); - } - if ( !( eventHandle = elemData.handle ) ) { - eventHandle = elemData.handle = function( e ) { - - // Discard the second event of a jQuery.event.trigger() and - // when an event is called after a page has unloaded - return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? - jQuery.event.dispatch.apply( elem, arguments ) : undefined; - }; - } - - // Handle multiple events separated by a space - types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[ t ] ) || []; - type = origType = tmp[ 1 ]; - namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); - - // There *must* be a type, no attaching namespace-only handlers - if ( !type ) { - continue; - } - - // If event changes its type, use the special event handlers for the changed type - special = jQuery.event.special[ type ] || {}; - - // If selector defined, determine special event api type, otherwise given type - type = ( selector ? special.delegateType : special.bindType ) || type; - - // Update special based on newly reset type - special = jQuery.event.special[ type ] || {}; - - // handleObj is passed to all event handlers - handleObj = jQuery.extend( { - type: type, - origType: origType, - data: data, - handler: handler, - guid: handler.guid, - selector: selector, - needsContext: selector && jQuery.expr.match.needsContext.test( selector ), - namespace: namespaces.join( "." ) - }, handleObjIn ); - - // Init the event handler queue if we're the first - if ( !( handlers = events[ type ] ) ) { - handlers = events[ type ] = []; - handlers.delegateCount = 0; - - // Only use addEventListener if the special events handler returns false - if ( !special.setup || - special.setup.call( elem, data, namespaces, eventHandle ) === false ) { - - if ( elem.addEventListener ) { - elem.addEventListener( type, eventHandle ); - } - } - } - - if ( special.add ) { - special.add.call( elem, handleObj ); - - if ( !handleObj.handler.guid ) { - handleObj.handler.guid = handler.guid; - } - } - - // Add to the element's handler list, delegates in front - if ( selector ) { - handlers.splice( handlers.delegateCount++, 0, handleObj ); - } else { - handlers.push( handleObj ); - } - - // Keep track of which events have ever been used, for event optimization - jQuery.event.global[ type ] = true; - } - - }, - - // Detach an event or set of events from an element - remove: function( elem, types, handler, selector, mappedTypes ) { - - var j, origCount, tmp, - events, t, handleObj, - special, handlers, type, namespaces, origType, - elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); - - if ( !elemData || !( events = elemData.events ) ) { - return; - } - - // Once for each type.namespace in types; type may be omitted - types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[ t ] ) || []; - type = origType = tmp[ 1 ]; - namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); - - // Unbind all events (on this namespace, if provided) for the element - if ( !type ) { - for ( type in events ) { - jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); - } - continue; - } - - special = jQuery.event.special[ type ] || {}; - type = ( selector ? special.delegateType : special.bindType ) || type; - handlers = events[ type ] || []; - tmp = tmp[ 2 ] && - new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); - - // Remove matching events - origCount = j = handlers.length; - while ( j-- ) { - handleObj = handlers[ j ]; - - if ( ( mappedTypes || origType === handleObj.origType ) && - ( !handler || handler.guid === handleObj.guid ) && - ( !tmp || tmp.test( handleObj.namespace ) ) && - ( !selector || selector === handleObj.selector || - selector === "**" && handleObj.selector ) ) { - handlers.splice( j, 1 ); - - if ( handleObj.selector ) { - handlers.delegateCount--; - } - if ( special.remove ) { - special.remove.call( elem, handleObj ); - } - } - } - - // Remove generic event handler if we removed something and no more handlers exist - // (avoids potential for endless recursion during removal of special event handlers) - if ( origCount && !handlers.length ) { - if ( !special.teardown || - special.teardown.call( elem, namespaces, elemData.handle ) === false ) { - - jQuery.removeEvent( elem, type, elemData.handle ); - } - - delete events[ type ]; - } - } - - // Remove data and the expando if it's no longer used - if ( jQuery.isEmptyObject( events ) ) { - dataPriv.remove( elem, "handle events" ); - } - }, - - dispatch: function( nativeEvent ) { - - var i, j, ret, matched, handleObj, handlerQueue, - args = new Array( arguments.length ), - - // Make a writable jQuery.Event from the native event object - event = jQuery.event.fix( nativeEvent ), - - handlers = ( - dataPriv.get( this, "events" ) || Object.create( null ) - )[ event.type ] || [], - special = jQuery.event.special[ event.type ] || {}; - - // Use the fix-ed jQuery.Event rather than the (read-only) native event - args[ 0 ] = event; - - for ( i = 1; i < arguments.length; i++ ) { - args[ i ] = arguments[ i ]; - } - - event.delegateTarget = this; - - // Call the preDispatch hook for the mapped type, and let it bail if desired - if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { - return; - } - - // Determine handlers - handlerQueue = jQuery.event.handlers.call( this, event, handlers ); - - // Run delegates first; they may want to stop propagation beneath us - i = 0; - while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { - event.currentTarget = matched.elem; - - j = 0; - while ( ( handleObj = matched.handlers[ j++ ] ) && - !event.isImmediatePropagationStopped() ) { - - // If the event is namespaced, then each handler is only invoked if it is - // specially universal or its namespaces are a superset of the event's. - if ( !event.rnamespace || handleObj.namespace === false || - event.rnamespace.test( handleObj.namespace ) ) { - - event.handleObj = handleObj; - event.data = handleObj.data; - - ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || - handleObj.handler ).apply( matched.elem, args ); - - if ( ret !== undefined ) { - if ( ( event.result = ret ) === false ) { - event.preventDefault(); - event.stopPropagation(); - } - } - } - } - } - - // Call the postDispatch hook for the mapped type - if ( special.postDispatch ) { - special.postDispatch.call( this, event ); - } - - return event.result; - }, - - handlers: function( event, handlers ) { - var i, handleObj, sel, matchedHandlers, matchedSelectors, - handlerQueue = [], - delegateCount = handlers.delegateCount, - cur = event.target; - - // Find delegate handlers - if ( delegateCount && - - // Support: IE <=9 - // Black-hole SVG instance trees (trac-13180) - cur.nodeType && - - // Support: Firefox <=42 - // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) - // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click - // Support: IE 11 only - // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) - !( event.type === "click" && event.button >= 1 ) ) { - - for ( ; cur !== this; cur = cur.parentNode || this ) { - - // Don't check non-elements (#13208) - // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) - if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { - matchedHandlers = []; - matchedSelectors = {}; - for ( i = 0; i < delegateCount; i++ ) { - handleObj = handlers[ i ]; - - // Don't conflict with Object.prototype properties (#13203) - sel = handleObj.selector + " "; - - if ( matchedSelectors[ sel ] === undefined ) { - matchedSelectors[ sel ] = handleObj.needsContext ? - jQuery( sel, this ).index( cur ) > -1 : - jQuery.find( sel, this, null, [ cur ] ).length; - } - if ( matchedSelectors[ sel ] ) { - matchedHandlers.push( handleObj ); - } - } - if ( matchedHandlers.length ) { - handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); - } - } - } - } - - // Add the remaining (directly-bound) handlers - cur = this; - if ( delegateCount < handlers.length ) { - handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); - } - - return handlerQueue; - }, - - addProp: function( name, hook ) { - Object.defineProperty( jQuery.Event.prototype, name, { - enumerable: true, - configurable: true, - - get: isFunction( hook ) ? - function() { - if ( this.originalEvent ) { - return hook( this.originalEvent ); - } - } : - function() { - if ( this.originalEvent ) { - return this.originalEvent[ name ]; - } - }, - - set: function( value ) { - Object.defineProperty( this, name, { - enumerable: true, - configurable: true, - writable: true, - value: value - } ); - } - } ); - }, - - fix: function( originalEvent ) { - return originalEvent[ jQuery.expando ] ? - originalEvent : - new jQuery.Event( originalEvent ); - }, - - special: { - load: { - - // Prevent triggered image.load events from bubbling to window.load - noBubble: true - }, - click: { - - // Utilize native event to ensure correct state for checkable inputs - setup: function( data ) { - - // For mutual compressibility with _default, replace `this` access with a local var. - // `|| data` is dead code meant only to preserve the variable through minification. - var el = this || data; - - // Claim the first handler - if ( rcheckableType.test( el.type ) && - el.click && nodeName( el, "input" ) ) { - - // dataPriv.set( el, "click", ... ) - leverageNative( el, "click", returnTrue ); - } - - // Return false to allow normal processing in the caller - return false; - }, - trigger: function( data ) { - - // For mutual compressibility with _default, replace `this` access with a local var. - // `|| data` is dead code meant only to preserve the variable through minification. - var el = this || data; - - // Force setup before triggering a click - if ( rcheckableType.test( el.type ) && - el.click && nodeName( el, "input" ) ) { - - leverageNative( el, "click" ); - } - - // Return non-false to allow normal event-path propagation - return true; - }, - - // For cross-browser consistency, suppress native .click() on links - // Also prevent it if we're currently inside a leveraged native-event stack - _default: function( event ) { - var target = event.target; - return rcheckableType.test( target.type ) && - target.click && nodeName( target, "input" ) && - dataPriv.get( target, "click" ) || - nodeName( target, "a" ); - } - }, - - beforeunload: { - postDispatch: function( event ) { - - // Support: Firefox 20+ - // Firefox doesn't alert if the returnValue field is not set. - if ( event.result !== undefined && event.originalEvent ) { - event.originalEvent.returnValue = event.result; - } - } - } - } -}; - -// Ensure the presence of an event listener that handles manually-triggered -// synthetic events by interrupting progress until reinvoked in response to -// *native* events that it fires directly, ensuring that state changes have -// already occurred before other listeners are invoked. -function leverageNative( el, type, expectSync ) { - - // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add - if ( !expectSync ) { - if ( dataPriv.get( el, type ) === undefined ) { - jQuery.event.add( el, type, returnTrue ); - } - return; - } - - // Register the controller as a special universal handler for all event namespaces - dataPriv.set( el, type, false ); - jQuery.event.add( el, type, { - namespace: false, - handler: function( event ) { - var notAsync, result, - saved = dataPriv.get( this, type ); - - if ( ( event.isTrigger & 1 ) && this[ type ] ) { - - // Interrupt processing of the outer synthetic .trigger()ed event - // Saved data should be false in such cases, but might be a leftover capture object - // from an async native handler (gh-4350) - if ( !saved.length ) { - - // Store arguments for use when handling the inner native event - // There will always be at least one argument (an event object), so this array - // will not be confused with a leftover capture object. - saved = slice.call( arguments ); - dataPriv.set( this, type, saved ); - - // Trigger the native event and capture its result - // Support: IE <=9 - 11+ - // focus() and blur() are asynchronous - notAsync = expectSync( this, type ); - this[ type ](); - result = dataPriv.get( this, type ); - if ( saved !== result || notAsync ) { - dataPriv.set( this, type, false ); - } else { - result = {}; - } - if ( saved !== result ) { - - // Cancel the outer synthetic event - event.stopImmediatePropagation(); - event.preventDefault(); - - // Support: Chrome 86+ - // In Chrome, if an element having a focusout handler is blurred by - // clicking outside of it, it invokes the handler synchronously. If - // that handler calls `.remove()` on the element, the data is cleared, - // leaving `result` undefined. We need to guard against this. - return result && result.value; - } - - // If this is an inner synthetic event for an event with a bubbling surrogate - // (focus or blur), assume that the surrogate already propagated from triggering the - // native event and prevent that from happening again here. - // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the - // bubbling surrogate propagates *after* the non-bubbling base), but that seems - // less bad than duplication. - } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { - event.stopPropagation(); - } - - // If this is a native event triggered above, everything is now in order - // Fire an inner synthetic event with the original arguments - } else if ( saved.length ) { - - // ...and capture the result - dataPriv.set( this, type, { - value: jQuery.event.trigger( - - // Support: IE <=9 - 11+ - // Extend with the prototype to reset the above stopImmediatePropagation() - jQuery.extend( saved[ 0 ], jQuery.Event.prototype ), - saved.slice( 1 ), - this - ) - } ); - - // Abort handling of the native event - event.stopImmediatePropagation(); - } - } - } ); -} - -jQuery.removeEvent = function( elem, type, handle ) { - - // This "if" is needed for plain objects - if ( elem.removeEventListener ) { - elem.removeEventListener( type, handle ); - } -}; - -jQuery.Event = function( src, props ) { - - // Allow instantiation without the 'new' keyword - if ( !( this instanceof jQuery.Event ) ) { - return new jQuery.Event( src, props ); - } - - // Event object - if ( src && src.type ) { - this.originalEvent = src; - this.type = src.type; - - // Events bubbling up the document may have been marked as prevented - // by a handler lower down the tree; reflect the correct value. - this.isDefaultPrevented = src.defaultPrevented || - src.defaultPrevented === undefined && - - // Support: Android <=2.3 only - src.returnValue === false ? - returnTrue : - returnFalse; - - // Create target properties - // Support: Safari <=6 - 7 only - // Target should not be a text node (#504, #13143) - this.target = ( src.target && src.target.nodeType === 3 ) ? - src.target.parentNode : - src.target; - - this.currentTarget = src.currentTarget; - this.relatedTarget = src.relatedTarget; - - // Event type - } else { - this.type = src; - } - - // Put explicitly provided properties onto the event object - if ( props ) { - jQuery.extend( this, props ); - } - - // Create a timestamp if incoming event doesn't have one - this.timeStamp = src && src.timeStamp || Date.now(); - - // Mark it as fixed - this[ jQuery.expando ] = true; -}; - -// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding -// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html -jQuery.Event.prototype = { - constructor: jQuery.Event, - isDefaultPrevented: returnFalse, - isPropagationStopped: returnFalse, - isImmediatePropagationStopped: returnFalse, - isSimulated: false, - - preventDefault: function() { - var e = this.originalEvent; - - this.isDefaultPrevented = returnTrue; - - if ( e && !this.isSimulated ) { - e.preventDefault(); - } - }, - stopPropagation: function() { - var e = this.originalEvent; - - this.isPropagationStopped = returnTrue; - - if ( e && !this.isSimulated ) { - e.stopPropagation(); - } - }, - stopImmediatePropagation: function() { - var e = this.originalEvent; - - this.isImmediatePropagationStopped = returnTrue; - - if ( e && !this.isSimulated ) { - e.stopImmediatePropagation(); - } - - this.stopPropagation(); - } -}; - -// Includes all common event props including KeyEvent and MouseEvent specific props -jQuery.each( { - altKey: true, - bubbles: true, - cancelable: true, - changedTouches: true, - ctrlKey: true, - detail: true, - eventPhase: true, - metaKey: true, - pageX: true, - pageY: true, - shiftKey: true, - view: true, - "char": true, - code: true, - charCode: true, - key: true, - keyCode: true, - button: true, - buttons: true, - clientX: true, - clientY: true, - offsetX: true, - offsetY: true, - pointerId: true, - pointerType: true, - screenX: true, - screenY: true, - targetTouches: true, - toElement: true, - touches: true, - which: true -}, jQuery.event.addProp ); - -jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { - jQuery.event.special[ type ] = { - - // Utilize native event if possible so blur/focus sequence is correct - setup: function() { - - // Claim the first handler - // dataPriv.set( this, "focus", ... ) - // dataPriv.set( this, "blur", ... ) - leverageNative( this, type, expectSync ); - - // Return false to allow normal processing in the caller - return false; - }, - trigger: function() { - - // Force setup before trigger - leverageNative( this, type ); - - // Return non-false to allow normal event-path propagation - return true; - }, - - // Suppress native focus or blur as it's already being fired - // in leverageNative. - _default: function() { - return true; - }, - - delegateType: delegateType - }; -} ); - -// Create mouseenter/leave events using mouseover/out and event-time checks -// so that event delegation works in jQuery. -// Do the same for pointerenter/pointerleave and pointerover/pointerout -// -// Support: Safari 7 only -// Safari sends mouseenter too often; see: -// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 -// for the description of the bug (it existed in older Chrome versions as well). -jQuery.each( { - mouseenter: "mouseover", - mouseleave: "mouseout", - pointerenter: "pointerover", - pointerleave: "pointerout" -}, function( orig, fix ) { - jQuery.event.special[ orig ] = { - delegateType: fix, - bindType: fix, - - handle: function( event ) { - var ret, - target = this, - related = event.relatedTarget, - handleObj = event.handleObj; - - // For mouseenter/leave call the handler if related is outside the target. - // NB: No relatedTarget if the mouse left/entered the browser window - if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { - event.type = handleObj.origType; - ret = handleObj.handler.apply( this, arguments ); - event.type = fix; - } - return ret; - } - }; -} ); - -jQuery.fn.extend( { - - on: function( types, selector, data, fn ) { - return on( this, types, selector, data, fn ); - }, - one: function( types, selector, data, fn ) { - return on( this, types, selector, data, fn, 1 ); - }, - off: function( types, selector, fn ) { - var handleObj, type; - if ( types && types.preventDefault && types.handleObj ) { - - // ( event ) dispatched jQuery.Event - handleObj = types.handleObj; - jQuery( types.delegateTarget ).off( - handleObj.namespace ? - handleObj.origType + "." + handleObj.namespace : - handleObj.origType, - handleObj.selector, - handleObj.handler - ); - return this; - } - if ( typeof types === "object" ) { - - // ( types-object [, selector] ) - for ( type in types ) { - this.off( type, selector, types[ type ] ); - } - return this; - } - if ( selector === false || typeof selector === "function" ) { - - // ( types [, fn] ) - fn = selector; - selector = undefined; - } - if ( fn === false ) { - fn = returnFalse; - } - return this.each( function() { - jQuery.event.remove( this, types, fn, selector ); - } ); - } -} ); - - -var - - // Support: IE <=10 - 11, Edge 12 - 13 only - // In IE/Edge using regex groups here causes severe slowdowns. - // See https://connect.microsoft.com/IE/feedback/details/1736512/ - rnoInnerhtml = /\s*$/g; - -// Prefer a tbody over its parent table for containing new rows -function manipulationTarget( elem, content ) { - if ( nodeName( elem, "table" ) && - nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { - - return jQuery( elem ).children( "tbody" )[ 0 ] || elem; - } - - return elem; -} - -// Replace/restore the type attribute of script elements for safe DOM manipulation -function disableScript( elem ) { - elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; - return elem; -} -function restoreScript( elem ) { - if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { - elem.type = elem.type.slice( 5 ); - } else { - elem.removeAttribute( "type" ); - } - - return elem; -} - -function cloneCopyEvent( src, dest ) { - var i, l, type, pdataOld, udataOld, udataCur, events; - - if ( dest.nodeType !== 1 ) { - return; - } - - // 1. Copy private data: events, handlers, etc. - if ( dataPriv.hasData( src ) ) { - pdataOld = dataPriv.get( src ); - events = pdataOld.events; - - if ( events ) { - dataPriv.remove( dest, "handle events" ); - - for ( type in events ) { - for ( i = 0, l = events[ type ].length; i < l; i++ ) { - jQuery.event.add( dest, type, events[ type ][ i ] ); - } - } - } - } - - // 2. Copy user data - if ( dataUser.hasData( src ) ) { - udataOld = dataUser.access( src ); - udataCur = jQuery.extend( {}, udataOld ); - - dataUser.set( dest, udataCur ); - } -} - -// Fix IE bugs, see support tests -function fixInput( src, dest ) { - var nodeName = dest.nodeName.toLowerCase(); - - // Fails to persist the checked state of a cloned checkbox or radio button. - if ( nodeName === "input" && rcheckableType.test( src.type ) ) { - dest.checked = src.checked; - - // Fails to return the selected option to the default selected state when cloning options - } else if ( nodeName === "input" || nodeName === "textarea" ) { - dest.defaultValue = src.defaultValue; - } -} - -function domManip( collection, args, callback, ignored ) { - - // Flatten any nested arrays - args = flat( args ); - - var fragment, first, scripts, hasScripts, node, doc, - i = 0, - l = collection.length, - iNoClone = l - 1, - value = args[ 0 ], - valueIsFunction = isFunction( value ); - - // We can't cloneNode fragments that contain checked, in WebKit - if ( valueIsFunction || - ( l > 1 && typeof value === "string" && - !support.checkClone && rchecked.test( value ) ) ) { - return collection.each( function( index ) { - var self = collection.eq( index ); - if ( valueIsFunction ) { - args[ 0 ] = value.call( this, index, self.html() ); - } - domManip( self, args, callback, ignored ); - } ); - } - - if ( l ) { - fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); - first = fragment.firstChild; - - if ( fragment.childNodes.length === 1 ) { - fragment = first; - } - - // Require either new content or an interest in ignored elements to invoke the callback - if ( first || ignored ) { - scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); - hasScripts = scripts.length; - - // Use the original fragment for the last item - // instead of the first because it can end up - // being emptied incorrectly in certain situations (#8070). - for ( ; i < l; i++ ) { - node = fragment; - - if ( i !== iNoClone ) { - node = jQuery.clone( node, true, true ); - - // Keep references to cloned scripts for later restoration - if ( hasScripts ) { - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( scripts, getAll( node, "script" ) ); - } - } - - callback.call( collection[ i ], node, i ); - } - - if ( hasScripts ) { - doc = scripts[ scripts.length - 1 ].ownerDocument; - - // Reenable scripts - jQuery.map( scripts, restoreScript ); - - // Evaluate executable scripts on first document insertion - for ( i = 0; i < hasScripts; i++ ) { - node = scripts[ i ]; - if ( rscriptType.test( node.type || "" ) && - !dataPriv.access( node, "globalEval" ) && - jQuery.contains( doc, node ) ) { - - if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { - - // Optional AJAX dependency, but won't run scripts if not present - if ( jQuery._evalUrl && !node.noModule ) { - jQuery._evalUrl( node.src, { - nonce: node.nonce || node.getAttribute( "nonce" ) - }, doc ); - } - } else { - DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); - } - } - } - } - } - } - - return collection; -} - -function remove( elem, selector, keepData ) { - var node, - nodes = selector ? jQuery.filter( selector, elem ) : elem, - i = 0; - - for ( ; ( node = nodes[ i ] ) != null; i++ ) { - if ( !keepData && node.nodeType === 1 ) { - jQuery.cleanData( getAll( node ) ); - } - - if ( node.parentNode ) { - if ( keepData && isAttached( node ) ) { - setGlobalEval( getAll( node, "script" ) ); - } - node.parentNode.removeChild( node ); - } - } - - return elem; -} - -jQuery.extend( { - htmlPrefilter: function( html ) { - return html; - }, - - clone: function( elem, dataAndEvents, deepDataAndEvents ) { - var i, l, srcElements, destElements, - clone = elem.cloneNode( true ), - inPage = isAttached( elem ); - - // Fix IE cloning issues - if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && - !jQuery.isXMLDoc( elem ) ) { - - // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 - destElements = getAll( clone ); - srcElements = getAll( elem ); - - for ( i = 0, l = srcElements.length; i < l; i++ ) { - fixInput( srcElements[ i ], destElements[ i ] ); - } - } - - // Copy the events from the original to the clone - if ( dataAndEvents ) { - if ( deepDataAndEvents ) { - srcElements = srcElements || getAll( elem ); - destElements = destElements || getAll( clone ); - - for ( i = 0, l = srcElements.length; i < l; i++ ) { - cloneCopyEvent( srcElements[ i ], destElements[ i ] ); - } - } else { - cloneCopyEvent( elem, clone ); - } - } - - // Preserve script evaluation history - destElements = getAll( clone, "script" ); - if ( destElements.length > 0 ) { - setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); - } - - // Return the cloned set - return clone; - }, - - cleanData: function( elems ) { - var data, elem, type, - special = jQuery.event.special, - i = 0; - - for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { - if ( acceptData( elem ) ) { - if ( ( data = elem[ dataPriv.expando ] ) ) { - if ( data.events ) { - for ( type in data.events ) { - if ( special[ type ] ) { - jQuery.event.remove( elem, type ); - - // This is a shortcut to avoid jQuery.event.remove's overhead - } else { - jQuery.removeEvent( elem, type, data.handle ); - } - } - } - - // Support: Chrome <=35 - 45+ - // Assign undefined instead of using delete, see Data#remove - elem[ dataPriv.expando ] = undefined; - } - if ( elem[ dataUser.expando ] ) { - - // Support: Chrome <=35 - 45+ - // Assign undefined instead of using delete, see Data#remove - elem[ dataUser.expando ] = undefined; - } - } - } - } -} ); - -jQuery.fn.extend( { - detach: function( selector ) { - return remove( this, selector, true ); - }, - - remove: function( selector ) { - return remove( this, selector ); - }, - - text: function( value ) { - return access( this, function( value ) { - return value === undefined ? - jQuery.text( this ) : - this.empty().each( function() { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - this.textContent = value; - } - } ); - }, null, value, arguments.length ); - }, - - append: function() { - return domManip( this, arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.appendChild( elem ); - } - } ); - }, - - prepend: function() { - return domManip( this, arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.insertBefore( elem, target.firstChild ); - } - } ); - }, - - before: function() { - return domManip( this, arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this ); - } - } ); - }, - - after: function() { - return domManip( this, arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this.nextSibling ); - } - } ); - }, - - empty: function() { - var elem, - i = 0; - - for ( ; ( elem = this[ i ] ) != null; i++ ) { - if ( elem.nodeType === 1 ) { - - // Prevent memory leaks - jQuery.cleanData( getAll( elem, false ) ); - - // Remove any remaining nodes - elem.textContent = ""; - } - } - - return this; - }, - - clone: function( dataAndEvents, deepDataAndEvents ) { - dataAndEvents = dataAndEvents == null ? false : dataAndEvents; - deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; - - return this.map( function() { - return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); - } ); - }, - - html: function( value ) { - return access( this, function( value ) { - var elem = this[ 0 ] || {}, - i = 0, - l = this.length; - - if ( value === undefined && elem.nodeType === 1 ) { - return elem.innerHTML; - } - - // See if we can take a shortcut and just use innerHTML - if ( typeof value === "string" && !rnoInnerhtml.test( value ) && - !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { - - value = jQuery.htmlPrefilter( value ); - - try { - for ( ; i < l; i++ ) { - elem = this[ i ] || {}; - - // Remove element nodes and prevent memory leaks - if ( elem.nodeType === 1 ) { - jQuery.cleanData( getAll( elem, false ) ); - elem.innerHTML = value; - } - } - - elem = 0; - - // If using innerHTML throws an exception, use the fallback method - } catch ( e ) {} - } - - if ( elem ) { - this.empty().append( value ); - } - }, null, value, arguments.length ); - }, - - replaceWith: function() { - var ignored = []; - - // Make the changes, replacing each non-ignored context element with the new content - return domManip( this, arguments, function( elem ) { - var parent = this.parentNode; - - if ( jQuery.inArray( this, ignored ) < 0 ) { - jQuery.cleanData( getAll( this ) ); - if ( parent ) { - parent.replaceChild( elem, this ); - } - } - - // Force callback invocation - }, ignored ); - } -} ); - -jQuery.each( { - appendTo: "append", - prependTo: "prepend", - insertBefore: "before", - insertAfter: "after", - replaceAll: "replaceWith" -}, function( name, original ) { - jQuery.fn[ name ] = function( selector ) { - var elems, - ret = [], - insert = jQuery( selector ), - last = insert.length - 1, - i = 0; - - for ( ; i <= last; i++ ) { - elems = i === last ? this : this.clone( true ); - jQuery( insert[ i ] )[ original ]( elems ); - - // Support: Android <=4.0 only, PhantomJS 1 only - // .get() because push.apply(_, arraylike) throws on ancient WebKit - push.apply( ret, elems.get() ); - } - - return this.pushStack( ret ); - }; -} ); -var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); - -var getStyles = function( elem ) { - - // Support: IE <=11 only, Firefox <=30 (#15098, #14150) - // IE throws on elements created in popups - // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" - var view = elem.ownerDocument.defaultView; - - if ( !view || !view.opener ) { - view = window; - } - - return view.getComputedStyle( elem ); - }; - -var swap = function( elem, options, callback ) { - var ret, name, - old = {}; - - // Remember the old values, and insert the new ones - for ( name in options ) { - old[ name ] = elem.style[ name ]; - elem.style[ name ] = options[ name ]; - } - - ret = callback.call( elem ); - - // Revert the old values - for ( name in options ) { - elem.style[ name ] = old[ name ]; - } - - return ret; -}; - - -var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); - - - -( function() { - - // Executing both pixelPosition & boxSizingReliable tests require only one layout - // so they're executed at the same time to save the second computation. - function computeStyleTests() { - - // This is a singleton, we need to execute it only once - if ( !div ) { - return; - } - - container.style.cssText = "position:absolute;left:-11111px;width:60px;" + - "margin-top:1px;padding:0;border:0"; - div.style.cssText = - "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + - "margin:auto;border:1px;padding:1px;" + - "width:60%;top:1%"; - documentElement.appendChild( container ).appendChild( div ); - - var divStyle = window.getComputedStyle( div ); - pixelPositionVal = divStyle.top !== "1%"; - - // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 - reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; - - // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 - // Some styles come back with percentage values, even though they shouldn't - div.style.right = "60%"; - pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; - - // Support: IE 9 - 11 only - // Detect misreporting of content dimensions for box-sizing:border-box elements - boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; - - // Support: IE 9 only - // Detect overflow:scroll screwiness (gh-3699) - // Support: Chrome <=64 - // Don't get tricked when zoom affects offsetWidth (gh-4029) - div.style.position = "absolute"; - scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; - - documentElement.removeChild( container ); - - // Nullify the div so it wouldn't be stored in the memory and - // it will also be a sign that checks already performed - div = null; - } - - function roundPixelMeasures( measure ) { - return Math.round( parseFloat( measure ) ); - } - - var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, - reliableTrDimensionsVal, reliableMarginLeftVal, - container = document.createElement( "div" ), - div = document.createElement( "div" ); - - // Finish early in limited (non-browser) environments - if ( !div.style ) { - return; - } - - // Support: IE <=9 - 11 only - // Style of cloned element affects source element cloned (#8908) - div.style.backgroundClip = "content-box"; - div.cloneNode( true ).style.backgroundClip = ""; - support.clearCloneStyle = div.style.backgroundClip === "content-box"; - - jQuery.extend( support, { - boxSizingReliable: function() { - computeStyleTests(); - return boxSizingReliableVal; - }, - pixelBoxStyles: function() { - computeStyleTests(); - return pixelBoxStylesVal; - }, - pixelPosition: function() { - computeStyleTests(); - return pixelPositionVal; - }, - reliableMarginLeft: function() { - computeStyleTests(); - return reliableMarginLeftVal; - }, - scrollboxSize: function() { - computeStyleTests(); - return scrollboxSizeVal; - }, - - // Support: IE 9 - 11+, Edge 15 - 18+ - // IE/Edge misreport `getComputedStyle` of table rows with width/height - // set in CSS while `offset*` properties report correct values. - // Behavior in IE 9 is more subtle than in newer versions & it passes - // some versions of this test; make sure not to make it pass there! - // - // Support: Firefox 70+ - // Only Firefox includes border widths - // in computed dimensions. (gh-4529) - reliableTrDimensions: function() { - var table, tr, trChild, trStyle; - if ( reliableTrDimensionsVal == null ) { - table = document.createElement( "table" ); - tr = document.createElement( "tr" ); - trChild = document.createElement( "div" ); - - table.style.cssText = "position:absolute;left:-11111px;border-collapse:separate"; - tr.style.cssText = "border:1px solid"; - - // Support: Chrome 86+ - // Height set through cssText does not get applied. - // Computed height then comes back as 0. - tr.style.height = "1px"; - trChild.style.height = "9px"; - - // Support: Android 8 Chrome 86+ - // In our bodyBackground.html iframe, - // display for all div elements is set to "inline", - // which causes a problem only in Android 8 Chrome 86. - // Ensuring the div is display: block - // gets around this issue. - trChild.style.display = "block"; - - documentElement - .appendChild( table ) - .appendChild( tr ) - .appendChild( trChild ); - - trStyle = window.getComputedStyle( tr ); - reliableTrDimensionsVal = ( parseInt( trStyle.height, 10 ) + - parseInt( trStyle.borderTopWidth, 10 ) + - parseInt( trStyle.borderBottomWidth, 10 ) ) === tr.offsetHeight; - - documentElement.removeChild( table ); - } - return reliableTrDimensionsVal; - } - } ); -} )(); - - -function curCSS( elem, name, computed ) { - var width, minWidth, maxWidth, ret, - - // Support: Firefox 51+ - // Retrieving style before computed somehow - // fixes an issue with getting wrong values - // on detached elements - style = elem.style; - - computed = computed || getStyles( elem ); - - // getPropertyValue is needed for: - // .css('filter') (IE 9 only, #12537) - // .css('--customProperty) (#3144) - if ( computed ) { - ret = computed.getPropertyValue( name ) || computed[ name ]; - - if ( ret === "" && !isAttached( elem ) ) { - ret = jQuery.style( elem, name ); - } - - // A tribute to the "awesome hack by Dean Edwards" - // Android Browser returns percentage for some values, - // but width seems to be reliably pixels. - // This is against the CSSOM draft spec: - // https://drafts.csswg.org/cssom/#resolved-values - if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { - - // Remember the original values - width = style.width; - minWidth = style.minWidth; - maxWidth = style.maxWidth; - - // Put in the new values to get a computed value out - style.minWidth = style.maxWidth = style.width = ret; - ret = computed.width; - - // Revert the changed values - style.width = width; - style.minWidth = minWidth; - style.maxWidth = maxWidth; - } - } - - return ret !== undefined ? - - // Support: IE <=9 - 11 only - // IE returns zIndex value as an integer. - ret + "" : - ret; -} - - -function addGetHookIf( conditionFn, hookFn ) { - - // Define the hook, we'll check on the first run if it's really needed. - return { - get: function() { - if ( conditionFn() ) { - - // Hook not needed (or it's not possible to use it due - // to missing dependency), remove it. - delete this.get; - return; - } - - // Hook needed; redefine it so that the support test is not executed again. - return ( this.get = hookFn ).apply( this, arguments ); - } - }; -} - - -var cssPrefixes = [ "Webkit", "Moz", "ms" ], - emptyStyle = document.createElement( "div" ).style, - vendorProps = {}; - -// Return a vendor-prefixed property or undefined -function vendorPropName( name ) { - - // Check for vendor prefixed names - var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), - i = cssPrefixes.length; - - while ( i-- ) { - name = cssPrefixes[ i ] + capName; - if ( name in emptyStyle ) { - return name; - } - } -} - -// Return a potentially-mapped jQuery.cssProps or vendor prefixed property -function finalPropName( name ) { - var final = jQuery.cssProps[ name ] || vendorProps[ name ]; - - if ( final ) { - return final; - } - if ( name in emptyStyle ) { - return name; - } - return vendorProps[ name ] = vendorPropName( name ) || name; -} - - -var - - // Swappable if display is none or starts with table - // except "table", "table-cell", or "table-caption" - // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display - rdisplayswap = /^(none|table(?!-c[ea]).+)/, - rcustomProp = /^--/, - cssShow = { position: "absolute", visibility: "hidden", display: "block" }, - cssNormalTransform = { - letterSpacing: "0", - fontWeight: "400" - }; - -function setPositiveNumber( _elem, value, subtract ) { - - // Any relative (+/-) values have already been - // normalized at this point - var matches = rcssNum.exec( value ); - return matches ? - - // Guard against undefined "subtract", e.g., when used as in cssHooks - Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : - value; -} - -function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { - var i = dimension === "width" ? 1 : 0, - extra = 0, - delta = 0; - - // Adjustment may not be necessary - if ( box === ( isBorderBox ? "border" : "content" ) ) { - return 0; - } - - for ( ; i < 4; i += 2 ) { - - // Both box models exclude margin - if ( box === "margin" ) { - delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); - } - - // If we get here with a content-box, we're seeking "padding" or "border" or "margin" - if ( !isBorderBox ) { - - // Add padding - delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - - // For "border" or "margin", add border - if ( box !== "padding" ) { - delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - - // But still keep track of it otherwise - } else { - extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - } - - // If we get here with a border-box (content + padding + border), we're seeking "content" or - // "padding" or "margin" - } else { - - // For "content", subtract padding - if ( box === "content" ) { - delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - } - - // For "content" or "padding", subtract border - if ( box !== "margin" ) { - delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - } - } - } - - // Account for positive content-box scroll gutter when requested by providing computedVal - if ( !isBorderBox && computedVal >= 0 ) { - - // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border - // Assuming integer scroll gutter, subtract the rest and round down - delta += Math.max( 0, Math.ceil( - elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - - computedVal - - delta - - extra - - 0.5 - - // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter - // Use an explicit zero to avoid NaN (gh-3964) - ) ) || 0; - } - - return delta; -} - -function getWidthOrHeight( elem, dimension, extra ) { - - // Start with computed style - var styles = getStyles( elem ), - - // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). - // Fake content-box until we know it's needed to know the true value. - boxSizingNeeded = !support.boxSizingReliable() || extra, - isBorderBox = boxSizingNeeded && - jQuery.css( elem, "boxSizing", false, styles ) === "border-box", - valueIsBorderBox = isBorderBox, - - val = curCSS( elem, dimension, styles ), - offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); - - // Support: Firefox <=54 - // Return a confounding non-pixel value or feign ignorance, as appropriate. - if ( rnumnonpx.test( val ) ) { - if ( !extra ) { - return val; - } - val = "auto"; - } - - - // Support: IE 9 - 11 only - // Use offsetWidth/offsetHeight for when box sizing is unreliable. - // In those cases, the computed value can be trusted to be border-box. - if ( ( !support.boxSizingReliable() && isBorderBox || - - // Support: IE 10 - 11+, Edge 15 - 18+ - // IE/Edge misreport `getComputedStyle` of table rows with width/height - // set in CSS while `offset*` properties report correct values. - // Interestingly, in some cases IE 9 doesn't suffer from this issue. - !support.reliableTrDimensions() && nodeName( elem, "tr" ) || - - // Fall back to offsetWidth/offsetHeight when value is "auto" - // This happens for inline elements with no explicit setting (gh-3571) - val === "auto" || - - // Support: Android <=4.1 - 4.3 only - // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) - !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && - - // Make sure the element is visible & connected - elem.getClientRects().length ) { - - isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; - - // Where available, offsetWidth/offsetHeight approximate border box dimensions. - // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the - // retrieved value as a content box dimension. - valueIsBorderBox = offsetProp in elem; - if ( valueIsBorderBox ) { - val = elem[ offsetProp ]; - } - } - - // Normalize "" and auto - val = parseFloat( val ) || 0; - - // Adjust for the element's box model - return ( val + - boxModelAdjustment( - elem, - dimension, - extra || ( isBorderBox ? "border" : "content" ), - valueIsBorderBox, - styles, - - // Provide the current computed size to request scroll gutter calculation (gh-3589) - val - ) - ) + "px"; -} - -jQuery.extend( { - - // Add in style property hooks for overriding the default - // behavior of getting and setting a style property - cssHooks: { - opacity: { - get: function( elem, computed ) { - if ( computed ) { - - // We should always get a number back from opacity - var ret = curCSS( elem, "opacity" ); - return ret === "" ? "1" : ret; - } - } - } - }, - - // Don't automatically add "px" to these possibly-unitless properties - cssNumber: { - "animationIterationCount": true, - "columnCount": true, - "fillOpacity": true, - "flexGrow": true, - "flexShrink": true, - "fontWeight": true, - "gridArea": true, - "gridColumn": true, - "gridColumnEnd": true, - "gridColumnStart": true, - "gridRow": true, - "gridRowEnd": true, - "gridRowStart": true, - "lineHeight": true, - "opacity": true, - "order": true, - "orphans": true, - "widows": true, - "zIndex": true, - "zoom": true - }, - - // Add in properties whose names you wish to fix before - // setting or getting the value - cssProps: {}, - - // Get and set the style property on a DOM Node - style: function( elem, name, value, extra ) { - - // Don't set styles on text and comment nodes - if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { - return; - } - - // Make sure that we're working with the right name - var ret, type, hooks, - origName = camelCase( name ), - isCustomProp = rcustomProp.test( name ), - style = elem.style; - - // Make sure that we're working with the right name. We don't - // want to query the value if it is a CSS custom property - // since they are user-defined. - if ( !isCustomProp ) { - name = finalPropName( origName ); - } - - // Gets hook for the prefixed version, then unprefixed version - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; - - // Check if we're setting a value - if ( value !== undefined ) { - type = typeof value; - - // Convert "+=" or "-=" to relative numbers (#7345) - if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { - value = adjustCSS( elem, name, ret ); - - // Fixes bug #9237 - type = "number"; - } - - // Make sure that null and NaN values aren't set (#7116) - if ( value == null || value !== value ) { - return; - } - - // If a number was passed in, add the unit (except for certain CSS properties) - // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append - // "px" to a few hardcoded values. - if ( type === "number" && !isCustomProp ) { - value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); - } - - // background-* props affect original clone's values - if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { - style[ name ] = "inherit"; - } - - // If a hook was provided, use that value, otherwise just set the specified value - if ( !hooks || !( "set" in hooks ) || - ( value = hooks.set( elem, value, extra ) ) !== undefined ) { - - if ( isCustomProp ) { - style.setProperty( name, value ); - } else { - style[ name ] = value; - } - } - - } else { - - // If a hook was provided get the non-computed value from there - if ( hooks && "get" in hooks && - ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { - - return ret; - } - - // Otherwise just get the value from the style object - return style[ name ]; - } - }, - - css: function( elem, name, extra, styles ) { - var val, num, hooks, - origName = camelCase( name ), - isCustomProp = rcustomProp.test( name ); - - // Make sure that we're working with the right name. We don't - // want to modify the value if it is a CSS custom property - // since they are user-defined. - if ( !isCustomProp ) { - name = finalPropName( origName ); - } - - // Try prefixed name followed by the unprefixed name - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; - - // If a hook was provided get the computed value from there - if ( hooks && "get" in hooks ) { - val = hooks.get( elem, true, extra ); - } - - // Otherwise, if a way to get the computed value exists, use that - if ( val === undefined ) { - val = curCSS( elem, name, styles ); - } - - // Convert "normal" to computed value - if ( val === "normal" && name in cssNormalTransform ) { - val = cssNormalTransform[ name ]; - } - - // Make numeric if forced or a qualifier was provided and val looks numeric - if ( extra === "" || extra ) { - num = parseFloat( val ); - return extra === true || isFinite( num ) ? num || 0 : val; - } - - return val; - } -} ); - -jQuery.each( [ "height", "width" ], function( _i, dimension ) { - jQuery.cssHooks[ dimension ] = { - get: function( elem, computed, extra ) { - if ( computed ) { - - // Certain elements can have dimension info if we invisibly show them - // but it must have a current display style that would benefit - return rdisplayswap.test( jQuery.css( elem, "display" ) ) && - - // Support: Safari 8+ - // Table columns in Safari have non-zero offsetWidth & zero - // getBoundingClientRect().width unless display is changed. - // Support: IE <=11 only - // Running getBoundingClientRect on a disconnected node - // in IE throws an error. - ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? - swap( elem, cssShow, function() { - return getWidthOrHeight( elem, dimension, extra ); - } ) : - getWidthOrHeight( elem, dimension, extra ); - } - }, - - set: function( elem, value, extra ) { - var matches, - styles = getStyles( elem ), - - // Only read styles.position if the test has a chance to fail - // to avoid forcing a reflow. - scrollboxSizeBuggy = !support.scrollboxSize() && - styles.position === "absolute", - - // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) - boxSizingNeeded = scrollboxSizeBuggy || extra, - isBorderBox = boxSizingNeeded && - jQuery.css( elem, "boxSizing", false, styles ) === "border-box", - subtract = extra ? - boxModelAdjustment( - elem, - dimension, - extra, - isBorderBox, - styles - ) : - 0; - - // Account for unreliable border-box dimensions by comparing offset* to computed and - // faking a content-box to get border and padding (gh-3699) - if ( isBorderBox && scrollboxSizeBuggy ) { - subtract -= Math.ceil( - elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - - parseFloat( styles[ dimension ] ) - - boxModelAdjustment( elem, dimension, "border", false, styles ) - - 0.5 - ); - } - - // Convert to pixels if value adjustment is needed - if ( subtract && ( matches = rcssNum.exec( value ) ) && - ( matches[ 3 ] || "px" ) !== "px" ) { - - elem.style[ dimension ] = value; - value = jQuery.css( elem, dimension ); - } - - return setPositiveNumber( elem, value, subtract ); - } - }; -} ); - -jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, - function( elem, computed ) { - if ( computed ) { - return ( parseFloat( curCSS( elem, "marginLeft" ) ) || - elem.getBoundingClientRect().left - - swap( elem, { marginLeft: 0 }, function() { - return elem.getBoundingClientRect().left; - } ) - ) + "px"; - } - } -); - -// These hooks are used by animate to expand properties -jQuery.each( { - margin: "", - padding: "", - border: "Width" -}, function( prefix, suffix ) { - jQuery.cssHooks[ prefix + suffix ] = { - expand: function( value ) { - var i = 0, - expanded = {}, - - // Assumes a single number if not a string - parts = typeof value === "string" ? value.split( " " ) : [ value ]; - - for ( ; i < 4; i++ ) { - expanded[ prefix + cssExpand[ i ] + suffix ] = - parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; - } - - return expanded; - } - }; - - if ( prefix !== "margin" ) { - jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; - } -} ); - -jQuery.fn.extend( { - css: function( name, value ) { - return access( this, function( elem, name, value ) { - var styles, len, - map = {}, - i = 0; - - if ( Array.isArray( name ) ) { - styles = getStyles( elem ); - len = name.length; - - for ( ; i < len; i++ ) { - map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); - } - - return map; - } - - return value !== undefined ? - jQuery.style( elem, name, value ) : - jQuery.css( elem, name ); - }, name, value, arguments.length > 1 ); - } -} ); - - -function Tween( elem, options, prop, end, easing ) { - return new Tween.prototype.init( elem, options, prop, end, easing ); -} -jQuery.Tween = Tween; - -Tween.prototype = { - constructor: Tween, - init: function( elem, options, prop, end, easing, unit ) { - this.elem = elem; - this.prop = prop; - this.easing = easing || jQuery.easing._default; - this.options = options; - this.start = this.now = this.cur(); - this.end = end; - this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); - }, - cur: function() { - var hooks = Tween.propHooks[ this.prop ]; - - return hooks && hooks.get ? - hooks.get( this ) : - Tween.propHooks._default.get( this ); - }, - run: function( percent ) { - var eased, - hooks = Tween.propHooks[ this.prop ]; - - if ( this.options.duration ) { - this.pos = eased = jQuery.easing[ this.easing ]( - percent, this.options.duration * percent, 0, 1, this.options.duration - ); - } else { - this.pos = eased = percent; - } - this.now = ( this.end - this.start ) * eased + this.start; - - if ( this.options.step ) { - this.options.step.call( this.elem, this.now, this ); - } - - if ( hooks && hooks.set ) { - hooks.set( this ); - } else { - Tween.propHooks._default.set( this ); - } - return this; - } -}; - -Tween.prototype.init.prototype = Tween.prototype; - -Tween.propHooks = { - _default: { - get: function( tween ) { - var result; - - // Use a property on the element directly when it is not a DOM element, - // or when there is no matching style property that exists. - if ( tween.elem.nodeType !== 1 || - tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { - return tween.elem[ tween.prop ]; - } - - // Passing an empty string as a 3rd parameter to .css will automatically - // attempt a parseFloat and fallback to a string if the parse fails. - // Simple values such as "10px" are parsed to Float; - // complex values such as "rotate(1rad)" are returned as-is. - result = jQuery.css( tween.elem, tween.prop, "" ); - - // Empty strings, null, undefined and "auto" are converted to 0. - return !result || result === "auto" ? 0 : result; - }, - set: function( tween ) { - - // Use step hook for back compat. - // Use cssHook if its there. - // Use .style if available and use plain properties where available. - if ( jQuery.fx.step[ tween.prop ] ) { - jQuery.fx.step[ tween.prop ]( tween ); - } else if ( tween.elem.nodeType === 1 && ( - jQuery.cssHooks[ tween.prop ] || - tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { - jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); - } else { - tween.elem[ tween.prop ] = tween.now; - } - } - } -}; - -// Support: IE <=9 only -// Panic based approach to setting things on disconnected nodes -Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { - set: function( tween ) { - if ( tween.elem.nodeType && tween.elem.parentNode ) { - tween.elem[ tween.prop ] = tween.now; - } - } -}; - -jQuery.easing = { - linear: function( p ) { - return p; - }, - swing: function( p ) { - return 0.5 - Math.cos( p * Math.PI ) / 2; - }, - _default: "swing" -}; - -jQuery.fx = Tween.prototype.init; - -// Back compat <1.8 extension point -jQuery.fx.step = {}; - - - - -var - fxNow, inProgress, - rfxtypes = /^(?:toggle|show|hide)$/, - rrun = /queueHooks$/; - -function schedule() { - if ( inProgress ) { - if ( document.hidden === false && window.requestAnimationFrame ) { - window.requestAnimationFrame( schedule ); - } else { - window.setTimeout( schedule, jQuery.fx.interval ); - } - - jQuery.fx.tick(); - } -} - -// Animations created synchronously will run synchronously -function createFxNow() { - window.setTimeout( function() { - fxNow = undefined; - } ); - return ( fxNow = Date.now() ); -} - -// Generate parameters to create a standard animation -function genFx( type, includeWidth ) { - var which, - i = 0, - attrs = { height: type }; - - // If we include width, step value is 1 to do all cssExpand values, - // otherwise step value is 2 to skip over Left and Right - includeWidth = includeWidth ? 1 : 0; - for ( ; i < 4; i += 2 - includeWidth ) { - which = cssExpand[ i ]; - attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; - } - - if ( includeWidth ) { - attrs.opacity = attrs.width = type; - } - - return attrs; -} - -function createTween( value, prop, animation ) { - var tween, - collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), - index = 0, - length = collection.length; - for ( ; index < length; index++ ) { - if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { - - // We're done with this property - return tween; - } - } -} - -function defaultPrefilter( elem, props, opts ) { - var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, - isBox = "width" in props || "height" in props, - anim = this, - orig = {}, - style = elem.style, - hidden = elem.nodeType && isHiddenWithinTree( elem ), - dataShow = dataPriv.get( elem, "fxshow" ); - - // Queue-skipping animations hijack the fx hooks - if ( !opts.queue ) { - hooks = jQuery._queueHooks( elem, "fx" ); - if ( hooks.unqueued == null ) { - hooks.unqueued = 0; - oldfire = hooks.empty.fire; - hooks.empty.fire = function() { - if ( !hooks.unqueued ) { - oldfire(); - } - }; - } - hooks.unqueued++; - - anim.always( function() { - - // Ensure the complete handler is called before this completes - anim.always( function() { - hooks.unqueued--; - if ( !jQuery.queue( elem, "fx" ).length ) { - hooks.empty.fire(); - } - } ); - } ); - } - - // Detect show/hide animations - for ( prop in props ) { - value = props[ prop ]; - if ( rfxtypes.test( value ) ) { - delete props[ prop ]; - toggle = toggle || value === "toggle"; - if ( value === ( hidden ? "hide" : "show" ) ) { - - // Pretend to be hidden if this is a "show" and - // there is still data from a stopped show/hide - if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { - hidden = true; - - // Ignore all other no-op show/hide data - } else { - continue; - } - } - orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); - } - } - - // Bail out if this is a no-op like .hide().hide() - propTween = !jQuery.isEmptyObject( props ); - if ( !propTween && jQuery.isEmptyObject( orig ) ) { - return; - } - - // Restrict "overflow" and "display" styles during box animations - if ( isBox && elem.nodeType === 1 ) { - - // Support: IE <=9 - 11, Edge 12 - 15 - // Record all 3 overflow attributes because IE does not infer the shorthand - // from identically-valued overflowX and overflowY and Edge just mirrors - // the overflowX value there. - opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; - - // Identify a display type, preferring old show/hide data over the CSS cascade - restoreDisplay = dataShow && dataShow.display; - if ( restoreDisplay == null ) { - restoreDisplay = dataPriv.get( elem, "display" ); - } - display = jQuery.css( elem, "display" ); - if ( display === "none" ) { - if ( restoreDisplay ) { - display = restoreDisplay; - } else { - - // Get nonempty value(s) by temporarily forcing visibility - showHide( [ elem ], true ); - restoreDisplay = elem.style.display || restoreDisplay; - display = jQuery.css( elem, "display" ); - showHide( [ elem ] ); - } - } - - // Animate inline elements as inline-block - if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { - if ( jQuery.css( elem, "float" ) === "none" ) { - - // Restore the original display value at the end of pure show/hide animations - if ( !propTween ) { - anim.done( function() { - style.display = restoreDisplay; - } ); - if ( restoreDisplay == null ) { - display = style.display; - restoreDisplay = display === "none" ? "" : display; - } - } - style.display = "inline-block"; - } - } - } - - if ( opts.overflow ) { - style.overflow = "hidden"; - anim.always( function() { - style.overflow = opts.overflow[ 0 ]; - style.overflowX = opts.overflow[ 1 ]; - style.overflowY = opts.overflow[ 2 ]; - } ); - } - - // Implement show/hide animations - propTween = false; - for ( prop in orig ) { - - // General show/hide setup for this element animation - if ( !propTween ) { - if ( dataShow ) { - if ( "hidden" in dataShow ) { - hidden = dataShow.hidden; - } - } else { - dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); - } - - // Store hidden/visible for toggle so `.stop().toggle()` "reverses" - if ( toggle ) { - dataShow.hidden = !hidden; - } - - // Show elements before animating them - if ( hidden ) { - showHide( [ elem ], true ); - } - - /* eslint-disable no-loop-func */ - - anim.done( function() { - - /* eslint-enable no-loop-func */ - - // The final step of a "hide" animation is actually hiding the element - if ( !hidden ) { - showHide( [ elem ] ); - } - dataPriv.remove( elem, "fxshow" ); - for ( prop in orig ) { - jQuery.style( elem, prop, orig[ prop ] ); - } - } ); - } - - // Per-property setup - propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); - if ( !( prop in dataShow ) ) { - dataShow[ prop ] = propTween.start; - if ( hidden ) { - propTween.end = propTween.start; - propTween.start = 0; - } - } - } -} - -function propFilter( props, specialEasing ) { - var index, name, easing, value, hooks; - - // camelCase, specialEasing and expand cssHook pass - for ( index in props ) { - name = camelCase( index ); - easing = specialEasing[ name ]; - value = props[ index ]; - if ( Array.isArray( value ) ) { - easing = value[ 1 ]; - value = props[ index ] = value[ 0 ]; - } - - if ( index !== name ) { - props[ name ] = value; - delete props[ index ]; - } - - hooks = jQuery.cssHooks[ name ]; - if ( hooks && "expand" in hooks ) { - value = hooks.expand( value ); - delete props[ name ]; - - // Not quite $.extend, this won't overwrite existing keys. - // Reusing 'index' because we have the correct "name" - for ( index in value ) { - if ( !( index in props ) ) { - props[ index ] = value[ index ]; - specialEasing[ index ] = easing; - } - } - } else { - specialEasing[ name ] = easing; - } - } -} - -function Animation( elem, properties, options ) { - var result, - stopped, - index = 0, - length = Animation.prefilters.length, - deferred = jQuery.Deferred().always( function() { - - // Don't match elem in the :animated selector - delete tick.elem; - } ), - tick = function() { - if ( stopped ) { - return false; - } - var currentTime = fxNow || createFxNow(), - remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), - - // Support: Android 2.3 only - // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) - temp = remaining / animation.duration || 0, - percent = 1 - temp, - index = 0, - length = animation.tweens.length; - - for ( ; index < length; index++ ) { - animation.tweens[ index ].run( percent ); - } - - deferred.notifyWith( elem, [ animation, percent, remaining ] ); - - // If there's more to do, yield - if ( percent < 1 && length ) { - return remaining; - } - - // If this was an empty animation, synthesize a final progress notification - if ( !length ) { - deferred.notifyWith( elem, [ animation, 1, 0 ] ); - } - - // Resolve the animation and report its conclusion - deferred.resolveWith( elem, [ animation ] ); - return false; - }, - animation = deferred.promise( { - elem: elem, - props: jQuery.extend( {}, properties ), - opts: jQuery.extend( true, { - specialEasing: {}, - easing: jQuery.easing._default - }, options ), - originalProperties: properties, - originalOptions: options, - startTime: fxNow || createFxNow(), - duration: options.duration, - tweens: [], - createTween: function( prop, end ) { - var tween = jQuery.Tween( elem, animation.opts, prop, end, - animation.opts.specialEasing[ prop ] || animation.opts.easing ); - animation.tweens.push( tween ); - return tween; - }, - stop: function( gotoEnd ) { - var index = 0, - - // If we are going to the end, we want to run all the tweens - // otherwise we skip this part - length = gotoEnd ? animation.tweens.length : 0; - if ( stopped ) { - return this; - } - stopped = true; - for ( ; index < length; index++ ) { - animation.tweens[ index ].run( 1 ); - } - - // Resolve when we played the last frame; otherwise, reject - if ( gotoEnd ) { - deferred.notifyWith( elem, [ animation, 1, 0 ] ); - deferred.resolveWith( elem, [ animation, gotoEnd ] ); - } else { - deferred.rejectWith( elem, [ animation, gotoEnd ] ); - } - return this; - } - } ), - props = animation.props; - - propFilter( props, animation.opts.specialEasing ); - - for ( ; index < length; index++ ) { - result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); - if ( result ) { - if ( isFunction( result.stop ) ) { - jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = - result.stop.bind( result ); - } - return result; - } - } - - jQuery.map( props, createTween, animation ); - - if ( isFunction( animation.opts.start ) ) { - animation.opts.start.call( elem, animation ); - } - - // Attach callbacks from options - animation - .progress( animation.opts.progress ) - .done( animation.opts.done, animation.opts.complete ) - .fail( animation.opts.fail ) - .always( animation.opts.always ); - - jQuery.fx.timer( - jQuery.extend( tick, { - elem: elem, - anim: animation, - queue: animation.opts.queue - } ) - ); - - return animation; -} - -jQuery.Animation = jQuery.extend( Animation, { - - tweeners: { - "*": [ function( prop, value ) { - var tween = this.createTween( prop, value ); - adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); - return tween; - } ] - }, - - tweener: function( props, callback ) { - if ( isFunction( props ) ) { - callback = props; - props = [ "*" ]; - } else { - props = props.match( rnothtmlwhite ); - } - - var prop, - index = 0, - length = props.length; - - for ( ; index < length; index++ ) { - prop = props[ index ]; - Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; - Animation.tweeners[ prop ].unshift( callback ); - } - }, - - prefilters: [ defaultPrefilter ], - - prefilter: function( callback, prepend ) { - if ( prepend ) { - Animation.prefilters.unshift( callback ); - } else { - Animation.prefilters.push( callback ); - } - } -} ); - -jQuery.speed = function( speed, easing, fn ) { - var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { - complete: fn || !fn && easing || - isFunction( speed ) && speed, - duration: speed, - easing: fn && easing || easing && !isFunction( easing ) && easing - }; - - // Go to the end state if fx are off - if ( jQuery.fx.off ) { - opt.duration = 0; - - } else { - if ( typeof opt.duration !== "number" ) { - if ( opt.duration in jQuery.fx.speeds ) { - opt.duration = jQuery.fx.speeds[ opt.duration ]; - - } else { - opt.duration = jQuery.fx.speeds._default; - } - } - } - - // Normalize opt.queue - true/undefined/null -> "fx" - if ( opt.queue == null || opt.queue === true ) { - opt.queue = "fx"; - } - - // Queueing - opt.old = opt.complete; - - opt.complete = function() { - if ( isFunction( opt.old ) ) { - opt.old.call( this ); - } - - if ( opt.queue ) { - jQuery.dequeue( this, opt.queue ); - } - }; - - return opt; -}; - -jQuery.fn.extend( { - fadeTo: function( speed, to, easing, callback ) { - - // Show any hidden elements after setting opacity to 0 - return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() - - // Animate to the value specified - .end().animate( { opacity: to }, speed, easing, callback ); - }, - animate: function( prop, speed, easing, callback ) { - var empty = jQuery.isEmptyObject( prop ), - optall = jQuery.speed( speed, easing, callback ), - doAnimation = function() { - - // Operate on a copy of prop so per-property easing won't be lost - var anim = Animation( this, jQuery.extend( {}, prop ), optall ); - - // Empty animations, or finishing resolves immediately - if ( empty || dataPriv.get( this, "finish" ) ) { - anim.stop( true ); - } - }; - - doAnimation.finish = doAnimation; - - return empty || optall.queue === false ? - this.each( doAnimation ) : - this.queue( optall.queue, doAnimation ); - }, - stop: function( type, clearQueue, gotoEnd ) { - var stopQueue = function( hooks ) { - var stop = hooks.stop; - delete hooks.stop; - stop( gotoEnd ); - }; - - if ( typeof type !== "string" ) { - gotoEnd = clearQueue; - clearQueue = type; - type = undefined; - } - if ( clearQueue ) { - this.queue( type || "fx", [] ); - } - - return this.each( function() { - var dequeue = true, - index = type != null && type + "queueHooks", - timers = jQuery.timers, - data = dataPriv.get( this ); - - if ( index ) { - if ( data[ index ] && data[ index ].stop ) { - stopQueue( data[ index ] ); - } - } else { - for ( index in data ) { - if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { - stopQueue( data[ index ] ); - } - } - } - - for ( index = timers.length; index--; ) { - if ( timers[ index ].elem === this && - ( type == null || timers[ index ].queue === type ) ) { - - timers[ index ].anim.stop( gotoEnd ); - dequeue = false; - timers.splice( index, 1 ); - } - } - - // Start the next in the queue if the last step wasn't forced. - // Timers currently will call their complete callbacks, which - // will dequeue but only if they were gotoEnd. - if ( dequeue || !gotoEnd ) { - jQuery.dequeue( this, type ); - } - } ); - }, - finish: function( type ) { - if ( type !== false ) { - type = type || "fx"; - } - return this.each( function() { - var index, - data = dataPriv.get( this ), - queue = data[ type + "queue" ], - hooks = data[ type + "queueHooks" ], - timers = jQuery.timers, - length = queue ? queue.length : 0; - - // Enable finishing flag on private data - data.finish = true; - - // Empty the queue first - jQuery.queue( this, type, [] ); - - if ( hooks && hooks.stop ) { - hooks.stop.call( this, true ); - } - - // Look for any active animations, and finish them - for ( index = timers.length; index--; ) { - if ( timers[ index ].elem === this && timers[ index ].queue === type ) { - timers[ index ].anim.stop( true ); - timers.splice( index, 1 ); - } - } - - // Look for any animations in the old queue and finish them - for ( index = 0; index < length; index++ ) { - if ( queue[ index ] && queue[ index ].finish ) { - queue[ index ].finish.call( this ); - } - } - - // Turn off finishing flag - delete data.finish; - } ); - } -} ); - -jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) { - var cssFn = jQuery.fn[ name ]; - jQuery.fn[ name ] = function( speed, easing, callback ) { - return speed == null || typeof speed === "boolean" ? - cssFn.apply( this, arguments ) : - this.animate( genFx( name, true ), speed, easing, callback ); - }; -} ); - -// Generate shortcuts for custom animations -jQuery.each( { - slideDown: genFx( "show" ), - slideUp: genFx( "hide" ), - slideToggle: genFx( "toggle" ), - fadeIn: { opacity: "show" }, - fadeOut: { opacity: "hide" }, - fadeToggle: { opacity: "toggle" } -}, function( name, props ) { - jQuery.fn[ name ] = function( speed, easing, callback ) { - return this.animate( props, speed, easing, callback ); - }; -} ); - -jQuery.timers = []; -jQuery.fx.tick = function() { - var timer, - i = 0, - timers = jQuery.timers; - - fxNow = Date.now(); - - for ( ; i < timers.length; i++ ) { - timer = timers[ i ]; - - // Run the timer and safely remove it when done (allowing for external removal) - if ( !timer() && timers[ i ] === timer ) { - timers.splice( i--, 1 ); - } - } - - if ( !timers.length ) { - jQuery.fx.stop(); - } - fxNow = undefined; -}; - -jQuery.fx.timer = function( timer ) { - jQuery.timers.push( timer ); - jQuery.fx.start(); -}; - -jQuery.fx.interval = 13; -jQuery.fx.start = function() { - if ( inProgress ) { - return; - } - - inProgress = true; - schedule(); -}; - -jQuery.fx.stop = function() { - inProgress = null; -}; - -jQuery.fx.speeds = { - slow: 600, - fast: 200, - - // Default speed - _default: 400 -}; - - -// Based off of the plugin by Clint Helfers, with permission. -// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ -jQuery.fn.delay = function( time, type ) { - time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; - type = type || "fx"; - - return this.queue( type, function( next, hooks ) { - var timeout = window.setTimeout( next, time ); - hooks.stop = function() { - window.clearTimeout( timeout ); - }; - } ); -}; - - -( function() { - var input = document.createElement( "input" ), - select = document.createElement( "select" ), - opt = select.appendChild( document.createElement( "option" ) ); - - input.type = "checkbox"; - - // Support: Android <=4.3 only - // Default value for a checkbox should be "on" - support.checkOn = input.value !== ""; - - // Support: IE <=11 only - // Must access selectedIndex to make default options select - support.optSelected = opt.selected; - - // Support: IE <=11 only - // An input loses its value after becoming a radio - input = document.createElement( "input" ); - input.value = "t"; - input.type = "radio"; - support.radioValue = input.value === "t"; -} )(); - - -var boolHook, - attrHandle = jQuery.expr.attrHandle; - -jQuery.fn.extend( { - attr: function( name, value ) { - return access( this, jQuery.attr, name, value, arguments.length > 1 ); - }, - - removeAttr: function( name ) { - return this.each( function() { - jQuery.removeAttr( this, name ); - } ); - } -} ); - -jQuery.extend( { - attr: function( elem, name, value ) { - var ret, hooks, - nType = elem.nodeType; - - // Don't get/set attributes on text, comment and attribute nodes - if ( nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - // Fallback to prop when attributes are not supported - if ( typeof elem.getAttribute === "undefined" ) { - return jQuery.prop( elem, name, value ); - } - - // Attribute hooks are determined by the lowercase version - // Grab necessary hook if one is defined - if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { - hooks = jQuery.attrHooks[ name.toLowerCase() ] || - ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); - } - - if ( value !== undefined ) { - if ( value === null ) { - jQuery.removeAttr( elem, name ); - return; - } - - if ( hooks && "set" in hooks && - ( ret = hooks.set( elem, value, name ) ) !== undefined ) { - return ret; - } - - elem.setAttribute( name, value + "" ); - return value; - } - - if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { - return ret; - } - - ret = jQuery.find.attr( elem, name ); - - // Non-existent attributes return null, we normalize to undefined - return ret == null ? undefined : ret; - }, - - attrHooks: { - type: { - set: function( elem, value ) { - if ( !support.radioValue && value === "radio" && - nodeName( elem, "input" ) ) { - var val = elem.value; - elem.setAttribute( "type", value ); - if ( val ) { - elem.value = val; - } - return value; - } - } - } - }, - - removeAttr: function( elem, value ) { - var name, - i = 0, - - // Attribute names can contain non-HTML whitespace characters - // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 - attrNames = value && value.match( rnothtmlwhite ); - - if ( attrNames && elem.nodeType === 1 ) { - while ( ( name = attrNames[ i++ ] ) ) { - elem.removeAttribute( name ); - } - } - } -} ); - -// Hooks for boolean attributes -boolHook = { - set: function( elem, value, name ) { - if ( value === false ) { - - // Remove boolean attributes when set to false - jQuery.removeAttr( elem, name ); - } else { - elem.setAttribute( name, name ); - } - return name; - } -}; - -jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) { - var getter = attrHandle[ name ] || jQuery.find.attr; - - attrHandle[ name ] = function( elem, name, isXML ) { - var ret, handle, - lowercaseName = name.toLowerCase(); - - if ( !isXML ) { - - // Avoid an infinite loop by temporarily removing this function from the getter - handle = attrHandle[ lowercaseName ]; - attrHandle[ lowercaseName ] = ret; - ret = getter( elem, name, isXML ) != null ? - lowercaseName : - null; - attrHandle[ lowercaseName ] = handle; - } - return ret; - }; -} ); - - - - -var rfocusable = /^(?:input|select|textarea|button)$/i, - rclickable = /^(?:a|area)$/i; - -jQuery.fn.extend( { - prop: function( name, value ) { - return access( this, jQuery.prop, name, value, arguments.length > 1 ); - }, - - removeProp: function( name ) { - return this.each( function() { - delete this[ jQuery.propFix[ name ] || name ]; - } ); - } -} ); - -jQuery.extend( { - prop: function( elem, name, value ) { - var ret, hooks, - nType = elem.nodeType; - - // Don't get/set properties on text, comment and attribute nodes - if ( nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { - - // Fix name and attach hooks - name = jQuery.propFix[ name ] || name; - hooks = jQuery.propHooks[ name ]; - } - - if ( value !== undefined ) { - if ( hooks && "set" in hooks && - ( ret = hooks.set( elem, value, name ) ) !== undefined ) { - return ret; - } - - return ( elem[ name ] = value ); - } - - if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { - return ret; - } - - return elem[ name ]; - }, - - propHooks: { - tabIndex: { - get: function( elem ) { - - // Support: IE <=9 - 11 only - // elem.tabIndex doesn't always return the - // correct value when it hasn't been explicitly set - // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ - // Use proper attribute retrieval(#12072) - var tabindex = jQuery.find.attr( elem, "tabindex" ); - - if ( tabindex ) { - return parseInt( tabindex, 10 ); - } - - if ( - rfocusable.test( elem.nodeName ) || - rclickable.test( elem.nodeName ) && - elem.href - ) { - return 0; - } - - return -1; - } - } - }, - - propFix: { - "for": "htmlFor", - "class": "className" - } -} ); - -// Support: IE <=11 only -// Accessing the selectedIndex property -// forces the browser to respect setting selected -// on the option -// The getter ensures a default option is selected -// when in an optgroup -// eslint rule "no-unused-expressions" is disabled for this code -// since it considers such accessions noop -if ( !support.optSelected ) { - jQuery.propHooks.selected = { - get: function( elem ) { - - /* eslint no-unused-expressions: "off" */ - - var parent = elem.parentNode; - if ( parent && parent.parentNode ) { - parent.parentNode.selectedIndex; - } - return null; - }, - set: function( elem ) { - - /* eslint no-unused-expressions: "off" */ - - var parent = elem.parentNode; - if ( parent ) { - parent.selectedIndex; - - if ( parent.parentNode ) { - parent.parentNode.selectedIndex; - } - } - } - }; -} - -jQuery.each( [ - "tabIndex", - "readOnly", - "maxLength", - "cellSpacing", - "cellPadding", - "rowSpan", - "colSpan", - "useMap", - "frameBorder", - "contentEditable" -], function() { - jQuery.propFix[ this.toLowerCase() ] = this; -} ); - - - - - // Strip and collapse whitespace according to HTML spec - // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace - function stripAndCollapse( value ) { - var tokens = value.match( rnothtmlwhite ) || []; - return tokens.join( " " ); - } - - -function getClass( elem ) { - return elem.getAttribute && elem.getAttribute( "class" ) || ""; -} - -function classesToArray( value ) { - if ( Array.isArray( value ) ) { - return value; - } - if ( typeof value === "string" ) { - return value.match( rnothtmlwhite ) || []; - } - return []; -} - -jQuery.fn.extend( { - addClass: function( value ) { - var classes, elem, cur, curValue, clazz, j, finalValue, - i = 0; - - if ( isFunction( value ) ) { - return this.each( function( j ) { - jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); - } ); - } - - classes = classesToArray( value ); - - if ( classes.length ) { - while ( ( elem = this[ i++ ] ) ) { - curValue = getClass( elem ); - cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); - - if ( cur ) { - j = 0; - while ( ( clazz = classes[ j++ ] ) ) { - if ( cur.indexOf( " " + clazz + " " ) < 0 ) { - cur += clazz + " "; - } - } - - // Only assign if different to avoid unneeded rendering. - finalValue = stripAndCollapse( cur ); - if ( curValue !== finalValue ) { - elem.setAttribute( "class", finalValue ); - } - } - } - } - - return this; - }, - - removeClass: function( value ) { - var classes, elem, cur, curValue, clazz, j, finalValue, - i = 0; - - if ( isFunction( value ) ) { - return this.each( function( j ) { - jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); - } ); - } - - if ( !arguments.length ) { - return this.attr( "class", "" ); - } - - classes = classesToArray( value ); - - if ( classes.length ) { - while ( ( elem = this[ i++ ] ) ) { - curValue = getClass( elem ); - - // This expression is here for better compressibility (see addClass) - cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); - - if ( cur ) { - j = 0; - while ( ( clazz = classes[ j++ ] ) ) { - - // Remove *all* instances - while ( cur.indexOf( " " + clazz + " " ) > -1 ) { - cur = cur.replace( " " + clazz + " ", " " ); - } - } - - // Only assign if different to avoid unneeded rendering. - finalValue = stripAndCollapse( cur ); - if ( curValue !== finalValue ) { - elem.setAttribute( "class", finalValue ); - } - } - } - } - - return this; - }, - - toggleClass: function( value, stateVal ) { - var type = typeof value, - isValidValue = type === "string" || Array.isArray( value ); - - if ( typeof stateVal === "boolean" && isValidValue ) { - return stateVal ? this.addClass( value ) : this.removeClass( value ); - } - - if ( isFunction( value ) ) { - return this.each( function( i ) { - jQuery( this ).toggleClass( - value.call( this, i, getClass( this ), stateVal ), - stateVal - ); - } ); - } - - return this.each( function() { - var className, i, self, classNames; - - if ( isValidValue ) { - - // Toggle individual class names - i = 0; - self = jQuery( this ); - classNames = classesToArray( value ); - - while ( ( className = classNames[ i++ ] ) ) { - - // Check each className given, space separated list - if ( self.hasClass( className ) ) { - self.removeClass( className ); - } else { - self.addClass( className ); - } - } - - // Toggle whole class name - } else if ( value === undefined || type === "boolean" ) { - className = getClass( this ); - if ( className ) { - - // Store className if set - dataPriv.set( this, "__className__", className ); - } - - // If the element has a class name or if we're passed `false`, - // then remove the whole classname (if there was one, the above saved it). - // Otherwise bring back whatever was previously saved (if anything), - // falling back to the empty string if nothing was stored. - if ( this.setAttribute ) { - this.setAttribute( "class", - className || value === false ? - "" : - dataPriv.get( this, "__className__" ) || "" - ); - } - } - } ); - }, - - hasClass: function( selector ) { - var className, elem, - i = 0; - - className = " " + selector + " "; - while ( ( elem = this[ i++ ] ) ) { - if ( elem.nodeType === 1 && - ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { - return true; - } - } - - return false; - } -} ); - - - - -var rreturn = /\r/g; - -jQuery.fn.extend( { - val: function( value ) { - var hooks, ret, valueIsFunction, - elem = this[ 0 ]; - - if ( !arguments.length ) { - if ( elem ) { - hooks = jQuery.valHooks[ elem.type ] || - jQuery.valHooks[ elem.nodeName.toLowerCase() ]; - - if ( hooks && - "get" in hooks && - ( ret = hooks.get( elem, "value" ) ) !== undefined - ) { - return ret; - } - - ret = elem.value; - - // Handle most common string cases - if ( typeof ret === "string" ) { - return ret.replace( rreturn, "" ); - } - - // Handle cases where value is null/undef or number - return ret == null ? "" : ret; - } - - return; - } - - valueIsFunction = isFunction( value ); - - return this.each( function( i ) { - var val; - - if ( this.nodeType !== 1 ) { - return; - } - - if ( valueIsFunction ) { - val = value.call( this, i, jQuery( this ).val() ); - } else { - val = value; - } - - // Treat null/undefined as ""; convert numbers to string - if ( val == null ) { - val = ""; - - } else if ( typeof val === "number" ) { - val += ""; - - } else if ( Array.isArray( val ) ) { - val = jQuery.map( val, function( value ) { - return value == null ? "" : value + ""; - } ); - } - - hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; - - // If set returns undefined, fall back to normal setting - if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { - this.value = val; - } - } ); - } -} ); - -jQuery.extend( { - valHooks: { - option: { - get: function( elem ) { - - var val = jQuery.find.attr( elem, "value" ); - return val != null ? - val : - - // Support: IE <=10 - 11 only - // option.text throws exceptions (#14686, #14858) - // Strip and collapse whitespace - // https://html.spec.whatwg.org/#strip-and-collapse-whitespace - stripAndCollapse( jQuery.text( elem ) ); - } - }, - select: { - get: function( elem ) { - var value, option, i, - options = elem.options, - index = elem.selectedIndex, - one = elem.type === "select-one", - values = one ? null : [], - max = one ? index + 1 : options.length; - - if ( index < 0 ) { - i = max; - - } else { - i = one ? index : 0; - } - - // Loop through all the selected options - for ( ; i < max; i++ ) { - option = options[ i ]; - - // Support: IE <=9 only - // IE8-9 doesn't update selected after form reset (#2551) - if ( ( option.selected || i === index ) && - - // Don't return options that are disabled or in a disabled optgroup - !option.disabled && - ( !option.parentNode.disabled || - !nodeName( option.parentNode, "optgroup" ) ) ) { - - // Get the specific value for the option - value = jQuery( option ).val(); - - // We don't need an array for one selects - if ( one ) { - return value; - } - - // Multi-Selects return an array - values.push( value ); - } - } - - return values; - }, - - set: function( elem, value ) { - var optionSet, option, - options = elem.options, - values = jQuery.makeArray( value ), - i = options.length; - - while ( i-- ) { - option = options[ i ]; - - /* eslint-disable no-cond-assign */ - - if ( option.selected = - jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 - ) { - optionSet = true; - } - - /* eslint-enable no-cond-assign */ - } - - // Force browsers to behave consistently when non-matching value is set - if ( !optionSet ) { - elem.selectedIndex = -1; - } - return values; - } - } - } -} ); - -// Radios and checkboxes getter/setter -jQuery.each( [ "radio", "checkbox" ], function() { - jQuery.valHooks[ this ] = { - set: function( elem, value ) { - if ( Array.isArray( value ) ) { - return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); - } - } - }; - if ( !support.checkOn ) { - jQuery.valHooks[ this ].get = function( elem ) { - return elem.getAttribute( "value" ) === null ? "on" : elem.value; - }; - } -} ); - - - - -// Return jQuery for attributes-only inclusion - - -support.focusin = "onfocusin" in window; - - -var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, - stopPropagationCallback = function( e ) { - e.stopPropagation(); - }; - -jQuery.extend( jQuery.event, { - - trigger: function( event, data, elem, onlyHandlers ) { - - var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, - eventPath = [ elem || document ], - type = hasOwn.call( event, "type" ) ? event.type : event, - namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; - - cur = lastElement = tmp = elem = elem || document; - - // Don't do events on text and comment nodes - if ( elem.nodeType === 3 || elem.nodeType === 8 ) { - return; - } - - // focus/blur morphs to focusin/out; ensure we're not firing them right now - if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { - return; - } - - if ( type.indexOf( "." ) > -1 ) { - - // Namespaced trigger; create a regexp to match event type in handle() - namespaces = type.split( "." ); - type = namespaces.shift(); - namespaces.sort(); - } - ontype = type.indexOf( ":" ) < 0 && "on" + type; - - // Caller can pass in a jQuery.Event object, Object, or just an event type string - event = event[ jQuery.expando ] ? - event : - new jQuery.Event( type, typeof event === "object" && event ); - - // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) - event.isTrigger = onlyHandlers ? 2 : 3; - event.namespace = namespaces.join( "." ); - event.rnamespace = event.namespace ? - new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : - null; - - // Clean up the event in case it is being reused - event.result = undefined; - if ( !event.target ) { - event.target = elem; - } - - // Clone any incoming data and prepend the event, creating the handler arg list - data = data == null ? - [ event ] : - jQuery.makeArray( data, [ event ] ); - - // Allow special events to draw outside the lines - special = jQuery.event.special[ type ] || {}; - if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { - return; - } - - // Determine event propagation path in advance, per W3C events spec (#9951) - // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) - if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { - - bubbleType = special.delegateType || type; - if ( !rfocusMorph.test( bubbleType + type ) ) { - cur = cur.parentNode; - } - for ( ; cur; cur = cur.parentNode ) { - eventPath.push( cur ); - tmp = cur; - } - - // Only add window if we got to document (e.g., not plain obj or detached DOM) - if ( tmp === ( elem.ownerDocument || document ) ) { - eventPath.push( tmp.defaultView || tmp.parentWindow || window ); - } - } - - // Fire handlers on the event path - i = 0; - while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { - lastElement = cur; - event.type = i > 1 ? - bubbleType : - special.bindType || type; - - // jQuery handler - handle = ( dataPriv.get( cur, "events" ) || Object.create( null ) )[ event.type ] && - dataPriv.get( cur, "handle" ); - if ( handle ) { - handle.apply( cur, data ); - } - - // Native handler - handle = ontype && cur[ ontype ]; - if ( handle && handle.apply && acceptData( cur ) ) { - event.result = handle.apply( cur, data ); - if ( event.result === false ) { - event.preventDefault(); - } - } - } - event.type = type; - - // If nobody prevented the default action, do it now - if ( !onlyHandlers && !event.isDefaultPrevented() ) { - - if ( ( !special._default || - special._default.apply( eventPath.pop(), data ) === false ) && - acceptData( elem ) ) { - - // Call a native DOM method on the target with the same name as the event. - // Don't do default actions on window, that's where global variables be (#6170) - if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { - - // Don't re-trigger an onFOO event when we call its FOO() method - tmp = elem[ ontype ]; - - if ( tmp ) { - elem[ ontype ] = null; - } - - // Prevent re-triggering of the same event, since we already bubbled it above - jQuery.event.triggered = type; - - if ( event.isPropagationStopped() ) { - lastElement.addEventListener( type, stopPropagationCallback ); - } - - elem[ type ](); - - if ( event.isPropagationStopped() ) { - lastElement.removeEventListener( type, stopPropagationCallback ); - } - - jQuery.event.triggered = undefined; - - if ( tmp ) { - elem[ ontype ] = tmp; - } - } - } - } - - return event.result; - }, - - // Piggyback on a donor event to simulate a different one - // Used only for `focus(in | out)` events - simulate: function( type, elem, event ) { - var e = jQuery.extend( - new jQuery.Event(), - event, - { - type: type, - isSimulated: true - } - ); - - jQuery.event.trigger( e, null, elem ); - } - -} ); - -jQuery.fn.extend( { - - trigger: function( type, data ) { - return this.each( function() { - jQuery.event.trigger( type, data, this ); - } ); - }, - triggerHandler: function( type, data ) { - var elem = this[ 0 ]; - if ( elem ) { - return jQuery.event.trigger( type, data, elem, true ); - } - } -} ); - - -// Support: Firefox <=44 -// Firefox doesn't have focus(in | out) events -// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 -// -// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 -// focus(in | out) events fire after focus & blur events, -// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order -// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 -if ( !support.focusin ) { - jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { - - // Attach a single capturing handler on the document while someone wants focusin/focusout - var handler = function( event ) { - jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); - }; - - jQuery.event.special[ fix ] = { - setup: function() { - - // Handle: regular nodes (via `this.ownerDocument`), window - // (via `this.document`) & document (via `this`). - var doc = this.ownerDocument || this.document || this, - attaches = dataPriv.access( doc, fix ); - - if ( !attaches ) { - doc.addEventListener( orig, handler, true ); - } - dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); - }, - teardown: function() { - var doc = this.ownerDocument || this.document || this, - attaches = dataPriv.access( doc, fix ) - 1; - - if ( !attaches ) { - doc.removeEventListener( orig, handler, true ); - dataPriv.remove( doc, fix ); - - } else { - dataPriv.access( doc, fix, attaches ); - } - } - }; - } ); -} -var location = window.location; - -var nonce = { guid: Date.now() }; - -var rquery = ( /\?/ ); - - - -// Cross-browser xml parsing -jQuery.parseXML = function( data ) { - var xml, parserErrorElem; - if ( !data || typeof data !== "string" ) { - return null; - } - - // Support: IE 9 - 11 only - // IE throws on parseFromString with invalid input. - try { - xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); - } catch ( e ) {} - - parserErrorElem = xml && xml.getElementsByTagName( "parsererror" )[ 0 ]; - if ( !xml || parserErrorElem ) { - jQuery.error( "Invalid XML: " + ( - parserErrorElem ? - jQuery.map( parserErrorElem.childNodes, function( el ) { - return el.textContent; - } ).join( "\n" ) : - data - ) ); - } - return xml; -}; - - -var - rbracket = /\[\]$/, - rCRLF = /\r?\n/g, - rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, - rsubmittable = /^(?:input|select|textarea|keygen)/i; - -function buildParams( prefix, obj, traditional, add ) { - var name; - - if ( Array.isArray( obj ) ) { - - // Serialize array item. - jQuery.each( obj, function( i, v ) { - if ( traditional || rbracket.test( prefix ) ) { - - // Treat each array item as a scalar. - add( prefix, v ); - - } else { - - // Item is non-scalar (array or object), encode its numeric index. - buildParams( - prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", - v, - traditional, - add - ); - } - } ); - - } else if ( !traditional && toType( obj ) === "object" ) { - - // Serialize object item. - for ( name in obj ) { - buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); - } - - } else { - - // Serialize scalar item. - add( prefix, obj ); - } -} - -// Serialize an array of form elements or a set of -// key/values into a query string -jQuery.param = function( a, traditional ) { - var prefix, - s = [], - add = function( key, valueOrFunction ) { - - // If value is a function, invoke it and use its return value - var value = isFunction( valueOrFunction ) ? - valueOrFunction() : - valueOrFunction; - - s[ s.length ] = encodeURIComponent( key ) + "=" + - encodeURIComponent( value == null ? "" : value ); - }; - - if ( a == null ) { - return ""; - } - - // If an array was passed in, assume that it is an array of form elements. - if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { - - // Serialize the form elements - jQuery.each( a, function() { - add( this.name, this.value ); - } ); - - } else { - - // If traditional, encode the "old" way (the way 1.3.2 or older - // did it), otherwise encode params recursively. - for ( prefix in a ) { - buildParams( prefix, a[ prefix ], traditional, add ); - } - } - - // Return the resulting serialization - return s.join( "&" ); -}; - -jQuery.fn.extend( { - serialize: function() { - return jQuery.param( this.serializeArray() ); - }, - serializeArray: function() { - return this.map( function() { - - // Can add propHook for "elements" to filter or add form elements - var elements = jQuery.prop( this, "elements" ); - return elements ? jQuery.makeArray( elements ) : this; - } ).filter( function() { - var type = this.type; - - // Use .is( ":disabled" ) so that fieldset[disabled] works - return this.name && !jQuery( this ).is( ":disabled" ) && - rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && - ( this.checked || !rcheckableType.test( type ) ); - } ).map( function( _i, elem ) { - var val = jQuery( this ).val(); - - if ( val == null ) { - return null; - } - - if ( Array.isArray( val ) ) { - return jQuery.map( val, function( val ) { - return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; - } ); - } - - return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; - } ).get(); - } -} ); - - -var - r20 = /%20/g, - rhash = /#.*$/, - rantiCache = /([?&])_=[^&]*/, - rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, - - // #7653, #8125, #8152: local protocol detection - rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, - rnoContent = /^(?:GET|HEAD)$/, - rprotocol = /^\/\//, - - /* Prefilters - * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) - * 2) These are called: - * - BEFORE asking for a transport - * - AFTER param serialization (s.data is a string if s.processData is true) - * 3) key is the dataType - * 4) the catchall symbol "*" can be used - * 5) execution will start with transport dataType and THEN continue down to "*" if needed - */ - prefilters = {}, - - /* Transports bindings - * 1) key is the dataType - * 2) the catchall symbol "*" can be used - * 3) selection will start with transport dataType and THEN go to "*" if needed - */ - transports = {}, - - // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression - allTypes = "*/".concat( "*" ), - - // Anchor tag for parsing the document origin - originAnchor = document.createElement( "a" ); - -originAnchor.href = location.href; - -// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport -function addToPrefiltersOrTransports( structure ) { - - // dataTypeExpression is optional and defaults to "*" - return function( dataTypeExpression, func ) { - - if ( typeof dataTypeExpression !== "string" ) { - func = dataTypeExpression; - dataTypeExpression = "*"; - } - - var dataType, - i = 0, - dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; - - if ( isFunction( func ) ) { - - // For each dataType in the dataTypeExpression - while ( ( dataType = dataTypes[ i++ ] ) ) { - - // Prepend if requested - if ( dataType[ 0 ] === "+" ) { - dataType = dataType.slice( 1 ) || "*"; - ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); - - // Otherwise append - } else { - ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); - } - } - } - }; -} - -// Base inspection function for prefilters and transports -function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { - - var inspected = {}, - seekingTransport = ( structure === transports ); - - function inspect( dataType ) { - var selected; - inspected[ dataType ] = true; - jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { - var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); - if ( typeof dataTypeOrTransport === "string" && - !seekingTransport && !inspected[ dataTypeOrTransport ] ) { - - options.dataTypes.unshift( dataTypeOrTransport ); - inspect( dataTypeOrTransport ); - return false; - } else if ( seekingTransport ) { - return !( selected = dataTypeOrTransport ); - } - } ); - return selected; - } - - return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); -} - -// A special extend for ajax options -// that takes "flat" options (not to be deep extended) -// Fixes #9887 -function ajaxExtend( target, src ) { - var key, deep, - flatOptions = jQuery.ajaxSettings.flatOptions || {}; - - for ( key in src ) { - if ( src[ key ] !== undefined ) { - ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; - } - } - if ( deep ) { - jQuery.extend( true, target, deep ); - } - - return target; -} - -/* Handles responses to an ajax request: - * - finds the right dataType (mediates between content-type and expected dataType) - * - returns the corresponding response - */ -function ajaxHandleResponses( s, jqXHR, responses ) { - - var ct, type, finalDataType, firstDataType, - contents = s.contents, - dataTypes = s.dataTypes; - - // Remove auto dataType and get content-type in the process - while ( dataTypes[ 0 ] === "*" ) { - dataTypes.shift(); - if ( ct === undefined ) { - ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); - } - } - - // Check if we're dealing with a known content-type - if ( ct ) { - for ( type in contents ) { - if ( contents[ type ] && contents[ type ].test( ct ) ) { - dataTypes.unshift( type ); - break; - } - } - } - - // Check to see if we have a response for the expected dataType - if ( dataTypes[ 0 ] in responses ) { - finalDataType = dataTypes[ 0 ]; - } else { - - // Try convertible dataTypes - for ( type in responses ) { - if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { - finalDataType = type; - break; - } - if ( !firstDataType ) { - firstDataType = type; - } - } - - // Or just use first one - finalDataType = finalDataType || firstDataType; - } - - // If we found a dataType - // We add the dataType to the list if needed - // and return the corresponding response - if ( finalDataType ) { - if ( finalDataType !== dataTypes[ 0 ] ) { - dataTypes.unshift( finalDataType ); - } - return responses[ finalDataType ]; - } -} - -/* Chain conversions given the request and the original response - * Also sets the responseXXX fields on the jqXHR instance - */ -function ajaxConvert( s, response, jqXHR, isSuccess ) { - var conv2, current, conv, tmp, prev, - converters = {}, - - // Work with a copy of dataTypes in case we need to modify it for conversion - dataTypes = s.dataTypes.slice(); - - // Create converters map with lowercased keys - if ( dataTypes[ 1 ] ) { - for ( conv in s.converters ) { - converters[ conv.toLowerCase() ] = s.converters[ conv ]; - } - } - - current = dataTypes.shift(); - - // Convert to each sequential dataType - while ( current ) { - - if ( s.responseFields[ current ] ) { - jqXHR[ s.responseFields[ current ] ] = response; - } - - // Apply the dataFilter if provided - if ( !prev && isSuccess && s.dataFilter ) { - response = s.dataFilter( response, s.dataType ); - } - - prev = current; - current = dataTypes.shift(); - - if ( current ) { - - // There's only work to do if current dataType is non-auto - if ( current === "*" ) { - - current = prev; - - // Convert response if prev dataType is non-auto and differs from current - } else if ( prev !== "*" && prev !== current ) { - - // Seek a direct converter - conv = converters[ prev + " " + current ] || converters[ "* " + current ]; - - // If none found, seek a pair - if ( !conv ) { - for ( conv2 in converters ) { - - // If conv2 outputs current - tmp = conv2.split( " " ); - if ( tmp[ 1 ] === current ) { - - // If prev can be converted to accepted input - conv = converters[ prev + " " + tmp[ 0 ] ] || - converters[ "* " + tmp[ 0 ] ]; - if ( conv ) { - - // Condense equivalence converters - if ( conv === true ) { - conv = converters[ conv2 ]; - - // Otherwise, insert the intermediate dataType - } else if ( converters[ conv2 ] !== true ) { - current = tmp[ 0 ]; - dataTypes.unshift( tmp[ 1 ] ); - } - break; - } - } - } - } - - // Apply converter (if not an equivalence) - if ( conv !== true ) { - - // Unless errors are allowed to bubble, catch and return them - if ( conv && s.throws ) { - response = conv( response ); - } else { - try { - response = conv( response ); - } catch ( e ) { - return { - state: "parsererror", - error: conv ? e : "No conversion from " + prev + " to " + current - }; - } - } - } - } - } - } - - return { state: "success", data: response }; -} - -jQuery.extend( { - - // Counter for holding the number of active queries - active: 0, - - // Last-Modified header cache for next request - lastModified: {}, - etag: {}, - - ajaxSettings: { - url: location.href, - type: "GET", - isLocal: rlocalProtocol.test( location.protocol ), - global: true, - processData: true, - async: true, - contentType: "application/x-www-form-urlencoded; charset=UTF-8", - - /* - timeout: 0, - data: null, - dataType: null, - username: null, - password: null, - cache: null, - throws: false, - traditional: false, - headers: {}, - */ - - accepts: { - "*": allTypes, - text: "text/plain", - html: "text/html", - xml: "application/xml, text/xml", - json: "application/json, text/javascript" - }, - - contents: { - xml: /\bxml\b/, - html: /\bhtml/, - json: /\bjson\b/ - }, - - responseFields: { - xml: "responseXML", - text: "responseText", - json: "responseJSON" - }, - - // Data converters - // Keys separate source (or catchall "*") and destination types with a single space - converters: { - - // Convert anything to text - "* text": String, - - // Text to html (true = no transformation) - "text html": true, - - // Evaluate text as a json expression - "text json": JSON.parse, - - // Parse text as xml - "text xml": jQuery.parseXML - }, - - // For options that shouldn't be deep extended: - // you can add your own custom options here if - // and when you create one that shouldn't be - // deep extended (see ajaxExtend) - flatOptions: { - url: true, - context: true - } - }, - - // Creates a full fledged settings object into target - // with both ajaxSettings and settings fields. - // If target is omitted, writes into ajaxSettings. - ajaxSetup: function( target, settings ) { - return settings ? - - // Building a settings object - ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : - - // Extending ajaxSettings - ajaxExtend( jQuery.ajaxSettings, target ); - }, - - ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), - ajaxTransport: addToPrefiltersOrTransports( transports ), - - // Main method - ajax: function( url, options ) { - - // If url is an object, simulate pre-1.5 signature - if ( typeof url === "object" ) { - options = url; - url = undefined; - } - - // Force options to be an object - options = options || {}; - - var transport, - - // URL without anti-cache param - cacheURL, - - // Response headers - responseHeadersString, - responseHeaders, - - // timeout handle - timeoutTimer, - - // Url cleanup var - urlAnchor, - - // Request state (becomes false upon send and true upon completion) - completed, - - // To know if global events are to be dispatched - fireGlobals, - - // Loop variable - i, - - // uncached part of the url - uncached, - - // Create the final options object - s = jQuery.ajaxSetup( {}, options ), - - // Callbacks context - callbackContext = s.context || s, - - // Context for global events is callbackContext if it is a DOM node or jQuery collection - globalEventContext = s.context && - ( callbackContext.nodeType || callbackContext.jquery ) ? - jQuery( callbackContext ) : - jQuery.event, - - // Deferreds - deferred = jQuery.Deferred(), - completeDeferred = jQuery.Callbacks( "once memory" ), - - // Status-dependent callbacks - statusCode = s.statusCode || {}, - - // Headers (they are sent all at once) - requestHeaders = {}, - requestHeadersNames = {}, - - // Default abort message - strAbort = "canceled", - - // Fake xhr - jqXHR = { - readyState: 0, - - // Builds headers hashtable if needed - getResponseHeader: function( key ) { - var match; - if ( completed ) { - if ( !responseHeaders ) { - responseHeaders = {}; - while ( ( match = rheaders.exec( responseHeadersString ) ) ) { - responseHeaders[ match[ 1 ].toLowerCase() + " " ] = - ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) - .concat( match[ 2 ] ); - } - } - match = responseHeaders[ key.toLowerCase() + " " ]; - } - return match == null ? null : match.join( ", " ); - }, - - // Raw string - getAllResponseHeaders: function() { - return completed ? responseHeadersString : null; - }, - - // Caches the header - setRequestHeader: function( name, value ) { - if ( completed == null ) { - name = requestHeadersNames[ name.toLowerCase() ] = - requestHeadersNames[ name.toLowerCase() ] || name; - requestHeaders[ name ] = value; - } - return this; - }, - - // Overrides response content-type header - overrideMimeType: function( type ) { - if ( completed == null ) { - s.mimeType = type; - } - return this; - }, - - // Status-dependent callbacks - statusCode: function( map ) { - var code; - if ( map ) { - if ( completed ) { - - // Execute the appropriate callbacks - jqXHR.always( map[ jqXHR.status ] ); - } else { - - // Lazy-add the new callbacks in a way that preserves old ones - for ( code in map ) { - statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; - } - } - } - return this; - }, - - // Cancel the request - abort: function( statusText ) { - var finalText = statusText || strAbort; - if ( transport ) { - transport.abort( finalText ); - } - done( 0, finalText ); - return this; - } - }; - - // Attach deferreds - deferred.promise( jqXHR ); - - // Add protocol if not provided (prefilters might expect it) - // Handle falsy url in the settings object (#10093: consistency with old signature) - // We also use the url parameter if available - s.url = ( ( url || s.url || location.href ) + "" ) - .replace( rprotocol, location.protocol + "//" ); - - // Alias method option to type as per ticket #12004 - s.type = options.method || options.type || s.method || s.type; - - // Extract dataTypes list - s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; - - // A cross-domain request is in order when the origin doesn't match the current origin. - if ( s.crossDomain == null ) { - urlAnchor = document.createElement( "a" ); - - // Support: IE <=8 - 11, Edge 12 - 15 - // IE throws exception on accessing the href property if url is malformed, - // e.g. http://example.com:80x/ - try { - urlAnchor.href = s.url; - - // Support: IE <=8 - 11 only - // Anchor's host property isn't correctly set when s.url is relative - urlAnchor.href = urlAnchor.href; - s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== - urlAnchor.protocol + "//" + urlAnchor.host; - } catch ( e ) { - - // If there is an error parsing the URL, assume it is crossDomain, - // it can be rejected by the transport if it is invalid - s.crossDomain = true; - } - } - - // Convert data if not already a string - if ( s.data && s.processData && typeof s.data !== "string" ) { - s.data = jQuery.param( s.data, s.traditional ); - } - - // Apply prefilters - inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); - - // If request was aborted inside a prefilter, stop there - if ( completed ) { - return jqXHR; - } - - // We can fire global events as of now if asked to - // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) - fireGlobals = jQuery.event && s.global; - - // Watch for a new set of requests - if ( fireGlobals && jQuery.active++ === 0 ) { - jQuery.event.trigger( "ajaxStart" ); - } - - // Uppercase the type - s.type = s.type.toUpperCase(); - - // Determine if request has content - s.hasContent = !rnoContent.test( s.type ); - - // Save the URL in case we're toying with the If-Modified-Since - // and/or If-None-Match header later on - // Remove hash to simplify url manipulation - cacheURL = s.url.replace( rhash, "" ); - - // More options handling for requests with no content - if ( !s.hasContent ) { - - // Remember the hash so we can put it back - uncached = s.url.slice( cacheURL.length ); - - // If data is available and should be processed, append data to url - if ( s.data && ( s.processData || typeof s.data === "string" ) ) { - cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; - - // #9682: remove data so that it's not used in an eventual retry - delete s.data; - } - - // Add or update anti-cache param if needed - if ( s.cache === false ) { - cacheURL = cacheURL.replace( rantiCache, "$1" ); - uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) + - uncached; - } - - // Put hash and anti-cache on the URL that will be requested (gh-1732) - s.url = cacheURL + uncached; - - // Change '%20' to '+' if this is encoded form body content (gh-2658) - } else if ( s.data && s.processData && - ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { - s.data = s.data.replace( r20, "+" ); - } - - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - if ( jQuery.lastModified[ cacheURL ] ) { - jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); - } - if ( jQuery.etag[ cacheURL ] ) { - jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); - } - } - - // Set the correct header, if data is being sent - if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { - jqXHR.setRequestHeader( "Content-Type", s.contentType ); - } - - // Set the Accepts header for the server, depending on the dataType - jqXHR.setRequestHeader( - "Accept", - s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? - s.accepts[ s.dataTypes[ 0 ] ] + - ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : - s.accepts[ "*" ] - ); - - // Check for headers option - for ( i in s.headers ) { - jqXHR.setRequestHeader( i, s.headers[ i ] ); - } - - // Allow custom headers/mimetypes and early abort - if ( s.beforeSend && - ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { - - // Abort if not done already and return - return jqXHR.abort(); - } - - // Aborting is no longer a cancellation - strAbort = "abort"; - - // Install callbacks on deferreds - completeDeferred.add( s.complete ); - jqXHR.done( s.success ); - jqXHR.fail( s.error ); - - // Get transport - transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); - - // If no transport, we auto-abort - if ( !transport ) { - done( -1, "No Transport" ); - } else { - jqXHR.readyState = 1; - - // Send global event - if ( fireGlobals ) { - globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); - } - - // If request was aborted inside ajaxSend, stop there - if ( completed ) { - return jqXHR; - } - - // Timeout - if ( s.async && s.timeout > 0 ) { - timeoutTimer = window.setTimeout( function() { - jqXHR.abort( "timeout" ); - }, s.timeout ); - } - - try { - completed = false; - transport.send( requestHeaders, done ); - } catch ( e ) { - - // Rethrow post-completion exceptions - if ( completed ) { - throw e; - } - - // Propagate others as results - done( -1, e ); - } - } - - // Callback for when everything is done - function done( status, nativeStatusText, responses, headers ) { - var isSuccess, success, error, response, modified, - statusText = nativeStatusText; - - // Ignore repeat invocations - if ( completed ) { - return; - } - - completed = true; - - // Clear timeout if it exists - if ( timeoutTimer ) { - window.clearTimeout( timeoutTimer ); - } - - // Dereference transport for early garbage collection - // (no matter how long the jqXHR object will be used) - transport = undefined; - - // Cache response headers - responseHeadersString = headers || ""; - - // Set readyState - jqXHR.readyState = status > 0 ? 4 : 0; - - // Determine if successful - isSuccess = status >= 200 && status < 300 || status === 304; - - // Get response data - if ( responses ) { - response = ajaxHandleResponses( s, jqXHR, responses ); - } - - // Use a noop converter for missing script but not if jsonp - if ( !isSuccess && - jQuery.inArray( "script", s.dataTypes ) > -1 && - jQuery.inArray( "json", s.dataTypes ) < 0 ) { - s.converters[ "text script" ] = function() {}; - } - - // Convert no matter what (that way responseXXX fields are always set) - response = ajaxConvert( s, response, jqXHR, isSuccess ); - - // If successful, handle type chaining - if ( isSuccess ) { - - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - modified = jqXHR.getResponseHeader( "Last-Modified" ); - if ( modified ) { - jQuery.lastModified[ cacheURL ] = modified; - } - modified = jqXHR.getResponseHeader( "etag" ); - if ( modified ) { - jQuery.etag[ cacheURL ] = modified; - } - } - - // if no content - if ( status === 204 || s.type === "HEAD" ) { - statusText = "nocontent"; - - // if not modified - } else if ( status === 304 ) { - statusText = "notmodified"; - - // If we have data, let's convert it - } else { - statusText = response.state; - success = response.data; - error = response.error; - isSuccess = !error; - } - } else { - - // Extract error from statusText and normalize for non-aborts - error = statusText; - if ( status || !statusText ) { - statusText = "error"; - if ( status < 0 ) { - status = 0; - } - } - } - - // Set data for the fake xhr object - jqXHR.status = status; - jqXHR.statusText = ( nativeStatusText || statusText ) + ""; - - // Success/Error - if ( isSuccess ) { - deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); - } else { - deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); - } - - // Status-dependent callbacks - jqXHR.statusCode( statusCode ); - statusCode = undefined; - - if ( fireGlobals ) { - globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", - [ jqXHR, s, isSuccess ? success : error ] ); - } - - // Complete - completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); - - if ( fireGlobals ) { - globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); - - // Handle the global AJAX counter - if ( !( --jQuery.active ) ) { - jQuery.event.trigger( "ajaxStop" ); - } - } - } - - return jqXHR; - }, - - getJSON: function( url, data, callback ) { - return jQuery.get( url, data, callback, "json" ); - }, - - getScript: function( url, callback ) { - return jQuery.get( url, undefined, callback, "script" ); - } -} ); - -jQuery.each( [ "get", "post" ], function( _i, method ) { - jQuery[ method ] = function( url, data, callback, type ) { - - // Shift arguments if data argument was omitted - if ( isFunction( data ) ) { - type = type || callback; - callback = data; - data = undefined; - } - - // The url can be an options object (which then must have .url) - return jQuery.ajax( jQuery.extend( { - url: url, - type: method, - dataType: type, - data: data, - success: callback - }, jQuery.isPlainObject( url ) && url ) ); - }; -} ); - -jQuery.ajaxPrefilter( function( s ) { - var i; - for ( i in s.headers ) { - if ( i.toLowerCase() === "content-type" ) { - s.contentType = s.headers[ i ] || ""; - } - } -} ); - - -jQuery._evalUrl = function( url, options, doc ) { - return jQuery.ajax( { - url: url, - - // Make this explicit, since user can override this through ajaxSetup (#11264) - type: "GET", - dataType: "script", - cache: true, - async: false, - global: false, - - // Only evaluate the response if it is successful (gh-4126) - // dataFilter is not invoked for failure responses, so using it instead - // of the default converter is kludgy but it works. - converters: { - "text script": function() {} - }, - dataFilter: function( response ) { - jQuery.globalEval( response, options, doc ); - } - } ); -}; - - -jQuery.fn.extend( { - wrapAll: function( html ) { - var wrap; - - if ( this[ 0 ] ) { - if ( isFunction( html ) ) { - html = html.call( this[ 0 ] ); - } - - // The elements to wrap the target around - wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); - - if ( this[ 0 ].parentNode ) { - wrap.insertBefore( this[ 0 ] ); - } - - wrap.map( function() { - var elem = this; - - while ( elem.firstElementChild ) { - elem = elem.firstElementChild; - } - - return elem; - } ).append( this ); - } - - return this; - }, - - wrapInner: function( html ) { - if ( isFunction( html ) ) { - return this.each( function( i ) { - jQuery( this ).wrapInner( html.call( this, i ) ); - } ); - } - - return this.each( function() { - var self = jQuery( this ), - contents = self.contents(); - - if ( contents.length ) { - contents.wrapAll( html ); - - } else { - self.append( html ); - } - } ); - }, - - wrap: function( html ) { - var htmlIsFunction = isFunction( html ); - - return this.each( function( i ) { - jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); - } ); - }, - - unwrap: function( selector ) { - this.parent( selector ).not( "body" ).each( function() { - jQuery( this ).replaceWith( this.childNodes ); - } ); - return this; - } -} ); - - -jQuery.expr.pseudos.hidden = function( elem ) { - return !jQuery.expr.pseudos.visible( elem ); -}; -jQuery.expr.pseudos.visible = function( elem ) { - return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); -}; - - - - -jQuery.ajaxSettings.xhr = function() { - try { - return new window.XMLHttpRequest(); - } catch ( e ) {} -}; - -var xhrSuccessStatus = { - - // File protocol always yields status code 0, assume 200 - 0: 200, - - // Support: IE <=9 only - // #1450: sometimes IE returns 1223 when it should be 204 - 1223: 204 - }, - xhrSupported = jQuery.ajaxSettings.xhr(); - -support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); -support.ajax = xhrSupported = !!xhrSupported; - -jQuery.ajaxTransport( function( options ) { - var callback, errorCallback; - - // Cross domain only allowed if supported through XMLHttpRequest - if ( support.cors || xhrSupported && !options.crossDomain ) { - return { - send: function( headers, complete ) { - var i, - xhr = options.xhr(); - - xhr.open( - options.type, - options.url, - options.async, - options.username, - options.password - ); - - // Apply custom fields if provided - if ( options.xhrFields ) { - for ( i in options.xhrFields ) { - xhr[ i ] = options.xhrFields[ i ]; - } - } - - // Override mime type if needed - if ( options.mimeType && xhr.overrideMimeType ) { - xhr.overrideMimeType( options.mimeType ); - } - - // X-Requested-With header - // For cross-domain requests, seeing as conditions for a preflight are - // akin to a jigsaw puzzle, we simply never set it to be sure. - // (it can always be set on a per-request basis or even using ajaxSetup) - // For same-domain requests, won't change header if already provided. - if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { - headers[ "X-Requested-With" ] = "XMLHttpRequest"; - } - - // Set headers - for ( i in headers ) { - xhr.setRequestHeader( i, headers[ i ] ); - } - - // Callback - callback = function( type ) { - return function() { - if ( callback ) { - callback = errorCallback = xhr.onload = - xhr.onerror = xhr.onabort = xhr.ontimeout = - xhr.onreadystatechange = null; - - if ( type === "abort" ) { - xhr.abort(); - } else if ( type === "error" ) { - - // Support: IE <=9 only - // On a manual native abort, IE9 throws - // errors on any property access that is not readyState - if ( typeof xhr.status !== "number" ) { - complete( 0, "error" ); - } else { - complete( - - // File: protocol always yields status 0; see #8605, #14207 - xhr.status, - xhr.statusText - ); - } - } else { - complete( - xhrSuccessStatus[ xhr.status ] || xhr.status, - xhr.statusText, - - // Support: IE <=9 only - // IE9 has no XHR2 but throws on binary (trac-11426) - // For XHR2 non-text, let the caller handle it (gh-2498) - ( xhr.responseType || "text" ) !== "text" || - typeof xhr.responseText !== "string" ? - { binary: xhr.response } : - { text: xhr.responseText }, - xhr.getAllResponseHeaders() - ); - } - } - }; - }; - - // Listen to events - xhr.onload = callback(); - errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); - - // Support: IE 9 only - // Use onreadystatechange to replace onabort - // to handle uncaught aborts - if ( xhr.onabort !== undefined ) { - xhr.onabort = errorCallback; - } else { - xhr.onreadystatechange = function() { - - // Check readyState before timeout as it changes - if ( xhr.readyState === 4 ) { - - // Allow onerror to be called first, - // but that will not handle a native abort - // Also, save errorCallback to a variable - // as xhr.onerror cannot be accessed - window.setTimeout( function() { - if ( callback ) { - errorCallback(); - } - } ); - } - }; - } - - // Create the abort callback - callback = callback( "abort" ); - - try { - - // Do send the request (this may raise an exception) - xhr.send( options.hasContent && options.data || null ); - } catch ( e ) { - - // #14683: Only rethrow if this hasn't been notified as an error yet - if ( callback ) { - throw e; - } - } - }, - - abort: function() { - if ( callback ) { - callback(); - } - } - }; - } -} ); - - - - -// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) -jQuery.ajaxPrefilter( function( s ) { - if ( s.crossDomain ) { - s.contents.script = false; - } -} ); - -// Install script dataType -jQuery.ajaxSetup( { - accepts: { - script: "text/javascript, application/javascript, " + - "application/ecmascript, application/x-ecmascript" - }, - contents: { - script: /\b(?:java|ecma)script\b/ - }, - converters: { - "text script": function( text ) { - jQuery.globalEval( text ); - return text; - } - } -} ); - -// Handle cache's special case and crossDomain -jQuery.ajaxPrefilter( "script", function( s ) { - if ( s.cache === undefined ) { - s.cache = false; - } - if ( s.crossDomain ) { - s.type = "GET"; - } -} ); - -// Bind script tag hack transport -jQuery.ajaxTransport( "script", function( s ) { - - // This transport only deals with cross domain or forced-by-attrs requests - if ( s.crossDomain || s.scriptAttrs ) { - var script, callback; - return { - send: function( _, complete ) { - script = jQuery( "