commit 1fe72d15c7161ccc3c05bb70b940f02f8ce1e8a7 Author: Niko Reunanen Date: Wed Apr 9 11:12:51 2025 +0300 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9a3fed5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.DS_Store +*.swp +public diff --git a/archetypes/default.md b/archetypes/default.md new file mode 100644 index 0000000..00e77bd --- /dev/null +++ b/archetypes/default.md @@ -0,0 +1,6 @@ +--- +title: "{{ replace .Name "-" " " | title }}" +date: {{ .Date }} +draft: true +--- + diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000..4648439 --- /dev/null +++ b/config.yaml @@ -0,0 +1,80 @@ +baseURL: https://nikoreunanen.com +languageCode: en-us +defaultContentLanguage: en +title: Cognitive Digressions +theme: cupper-hugo-theme +# enableGitInfo: true +googleAnalytics: true + +taxonomies: + tag: tags + +permalinks: + post: /:filename/ + +imaging: + quality: 99 + +params: + description: Inner monologue or just hearing voices? + homeMetaContent: Cognitive Digressions is a repository of half-digested ideas + footer: All expressed views on this site are my own and do not represent the opinions of any entity whatsoever with which I have been, am now, or will be affiliated. + dateFormat: Jan 2, 2006 + katex: true + hideHeaderLinks: false + search: true + showThemeSwitcher: true + moveFooterToHeader: false + customCss: + - css/basic_highlight.css + customJs: + - js/basic_highlight.js + +menu: + nav: + - name: About + url: / + weight: 1 + - name: Posts + url: /post/ + weight: 2 + - name: LinkedIn + url: https://www.linkedin.com/in/nikoreunanen/ + weight: 3 + - name: Contracting + url: https://ntsol.io + weight: 4 + - name: RSS + url: /index.xml + weight: 5 + +markup: + defaultMarkdownHandler: goldmark + goldmark: + extensions: + definitionList: true + footnote: true + linkify: true + strikethrough: true + table: true + taskList: true + typographer: true + parser: + attribute: true + autoHeadingID: true + renderer: + hardWraps: false + unsafe: true + xHTML: false + highlight: + codeFences: false + hl_lines: "" + lineNoStart: 1 + lineNos: false + lineNumbersInTable: true + noClasses: true + style: monokai + tabWidth: 4 + tableOfContents: + endLevel: 6 + startLevel: 2 diff --git a/content/_index.md b/content/_index.md new file mode 100644 index 0000000..a732f9e --- /dev/null +++ b/content/_index.md @@ -0,0 +1,11 @@ +--- +title: "Cognitive Digressions" +date: 2021-01-21 +draft: false +--- + +Welcome to a repository of half-digested ideas, partially explored thoughts and other cognitive digressions. A mental scratchpad of sorts. + +Personal views: [absurdism](https://en.wikipedia.org/wiki/Absurdism), cats are awesome, coding is creative, anything can be interesting, boring kills businesses, music creates the feelings in a movie, it is not good to spend time with copies of yourself, the division between qualitative and quantitative research is artificial. + +Don't swallow information without chewing it. Especially online. All expressed views on this site are my own and do not represent the opinions of any entity whatsoever with which I have been, am now, or will be affiliated. diff --git a/content/post/augmenting-humans-with-machine-learning.md b/content/post/augmenting-humans-with-machine-learning.md new file mode 100644 index 0000000..b183769 --- /dev/null +++ b/content/post/augmenting-humans-with-machine-learning.md @@ -0,0 +1,7 @@ +--- +title: "Augmenting Humans With Machine Learning" +date: 2021-01-19T01:58:16+02:00 +draft: true +--- + +Why to replace them? Why to fight windmills? diff --git a/content/post/cell-division-as-objective.md b/content/post/cell-division-as-objective.md new file mode 100644 index 0000000..b20b809 --- /dev/null +++ b/content/post/cell-division-as-objective.md @@ -0,0 +1,6 @@ +--- +title: "Cell Division as Objective" +date: 2021-01-23T22:09:39+02:00 +draft: true +--- + diff --git a/content/post/combine-django-vue-vuetify-typescript.md b/content/post/combine-django-vue-vuetify-typescript.md new file mode 100644 index 0000000..bfca963 --- /dev/null +++ b/content/post/combine-django-vue-vuetify-typescript.md @@ -0,0 +1,373 @@ +--- +title: "Combine Django, Vue.js, Vuetify and TypeScript" +date: 2021-06-27T09:18:41+03:00 +draft: false +--- + +Let's combine Django, Vue.js, Vuetify and TypeScript into a complete toolchain for your next web project πŸš€πŸš€ + +Benefits: + +* Automatic reloading of front-end (TypeScript, Vue.js, Vuetify) and back-end (Python, Django) during development for quick code iterations +* [Django](https://www.djangoproject.com/) got you covered for web development, from excellent database tools to user authentication, and everything between +* [Vue.js](https://vuejs.org/) makes it easy to write interactive user interfaces +* [Vuetify](https://vuetifyjs.com/en/) gives you a toolbox of graphical user interface components that follow the Material Design-framework +* [TypeScript](https://www.typescriptlang.org/) extends JavaScript with typing to catch JavaScript errors before running the code +* You'll have a self-contained application to deploy in production with static assets. No separate front-end project. + +Notes: + +* You need to have Python β‰₯ 3.6 and Node.js installed +* We'll use Vue.js 2.x since Vue.js 3 is still in alpha +* You can use [Flask](https://flask.palletsprojects.com/en/2.0.x/) or other Python web frameworks instead of Django. Actually, you could use any web framework, including [Actix Web](https://actix.rs/) for Rust, since the same principles apply for connecting the front-end with the back-end. +* This tutorial uses [whitenoise](http://whitenoise.evans.io/en/stable/) with Django to serve static assets in production builds. You might want to switch to a [CDN](https://en.wikipedia.org/wiki/Content_delivery_network) if you have a high-traffic system. You just need to configure the *DEPLOYMENT_PATH* variable in *vue.config.js* to match your CDN URL addresses. + +Alright, let's go. First we need the official Vue.js standard tooling and to create a tutorial project folder. + +```bash +# Install the official Vue.js command line tool +npm install -g @vue/cli + +# Create a project folder +mkdir tutorial +``` + +## Create Vue.js front-end + +Create a new Vue.js 2.x project. +```bash +# current directory: tutorial + +# Create a new Vue.js 2.x project in tutorial/client +vue create client + +# Choose "Manually select features" +# Select TypeScript with spacebar +# Press enter +# Choose Vue version 2.x +# Use class-style component syntax?: n +# Use Babel alongside TypeScript...: Y +# Linter: ESLint with error prevention only +# Pick additional lint features: Lint on save +# Where do you prefer...: In dedicated config files +# Save this as a preset for future projects?: N + +# Install Vuetify +cd client +vue add vuetify # Choose the default option on prompt + +# Tracker to connect Django and webpack bundles +npm install -D webpack-bundle-tracker +``` + +Replace the existing content in `tutorial/client/vue.config.js` with the following: +```js +const BundleTracker = require("webpack-bundle-tracker"); + +const DEPLOYMENT_PATH = '/static/' + +module.exports = { + runtimeCompiler: true, + pages: { + index: { + entry: "./src/main.ts", + chunks: ["chunk-vendors"] + } + }, + publicPath: process.env.PROJECT_MODE === 'production' ? DEPLOYMENT_PATH : 'http://localhost:8080/', + outputDir: "../server/example/static/", + chainWebpack: config => { + config.optimization.splitChunks(false) + config.plugin('BundleTracker').use(BundleTracker, [{filename: '../server/webpack-stats.json'}]) + config.resolve.alias.set('__STATIC__', 'static') + config.devServer + .public('http://0.0.0.0:8080') + .host('0.0.0.0') + .port(8080) + .hotOnly(true) + .watchOptions({poll: 1000}) + .https(false) + .headers({"Access-Control-Allow-Origin": ["*"]}) + }, + transpileDependencies: ["vuetify"] +} +``` + +We will create the server Django project and the example app very soon. The above `vue.config.js` does the following: + +* The front-end is served from http://localhost:8080 during development (PROJECT\_MODE=development) +* Production version (PROJECT\_MODE=production) of the front-end is written into `tutorial/server/example/static` as a static build (`outputDir`). We'll create a Django app called example, which finds these static files with default configuration. +* `webpack-stats.json` informs Django how to access the front-end assets during development and production +* The front-end reloads automatically after changes +* The resulting bundle is named *index*. Notice that you could output multiple bundles with different TypeScript entry scripts. + +## Create Django back-end + +Install Django for back-end. + +```bash +# current directory: tutorial + +# Python virtual environment at tutorial/local/venv +python3 -m venv local/venv +source local/venv/bin/activate + +# Upgrade the installation tools +pip install --upgrade setuptools pip wheel + +# Install Django, whitenoise is for serving static assets +pip install Django whitenoise + +# Create a Django project in tutorial/server +django-admin startproject server + +# Create a new Django app for your project +cd server +python manage.py startapp example +``` + +Now let's register the new Django app by modifying the existing Django project settings `tutorial/server/server/settings.py`: +```python +# Add the following at the beginning of the file: +import os + +# Add the following at the end of the file: +STATS_FILE = os.path.join(BASE_DIR, 'webpack-stats.json') + +# Add the following in the INSTALLED_APPS list: +'example.apps.ExampleConfig' + +# Add the following in the MIDDLEWARE list +# below 'django.middleware.security.SecurityMiddleware' +# see http://whitenoise.evans.io/en/stable/ +'whitenoise.middleware.WhiteNoiseMiddleware' +``` + +Now let's route the views from the example app in the root project. Replace the existing content in `tutorial/server/server/urls.py` with the following: +```python +from django.contrib import admin +from django.urls import path, include + + +urlpatterns = [ + path('', include('example.urls')), + path('admin/', admin.site.urls) +] +``` + +We'll create register an index page in the example app. It will serve the automatically generated Vue.js and Vuetify example page through Django. Write the following in a new file `tutorial/server/example/urls.py` +```python +from django.urls import path + +from . import views + + +urlpatterns = [ + path('', views.index, name='index') +] +``` + +Finally it's time to create the actual view that we already registered previously. +Replace the existing content in `tutorial/server/example/views.py` with the following: +```python +import json +import pathlib + +from django.shortcuts import render +from django.http import HttpResponse +from django.conf import settings + + +class WebpackStatsProcessing: + def __init__(self): + self.data = json.loads(pathlib.Path(settings.STATS_FILE).read_text('utf-8')) + + def get_tags(self, bundle: str) -> str: + include_tags = [] + for asset in self.data['chunks'][bundle]: + if asset.endswith('.map'): + continue + + location = self.data['assets'][asset]['publicPath'] + if asset.endswith(('.js', 'js.gz')): + include_tags.append(f'') + include_tags.append(f'') + if asset.endswith(('.css', '.css.gz')): + include_tags.append(f'') + return '\n'.join(include_tags) + + +webpack_stats_tracker = WebpackStatsProcessing() + + +def index(request): + return HttpResponse(render(request, 'example/vuetify_bundle.html', context={ + 'bundle': webpack_stats_tracker.get_tags('index'), + 'title': 'Index page from Example Django app' + })) +``` + +The class *WebpackStatsProcessing* is an example on how to parse *webpack-stats.json* to load a webpack bundle (Vue.js, Vuetify, ...) and output the required script and style tags for Django HTML templates. As you see in the *index* view, the *bundle* context parameter contains the required tags. The file *webpack-stats.json* routes the assets through the development front-end server during development and a static asset build in a production. + +The only thing missing anymore is the actual Django HTML template that is rendered by the *index* view. First, create a new folder `tutorial/server/example/templates/example` and then write the following content in a new file `tutorial/server/example/templates/example/vuetify_bundle.html` + +```html + + + + + + + + {{ title }} + + + + +
+ + {{ bundle|safe }} + + +``` + +## Run development environment + +Now we are ready to run the development environment. You need two terminals at the same time: + +Terminal 1 (front-end): +```bash +# current directory: tutorial/client +npm run serve + +# Wait for the code compilation to finish because +# it will write the webpack-stats.json +``` + +Terminal 2 (back-end): +```bash +# current directory: tutorial +source local/venv/bin/activate +cd server +python manage.py runserver + +# You can ignore any database migration errors since +# this tutorial doesn't use database models +``` + +Open [http://localhost:8000](http://localhost:8000) and hopefully you'll see the front-end being served through Django with automatic reloads for front-end and back-end. For example, edit `tutorial/client/src/components/HelloWorld.vue`, save changes and the front-end should update automatically. Cool. Check the page source and you should see that the assets are requested from http://localhost:8080, which is the address configured in `vue.config.js`. This is possible because `webpack-stats.json` instructs Django to use the http://localhost:8080 during development. + +Your tutorial file structure should look something like this: +``` +tutorial +β”œβ”€β”€ client +β”‚Β Β  β”œβ”€β”€ README.md +β”‚Β Β  β”œβ”€β”€ babel.config.js +β”‚Β Β  β”œβ”€β”€ node_modules +β”‚Β Β  β”œβ”€β”€ package-lock.json +β”‚Β Β  β”œβ”€β”€ package.json +β”‚Β Β  β”œβ”€β”€ public +β”‚Β Β  β”‚Β Β  β”œβ”€β”€ favicon.ico +β”‚Β Β  β”‚Β Β  └── index.html +β”‚Β Β  β”œβ”€β”€ src +β”‚Β Β  β”‚Β Β  β”œβ”€β”€ App.vue +β”‚Β Β  β”‚Β Β  β”œβ”€β”€ assets +β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ logo.png +β”‚Β Β  β”‚Β Β  β”‚Β Β  └── logo.svg +β”‚Β Β  β”‚Β Β  β”œβ”€β”€ components +β”‚Β Β  β”‚Β Β  β”‚Β Β  └── HelloWorld.vue +β”‚Β Β  β”‚Β Β  β”œβ”€β”€ main.ts +β”‚Β Β  β”‚Β Β  β”œβ”€β”€ plugins +β”‚Β Β  β”‚Β Β  β”‚Β Β  └── vuetify.ts +β”‚Β Β  β”‚Β Β  β”œβ”€β”€ shims-tsx.d.ts +β”‚Β Β  β”‚Β Β  β”œβ”€β”€ shims-vue.d.ts +β”‚Β Β  β”‚Β Β  └── shims-vuetify.d.ts +β”‚Β Β  β”œβ”€β”€ tsconfig.json +β”‚Β Β  └── vue.config.js +β”œβ”€β”€ local +β”‚Β Β  └── venv +└── server + β”œβ”€β”€ db.sqlite3 + β”œβ”€β”€ example + β”‚Β Β  β”œβ”€β”€ __init__.py + β”‚Β Β  β”œβ”€β”€ admin.py + β”‚Β Β  β”œβ”€β”€ apps.py + β”‚Β Β  β”œβ”€β”€ migrations + β”‚Β Β  β”‚Β Β  └── __init__.py + β”‚Β Β  β”œβ”€β”€ models.py + β”‚Β Β  β”œβ”€β”€ templates + β”‚Β Β  β”‚Β Β  └── example + β”‚Β Β  β”‚Β Β  └── vuetify_bundle.html + β”‚Β Β  β”œβ”€β”€ tests.py + β”‚Β Β  β”œβ”€β”€ urls.py + β”‚Β Β  └── views.py + β”œβ”€β”€ manage.py + β”œβ”€β”€ server + β”‚Β Β  β”œβ”€β”€ __init__.py + β”‚Β Β  β”œβ”€β”€ asgi.py + β”‚Β Β  β”œβ”€β”€ settings.py + β”‚Β Β  β”œβ”€β”€ urls.py + β”‚Β Β  └── wsgi.py + └── webpack-stats.json +``` + +If you use Git for version control, then remember to put the following folders and files in .gitignore: +* node\_modules +* local +* static +* webpack-stats.json + +## Build a production version + +You might want to build a production version without the front-end development server so that only Django is used. Luckily it is very easy now: +```bash +# current directory: tutorial/client +PROJECT_MODE=production npm run build +``` + +Launch the Django development server again: +```bash +python manage.py runserver +```` + +Check the page source. You should see that the assets are Django static assets instead of the earlier origin http://localhost:8080. You could deploy the Django project this way if you don't mind the package *whitenoise* serving the static assets through Django and not an external CDN. + +One option is to use gunicorn to serve the Django WSGI app. You can build it as a Docker image and deploy the image in production (e.g. Cloud Run in Google Cloud Platform): +```docker +FROM python:3.9.5-slim + +ENV DEBIAN_FRONTEND noninteractive +ENV PYTHONUNBUFFERED True + +# No need to use a root account +RUN groupadd -g 61000 docker +RUN useradd -g 61000 -l -m -s /bin/false -u 61000 docker +USER docker + +COPY ./server /app +WORKDIR /app + +RUN pip install --upgrade --no-cache-dir pip wheel setuptools +RUN pip install --no-cache-dir Django whitenoise gunicorn + +# Add gunicorn to PATH +ENV PATH="/home/docker/.local/bin/:${PATH}" + +ENV MODE="production" + +CMD ["bash", "launch.sh"] +``` + +Write the following line in tutorial/server/launch.sh: +```bash +gunicorn server.wsgi:application --bind ":${PORT:-8000}" --workers 2 --threads 8 --timeout 0 +``` + +Done. + +## Conclusion + +There is still a lot of space for optimization, cleaning, configuration, making build sizes smaller and so on. However, this should get you started. Now you have Django, Vue.js, Vuetify and TypeScript living together. Happy coding! + diff --git a/content/post/data-science-tips-life-hacks.md b/content/post/data-science-tips-life-hacks.md new file mode 100644 index 0000000..67a1714 --- /dev/null +++ b/content/post/data-science-tips-life-hacks.md @@ -0,0 +1,65 @@ +--- +title: "Data Science Tips And Life Hacks" +date: 2021-01-30 +draft: false +--- + +Here are some tips and life hacks on how to survive data science. As they say, work smart, not hard. As a matter of fact, my favorite tasks are the tasks that I don't have to do. It is possible that task, as a word, is defined in some forgotten dictionary as something to avoid. Like, has anyone in the history of humankind used the word task in a positive context? Anyway, going off on a tangent. Let's get started. + +### Learn the domain where you work + +It is easy to acquire a pile of numbers and compute something. Just calculate the average value or apply a statistical model. However, to be useful, connect your work with the domain. What do your results mean? What is the actual insight within your results? What kind of actions can be taken based on the results? Are the results valid? Are the results significant? Is there anything fishy about the results? Are your statistical assumptions valid? How to define and measure success? Are [type I errors](https://en.wikipedia.org/wiki/Type_I_and_type_II_errors) more serious than [type II errors](https://en.wikipedia.org/wiki/Type_I_and_type_II_errors)? Do you need to explain the logic behind the results or is a black box sufficient? It is hard to make impact and be relevant inside a vacuum. + +### Are you solving the correct problem? + +Make sure that you are solving a valuable problem in the first place. Otherwise you are fighting against windmills, competing in a race that you can't win. The "why" is at least as important as the "how". + +{{< blockquote author="The author of this post" >}} +The value of the best solution for a wrong problem is a big number times zero +{{< /blockquote >}} + +Some people will have a hard time communicating with you or even understanding what you do. Your world can be very abstract to others. However, you still need to interact with them and figure out what they need. For example, you can create mockups of deliverables, from documents to user interfaces. This will help people to latch on something concrete and have a real, grounded basis for discussion. They can relatively easily tell if you are moving towards a good direction, and to help find one. The quality of feedback, interaction, requirements and extracted domain knowledge will skyrocket. + +### Understand people + +Humans are irrational and implicitly anthropomorphize everything. Like, "this restaurant makes excellent pasta" or "did you hear about the artificial intelligence that detects cats in images." However, organizations, businesses and companies are not living creatures. They consists of humans. Clients and customers are humans, even in business-to-business context. You operate in a sandbox filled to the brim with Homo sapientes. There is no escape. Soft skills are very important and required for efficient collaboration. Before you point your finger at me, I acknowledge that many people together form a system that exhibits its own behavior patterns, like how birds fly together or how atoms form solid matter together. + +Find your supporters in your organization, possibly the people who decided to hire you. Try to stay in friendly terms with them, remember their birthdays and so on. Supporters are your hedge against problems. Let's say that your employer experiences financial challenges. If decision makers in the organization like you, and they understand your value for the business, then you might not be the first one to let go. + +Try to get along with people, try to be like-able. Smile. Don't make your work life harder than it has to be. It sucks when you build an awesome, provenly beneficial system and no-one wants to use it. You effectively end up doing nothing. Zero impact and no business value. Therefore, it is of a paramount importance to obtain supporters, testers, collaborators, feedback givers, users and internal buy-in for your work. + +Data science is an abstract, hazy concept for many people. Just remember that they are not stupid or less intelligent than you. They are professionals in their own field. Who knows, maybe they are just not interested in data science. It is on you to help them understand your value. Never grow a unhealthy ego, no-one wants to feel stupid. The universe does not owe success and fame to you because you can implement the latest tricks in machine learning. Teach the intuition of relevant concepts in data science to your work buddies. Do not mind repeating the same things over and over again. Data science is a complex area and you can provide an easy, abstracted interface to utilize the power of data science. + +Empathy is not only for hippies. Empathy is seriously important and gives you tools to build a personal connection with the person in front of you. Step in her shoes. Understand her and think from her perspective. What motivates her? What makes her tick? What are her hobbies? What are her values? Now you can use familiar vocabulary, build analogies, frame impact into relevant outcomes and become interesting. You can help her to understand you, what you do and why your work is important. + +### Data? Evaluation metrics? + +You need to define what you are doing before acquiring data. If you have to produce insight or create something that works, practice designing experiments and to apply the scientific method. Put science in data science. For example, define hypotheses and how to measure effect sizes to reject or accept the hypotheses. + +Be careful about the measures of effect size, which are typically some evaluation metrics in machine learning context. For example, accuracy metric is a bad choice for data with class imbalance. Let's say that you are building a system to diagnose a disease where 0.1% of population has the disease. You get an expected accuracy of 99.9% simply by diagnosing everyone to not have the disease. You provide accurate diagnoses while denying treatment from a lot of people. + +Now you need to define the required data. You need to understand the business context and domain to figure out if the data exist, can be bought or if you have to collect it. Take the following dimensions into account: cost, time, effort, legislation and availability. + +### Don’t tune parameters forever + +Create a baseline result using a simple model that is easy to implement, understand and deploy in production. The baseline result is a measure stick to determine progress, and sometimes you'll end up noticing that the simple model is all you need. For example, use [Logistic Regression](/logistic-regression-is-worth-learning/). Set a time limit for experiments and be systematic with tracking your experiments. Sometimes you tilt, go into a parameter frenzy and will eventually repeat existing experiments. Don't tweak model parameters or hyperparameters for months if the incremental improvement is not justified by its business potential. Sometimes significant improvements are acquired by paradigm changes in model structure and using more accurate statistical assumptions through better understanding of the domain. + +### Feeling of loneliness and organizational maturity + +One of the common reasons for a data science person to quit is the feeling of loneliness. If you are looking for a new job, find out if the potential work places have peers and mentors available. Businesses that are not mature in data science might not be easy on your mental health unless you have a very entrepreneurial personality. Ask questions during the recruitment process to assess the maturity. For example, by probing how they approach model deployment. Remember that you can also find mentors and data friends outside your work environment. + +### Prepare for help requests and to turn down some of them + +If people find you useful, then they are likely to want your input and effort on many things. You might have to start turning some of them down to get your own work done. A data science manager can be helpful to have around since she can gate the incoming tickets on your behalf. She is someone who guards your time. A data science manager is probably found in organizations that are more mature in data science. + +### Never stop learning + +Learn to learn, and never stop, it will keep rewarding you. You will become much more productive if you have a wide skill set. Maybe even a tenfold increase in productivity. I personally like the idea of a T-shaped skill set where a person knows a lot about some topics and a bit about many topics. It is very empowering to be able to create a concrete prototype of any idea: app, website, machine learning algorithm and so on. + +Do not overfit your machine learning skills by being an expert in using only one specific method. Your favorite algorithm is not a hammer and not every problem is a nail. Have knowledge of many different methods, understand underlying theories and form mathematical connections between machine learning topics. Then you can design a suitable solution for a problem, and not the other way around. + +Learn basics of software development, deployment and some best practices. Write unit tests and integration tests, especially for feature extraction pipelines. Implement active learning for speeding up the model training and data annotation. Learn the basics of containers and cloud platforms. You'll learn to structure your software in a deployment-friendly manner, especially if someone else deploys it. This person will quickly understand how your software is used, she will save a lot of time and have a longer, happier life. + +### Math bugs are different than code bugs + +A compiler or an interpreter can catch problems in source code but you are on your own with problems in mathematics and logic. Your neural network will happily keep training using random, shuffled labels, even though it's not what you want. Code defensibly and do not trust the data that come in. Depending on the domain and context, it is sometimes better to just crash the program after a failed logic assertion. Review your code carefully. diff --git a/content/post/distance-metrics-refresher-pdf.md b/content/post/distance-metrics-refresher-pdf.md new file mode 100644 index 0000000..9717750 --- /dev/null +++ b/content/post/distance-metrics-refresher-pdf.md @@ -0,0 +1,10 @@ +--- +title: "[PDF] Distance Metrics Refresher" +date: 2021-01-21 +draft: false +--- + +[Here is a distance metric refresher for you as a PDF document](/pdf/distance_metrics_refresher.pdf). I wrote it in 2014 but the related mathematics have not changed over the years. Surprise. Enjoy. + +Peace and harmony. + diff --git a/content/post/logistic-regression-is-worth-learning.md b/content/post/logistic-regression-is-worth-learning.md new file mode 100644 index 0000000..b15409f --- /dev/null +++ b/content/post/logistic-regression-is-worth-learning.md @@ -0,0 +1,307 @@ +--- +title: "Logistic Regression is Worth Learning" +date: 2021-01-21 +draft: false +--- + +Logistic Regression (LR) is not a divine truth that exists as a [Platonic ideal](https://en.wikipedia.org/wiki/Platonic_idealism). It is possible to design LR from first principles. LR is a great gateway to learn a variety of topics in mathematics, statistics and machine learning. And as a bonus, LR is useful in practice. Let's talk about LR. + +## Corporate roleplay + +It is Tuesday. You are enjoying a cup of coffee and your manager calls you. She says that we need to predict if a customer goes away (churns) in one month. You listen to her until you agree to do it, probably because your coffee is getting cold. Eventually you start to think about the problem and come up with a very short specification: + +* Churn happens or doesn't. It is a binary outcome per customer. +* Your manager wants to prioritize her time with customers that are the most likely to churn. She needs the estimated probability of churn per customer. +* Your manager will be interested to understand how the predictions are made, especially if they are accurate. She might learn useful insights on how different factors affect customer churn. +* The probability estimates should not fluctuate heavily all the time. + +## Start simple + +Time to define the churn prediction task. We need a model that outputs the estimated probability of churn \\(\hat{p}\\): + +$$ \hat{p}=P(churn | \vec{x}) \in [0,1] $$ + +The symbol \\(\vec{x}\\) denotes a \\(d\\)-dimensional vector \\(\vec{x}=\\{x_1,x_2,...x_d\\}\\) of \\(d\\) numbers. Let's call them data vectors. Also known as [feature](https://en.wikipedia.org/wiki/Feature_(machine_learning)) values in machine learning lingo. If a specific customer has a corresponding data vector \\(\vec{x}\\), then this customer has an estimated churn probability of \\(P(churn | \vec{x})\\). A math person might write \\(\vec{x} \in \mathbb{R}^d\\) meaning that the data vectors are contained in \\(d\\)-dimensional [Euclidean space](https://en.wikipedia.org/wiki/Euclidean_space). Relevant data vectors for churn prediction might be available in your Customer Relationship Managemen (CRM) system database. + +We need to define a statistical model for \\(\hat{p}\\). Like, explicitly tell how \\(\hat{p}\\) is manifested in our world. Let's define a simple model for easy computation, inference and implementation. You are also more likely to see consistent results. Not so much with large neural networks where random initialization can give you a different winning [lottery ticket](https://arxiv.org/abs/1803.03635) every time, even if the data do not change. Machine learning is probably the only field where winning the lottery means that you have to work and grind even harder. + +{{< blockquote author="The author of this post" >}} +You learn to expect that training a large neural network over and over again using the same data gives you different results. +{{< /blockquote >}} + +However. + +{{< blockquote author="Someone, not Einstein" >}} +The definition of insanity is doing the same thing over and over again and expecting different results. +{{< /blockquote >}} + +Deep learning, not even once. Makes you crazy by definition... You can stop throwing tomatoes at me. I understand that if you fix every initial condition, you will get identical results over repetitions. And that there are techniques making the results more stable. + +Can you imagine how unhappy your manager might become, and how hard her job would be, if "nothing changes" and the estimates change significantly? Of course we are assuming that you get acceptable results using a simple model. You can always test if a simple model is accurate enough in your business context. Anyway, I believe it is always a good idea to start with a simple model when it makes sense. Tip: you might need a complex model for speech recognition. + +Ok, sorry for that digression, let's get back to designing a simple model for \\(\hat{p}\\). How about the following linear [functional form](https://www.encyclopedia.com/social-sciences/applied-and-social-sciences-magazines/functional-form) using a [dot product](https://en.wikipedia.org/wiki/Dot_product)? + +$$ \hat{p}(\vec{x},\vec{w}) = \vec{x} \cdot \vec{w} = \sum\nolimits_i^d{\vec{x}_i\vec{w}_i} = \vec{x}_1\vec{w}_1 + ... + \vec{x}_d\vec{w}_d $$ + +How is the dot product useful for our purposes? It matches and activates to patterns that are exhibited by customers with a high probability of churn. It maps a customer-specific \\(\vec{x}\\) to a large value if \\(\vec{x}\\) contains patterns that indicate churn. Notice that you can only tweak the parameter vector \\(\vec{w}\\) because \\(\vec{x}\\) is already fixed and given. This is the reason why \\(\vec{w}\\) contains the model's knowledge, why it is called a parameter vector and why finding the values of \\(\vec{w}\\) is called training the model. + +Unfortunately, the functional form that we designed sucks: + +1. The [range](https://www.sydney.edu.au/content/dam/students/documents/mathematics-learning-centre/functions-domain-and-range.pdf) of the dot product \\(\vec{x} \cdot \vec{w}\\) is \\((-\infty,\infty)\\), which is not great if we want \\(\hat{p}\\) to represent a probability. Our earlier definition of \\(P\\), and the definition of a probability in general, says that a probability takes values within \\([0,1]\\). How about using the dot product as a unnormalized probability? No thanks, it's an ad hoc hack. How large values are considered to be large enough to indicate churn? It is a nightmare to find a reliable magic threshold. +2. If the [norms](https://en.wikipedia.org/wiki/Norm_(mathematics)) of \\(\vec{x}\\) or \\(\vec{w}\\) are large, then large changes are required for significant change in \\(\hat{p}\\). It is a gateway to [overfitting](https://en.wikipedia.org/wiki/Overfitting) your models to noise or having some variables dominate others just because they are measured in larger absolute units. + +Time for a new iteration. + +## Responsive math design + +How to make \\(\hat{p}\\) more sensitive to smaller changes in \\(\vec{x}\\) or \\(\vec{w}\\)? Exponents. Those have some serious firepower. Let's use the natural exponential function as follows: + +$$ \hat{p}(\vec{x},\vec{w}) = e^{\vec{x} \cdot \vec{w}} $$ + +Now changes in \\(\vec{x}\\) or \\(\vec{w}\\) result in multiplicative change in \\(\hat{p}\\). + +The previously mentioned problem of \\(\hat{p}\\) not being a probability is still here. The value of \\(\hat{p}\\) is between zero and positive infinity: \\(e^{\vec{x} \cdot \vec{w}} \in (0,\infty)\\). Therefore, \\(\hat{p}\\) is not a valid probability unless we use duck tape and define \\(\hat{p}=min(1,e^{\vec{x} \cdot \vec{w}})\\). Just... no. Let's come up with something better. + +## Houston, we have a probability + +What is a suitable function that maps \\((0,\infty)\\) to \\((0,1)\\)? + +Time passes, the head scratching is audible and nothing comes up. Time to quit? No. We should try something different. So far we have been focusing on the right side of the equation. + +Let's define a function called odds to map a probability \\(p \in \[0,1)\\) to \\(\[0,\infty)\\): + +$$ + odds(p) = \frac{p}{1-p} +$$ + +The inverse function of odds maps \\(\[0,\infty)\\) back to a probability \\(\[0,1)\\): + +$$ + p(odds) = \frac{odds}{1+odds} +$$ + +Ok. So we have the following functions available: + +| Function | Domain (from) | Range (to) | +| ------------- |:-------------:| -----:| +| \\(odds(p) = \frac{p}{1-p}\\) | \\(\[0,1)\\) | \\(\[0,\infty)\\) | +| \\(p(\vec{x},\vec{w})=e^{\vec{x} \cdot \vec{w}}\\) | \\((-\infty,\infty)\\) | \\((0,\infty)\\) | + +Interesting. Two equations with matching legos. It is possible to model the \\(odds\\) function as a dot product since they have compatible ranges: + +$$ + odds(p) = p(\vec{x},\vec{w}) +$$ + +Now throw the inverse odds function into the mix: + +$$ + p(odds(\vec{x},\vec{w})) = \frac{e^{\vec{x} \cdot \vec{w}}}{1+e^{\vec{x} \cdot \vec{w}}} = \frac{1}{1+e^{-\vec{x} \cdot \vec{w}}} +$$ + +You'll get the last form by multiplying \\(\frac{e^{\vec{x} \cdot \vec{w}}}{1+e^{\vec{x} \cdot \vec{w}}}\\) with \\(\frac{e^{-\vec{x} \cdot \vec{w}}}{e^{-\vec{x} \cdot \vec{w}}}\\). Since \\(p\\) behaves like a probability, and we want a probability, let's redefine \\(\hat{p}\\) using \\(p\\) like a coder who re-assigns a variable value: + +$$ + \hat{p} = P(churn | \vec{x}) = p(odds(\vec{x},\vec{w})) = \frac{1}{1+e^{-\vec{x} \cdot \vec{w}}} +$$ + +Well, well, look who's here. Our good friend Logistic Regression. The model outputs a valid probability estimate and its decision boundary is linear. We have a model that fulfills our short specification for churn prediction. We might be wrong but at least we are not guaranteed to be wrong. + +Please wait. Do not deploy your model in production yet. Your manager is not going to be happy with the results. Why? The model spews out noise, it makes random guesses, no intelligence involved. Why? There is zero knowledge in the model since \\(\vec{w}\\) is unchanged. The model needs to be trained and the parameters adjusted using training data. + +## Why the name? + +Why LR is called Logistic and Regression? Let's take some shortcuts instead of repeating all the previous steps. The following function is called [logit](https://en.wikipedia.org/wiki/Logit) and it is the logarithm of our previously defined odds function: + +$$ + logit(p) = ln(\frac{p}{1-p}) \in (-\infty,\infty) +$$ + +Let's build a model where the logit defined as the previously used dot product. It is fine since \\(\vec{x} \cdot \vec{w} \in (-\infty,\infty)\\): + +$$ + ln(\frac{p}{1-p}) = \vec{x} \cdot \vec{w} +$$ + +Solve for \\(p\\) and you'll get: + +$$ + p = \frac{e^{\vec{x} \cdot \vec{w}}}{1+e^{\vec{x} \cdot \vec{w}}} = \frac{1}{1+e^{-\vec{x} \cdot \vec{w}}} = \sigma(\vec{x} \cdot \vec{w}) +$$ + +We find LR again. The function \\(\sigma\\) is called a [sigmoid function](https://en.wikipedia.org/wiki/Sigmoid_function) and it is a special case of [logistic function](https://en.wikipedia.org/wiki/Logistic_function). Sigmoid function is actually the inverse function of the logit function. + +Regression comes from the linear regression model that we throw in the sigmoid function, which is a special case of logistic function. That's Logistic and Regression in Logistic Regression. + +## Learning in machine learning + +We have defined the model, which turned out to be LR. The next step is to put learning in machine learning. For our LR, learning means finding a parameter vector \\(\vec{w}\\) that outputs a large value of \\(\vec{x} \cdot \vec{w}\\) when \\(\vec{x}\\) corresponds to a customer with a high probability of churn, and vice versa. + +What is a principled basis for tweaking the parameter vector? First of all, it is mathematically impossible to create information from nothing. We need to continue injecting knowledge and assumptions into the system, which we already started by defining the functional form or our LR model. How about observations of what has happened so far? Well, at least they might reflect the real world whereas our own guesses might not. Luckily your CRM system maintains a database of past and current customer behavior. We can extract \\(n\\) pairs of data vectors \\(\vec{x}\_i\\) and churn outcomes \\(y\_i \in \\{0,1\\}\\). This set of actual, historical behavior is called a dataset: + +$$ + \mathbf{X}=\\{(\\vec{x}\_1,y\_1),...,(\\vec{x}\_n,y\_n)\\} \subset \mathbf{R}^d \times \\{0,1\\} +$$ + +Okay. How to get started in figuring out a suitable \\(\vec{w}\\)? How do we even know what is a good \\(\vec{w}\\)? Well, it is impossible to define what is good if there is no reference of bad. + +{{< blockquote author="Antoine-Augustin Cournot, 1847" >}} +If you can not measure it, you can not improve it. +{{< /blockquote >}} + +Thanks Antoine-Augustin. Indeed, it is a fundamental necessity to have a function \\(L(\vec{w} | \mathbf{X})\\) that tells us how good fit a given parameter vector is for the available dataset. Let's call it a [likelihood function](https://en.wikipedia.org/wiki/Likelihood_function). This formulation let's us to find a parameter vector \\(\vec{w}\_\*\\) that is consistent with the dataset by maximising the likelihood function value: + +$$ + \vec{w}\_\*=argmax_{\vec{w}}L(\vec{w} | \mathbf{X}) +$$ + +Therefore, \\(\vec{w}\_\*\\) gives us LR that is aligned with the dataset. The implicit assumption for making predictions using previously unseen data vectors is that the patterns in past behavior are indicative and predictive of the future behavior. If this assumption does not hold for churn prediction, then you'll get incorrect predictions. Therefore, we will continue by assuming that many customers share similar reasons for churn. Let's call \\(\vec{w}\_\*\\) a solution since it is the parameter vector of the highest likelihood for the given data. + +Notice that if we had a dataset of infinite size (\\(n=\infty\\)), then we wouldn't need LR at all. We could calculate the churn probability as the ratio between the number of churn positives and total observations for a data vector. It would be a database query. However, since obviously we don't have an infinite amount of data, let alone a computer that runs for an infinite time, we have to make assumptions and find patterns. And hope that the customers now and in the future exhibit the same patterns. + +Ok. What next? We need to define the functional form of the likelihood function that computes the compatibility of a parameter vector with a given dataset. Let's start by defining the likelihood function for a single pair of a data vector and outcome. Notice that it is a dataset of size \\(n=1\\). How about the following definition? + +$$ +L(\vec{w} | \vec{x},y) = \begin{cases} + \hat{p}(\vec{x},\vec{w}) & \text{if } y=1 \\\\ + 1-\hat{p}(\vec{x},\vec{w}) & \text{if } y=0 +\end{cases} +$$ + +If a customer did actually churn (\\(y=1\\)), then the likelihood is the estimated probability of churn. If the customer didn't churn (\\(y=0\\)), then the likelihood is the estimated probability of not churning. No matter what is the outcome, if we maximize the value of \\(L(\vec{w} | \vec{x},y)\\), then we maximize the fit of our LR model to the data. It is exactly what we want. In other words, maximizing the likelihood function maximizes the amount of correctly assigned probability. Let's do what coders do and refactor it into a single expression for mathematical convenience: + +$$ + L(\vec{w} | \vec{x},y) = \hat{p}(\vec{x},\vec{w})^{y}(1-\hat{p}(\vec{x},\vec{w}))^{1-y} +$$ + +Done. The definition has not changed. Let's assume that the pairs of \\(\vec{x}\_i,y\_i\\) are [independent and identically distributed](https://en.wikipedia.org/wiki/Independent_and_identically_distributed_random_variables). Now we can generalize the definition of the likelihood function for datasets of size \\(n \ge 1\\) by defining a joint probability: + +$$ + L(\vec{w} | \mathbf{X}) = \prod\nolimits_i^n \hat{p}(\vec{x_i},\vec{w})^{y_i}(1-\hat{p}(\vec{x_i},\vec{w}))^{1-y_i} +$$ + +Phew! There it is. Maximize \\(L(\vec{w} | \mathbf{X})\\) to get a solution and call it a day. However, aren't we missing something quite essential here? We have to maximize the likelihood function and it's not going to happen by wishful thinking alone. + +## Learning to learn continues + +The situation is as follows. We have a dataset and we have a likelihood function. The next step is to find a parameter vector to maximize the likelihood function. It smells like an optimization task. Where and how to get started? Maybe you could gamble and sample random parameter vectors from a [uniform distribution](https://en.wikipedia.org/wiki/Continuous_uniform_distribution) for ten minutes, and save the \\(\vec{w}\\) with the highest likelihood function value. Well... you might get lucky and get reasonable results. Or not. It is not deterministic, there are no guarantees of optimality even if you throw dice for the rest of your life. + +Do you happen to remember calculus? Derivatives, integrals, rate of change. The stuff that was butchered by a boring teacher. The stuff that you never used? Good news. You did not study for nothing. We have a real use case for calculus. Let's start. + +[Calculus](https://en.wikipedia.org/wiki/Calculus) is the mathematics of change where [derivatives](https://en.wikipedia.org/wiki/Derivative) measure the rate of change of a function at a given point. Like, how stable the particular location of a function is. Or how sensitive the function is to a given input. We can use derivatives to define the change in our likelihood function given a parameter vector as an input. Sounds promising. Actually, we can calculate the direction where we need to move the parameter vector to increase the value of the likelihood function. That's exactly what we need, a tool served for us on a silver platter. To use this mighty tool, we need to define the derivatives of the likelihood function with respect to the individual elements of a parameter vector. They are called [partial derivatives](https://en.wikipedia.org/wiki/Derivative#Partial_derivatives). When we collect the partial derivatives into a vector, then it is called a [gradient](https://en.wikipedia.org/wiki/Gradient). Now, the gradient gives us the direction from \\(\vec{w}\\) that increases the \\(L(\vec{w} | \mathbf{X})\\) the fastest. + +We have already spelled out the algorithm for maximizing the likelihood function. Push the parameter vector towards the direction defined by the gradient of the likelihood function with respect to the parameter vector. If we take repeated turns between calculating the gradient and updating the parameter vector, we arrive to an optimization algorithm called gradient ascend. Hopefully math people are not offended by our abuse of notation when we define the following assignment using the equality sign: + +$$ + \vec{w} = \vec{w} + \alpha\nabla_wL +$$ + +The alpha is a parameter of the gradient descend, and it is not a parameter of the LR model. In machine learning lingo, the alpha is a hyperparameter and it is called a learning rate in this gradient ascend context. The alpha is utilized to stabilize the gradient ascend by not taking too large or small steps towards the gradient. You can try \\(\alpha=\frac{0.05}{n}\\) as a starting point. The meaning behind the name of the gradient ascend is hopefully clear. The parameter vector ascends towards the direction of increased likelihood. + +Now, to train the LR model, we can repeat the gradient ascend step until the likelihood function does not increase anymore. Done, lights out, time to go home now. Or not. We haven't derived the gradient yet. I promise that this is as deep as the recursion gets. Light is visible at the end of the tunnel already. + +## Gradient without colors + +The parameter vector has \\(d\\) elements. However, without loss of generality, we can derive the partial derivative of the \\(j\\):th element with respect to the likelihood function, and just copy it for the remaining elements to form the gradient. As a refresher, the following was the definition of the likelihood function: + +$$ + L(\vec{w} | \mathbf{X}) = \prod\nolimits_i^n \hat{p}(\vec{x_i},\vec{w})^{y_i}(1-\hat{p}(\vec{x_i},\vec{w}))^{1-y_i} +$$ + +The product is quite hairy and feels troublesome. Like, the equation is a giant multiplication. Have fun deriving monster sized equations. No thanks. + +Let's use a transformation that doesn't change the solution \\(\vec{w}\_\*\\) while it makes the derivation easier. One such a function is the natural logarithm, which is a [monotonic function](https://en.wikipedia.org/wiki/Monotonic_function). It has the awesome property of converting products into sums: + +$$ + ln(L(\vec{w} | \mathbf{X})) = l(\vec{w} | \mathbf{X}) = \sum\nolimits_i^n y_iln(\hat{p}(\vec{x_i},\vec{w})) + (1-y_i)ln(1-\hat{p}(\vec{x_i},\vec{w})) +$$ + +The partial derivative of \\(l(\vec{w} | \mathbf{X})\\) with respect to the \\(j\\):th element of \\(\vec{w}\\) is denoted as \\(\frac{\partial l(\vec{w} | \mathbf{X})}{\partial w_j}\\). Time to wear a calculus hat and start deriving the derivative. Let's temporarily use a shorter notation \\(\hat{p}(\vec{x},\vec{w})=\sigma(z)=\hat{p}\\) where \\(z=\vec{x} \cdot \vec{w}\\) to make the equations shorter. + +$$ + \begin{aligned} + \frac{l(\vec{w} | \mathbf{X})}{\partial w_j} &= \frac{\partial}{\partial w_j}[\sum\nolimits_i^n y_iln(\hat{p}) + (1-y_i)ln(1-\hat{p})] \cr + &= \sum\nolimits_i^n \frac{\partial}{\partial w_j}[y_iln(\hat{p}) + (1-y_i)ln(1-\hat{p})] \cr + &= \sum\nolimits_i^n \frac{\partial}{\partial w_j}[y_iln(\hat{p})] + \frac{\partial}{\partial w_j}[(1-y_i)ln(1-\hat{p})] \cr + &= \sum\nolimits_i^n y_i\frac{\partial}{\partial \hat{p}}\frac{\partial \hat{p}}{\partial w_j}[ln(\hat{p})] + (1-y_i)\frac{\partial}{\partial \hat{p}}\frac{\partial \hat{p}}{\partial w_j}[ln(1-\hat{p})] \cr + &= \sum\nolimits_i^n y_i\frac{1}{\hat{p}}\frac{\partial}{\partial z}\frac{\partial \hat{z}}{\partial w_j}[\hat{p}] + (1-y_i)\frac{1}{1-\hat{p}}(-1)\frac{\partial}{\partial \hat{z}}\frac{\partial \hat{z}}{\partial w_j}[\hat{p}] \cr + &= \sum\nolimits_i^n \frac{y_i}{\hat{p}}\frac{\partial}{\partial z}\frac{\partial \hat{z}}{\partial w_j}[\hat{p}] + \frac{y_i-1}{1-\hat{p}}\frac{\partial}{\partial \hat{z}}\frac{\partial \hat{z}}{\partial w_j}[\hat{p}] \cr + \end{aligned} +$$ + +Okay... let's have a pause here and derive \\(\frac{\partial \hat{p}}{\partial z}\\) separately so that we can have a break from the previous monster. + +$$ + \begin{aligned} +\frac{\partial \hat{p}}{\partial z} &= \frac{\partial}{\partial z}\frac{1}{1+e^{-z}} \cr +&= \frac{\partial}{\partial z}(1+e^{-z})^{-1} \cr +&= -(1+e^{-z})^{-2}\frac{\partial}{\partial z}(1+e^{-z}) \cr +&= -(1+e^{-z})^{-2}e^{-z}\frac{\partial}{\partial z}[-z] \cr +&= (1+e^{-z})^{-2}e^{-z} \cr +&= \frac{e^{-z}}{(1+e^{-z})^2} \cr +&= \frac{1}{1+e^{-z}}\frac{e^{-z}}{1+e^{-z}} \cr +&= \hat{p}(1-\hat{p}) \cr + \end{aligned} +$$ + +Back to the likelihood business: + +$$ + \begin{aligned} +\frac{l(\vec{w} | \mathbf{X})}{\partial w_j} &= \sum\nolimits_i^n \frac{y_i}{\hat{p}}\frac{\partial}{\partial z}\frac{\partial \hat{z}}{\partial w_j}[\hat{p}] + \frac{y_i-1}{1-\hat{p}}\frac{\partial}{\partial \hat{z}}\frac{\partial \hat{z}}{\partial w_j}[\hat{p}] \cr +&= \sum\nolimits_i^n \frac{y_i}{\hat{p}}\hat{p}(1-\hat{p})\frac{\partial \hat{z}}{\partial w_j}[z] + \frac{y_i-1}{1-\hat{p}}\hat{p}(1-\hat{p})\frac{\partial \hat{z}}{\partial w_j}[z] \cr +&= \sum\nolimits_i^n \frac{y_i}{\hat{p}}\hat{p}(1-\hat{p})x_i^j + \frac{y_i-1}{1-\hat{p}}\hat{p}(1-\hat{p})x_i^j \cr +&= \sum\nolimits_i^n y_i(1-\hat{p})x_i^j + (y_i-1)\hat{p}x_i^j \cr +&= \sum\nolimits_i^n y_ix_i^j-y_i\hat{p}x_i^j+y_i\hat{p}x_i^j-\hat{p}x_i^j \cr +&= \sum\nolimits_i^n y_ix_i^j-\hat{p}x_i^j \cr +&= \sum\nolimits_i^n (y_i-\hat{p})x_i^j \cr + \end{aligned} +$$ + +There it is! The partial derivative that can save us. Let's say it aloud once more: + +$$ + \frac{\partial l(\vec{w} | \mathbf{X})}{\partial w_j} = \sum\nolimits_i^n (y_i - \hat{p}(\vec{x_i},\vec{w}))x_i^j +$$ + +The symbol \\(x_i^j\\) means the \\(j\\):th element of the \\(i\\):th data vector in the dataset. Now, the gradient is as follows: + +$$ + \nabla_wl(\vec{w} | \mathbf{X}) = \\{\frac{\partial l(\vec{w} | \mathbf{X})}{\partial w_1},...,\frac{\partial l(\vec{w} | \mathbf{X})}{\partial w_d}\\} +$$ + +Now you are ready to train your LR for churn prediction: +1. Get a dataset +2. Initialize \\(\vec{w}\\) with random values +3. Train the LR: + 1. Calculate \\(\nabla_wl(\vec{w} | \mathbf{X})\\) + 2. Update \\(\vec{w} = \vec{w} + \alpha\nabla_wl(\vec{w} | \mathbf{X})\\). + 3. If the likelihood function \\(L(\vec{w} | \mathbf{x})\\) increased, then go to 3a. If not, then \\(\vec{w}\_\*=\vec{w}\\) and go to 4. +4. Use your LR to make churn predictions as \\(\hat{p}(\vec{x},\vec{w}\_\*) = \sigma(\vec{x} \cdot \vec{w}\_\*)\\) + + +## Conclusion + +LR is worth learning since it covers multiple topics. LR is also relevant and useful in practice. I skipped some topics that might be helpful to know. Nevertheless, you are ready to implement the first version of the LR for churn prediction. + +A coder can import scikit-learn in Python and use the provided LR without any knowledge of the previously explained stuff. That's fine until its not. For example, the gradient ascend of the likelihood function might not converge properly. If you understand the concepts behind LR, then you might have an intuition of how to fix the problem. Otherwise, you have to guess what to ask at Stack Overflow. + +LR has some direct connections to more complex models: +* If you [Kaggle](https://www.kaggle.com/), then you might be aware that [sigmoid function is available](https://xgboost.readthedocs.io/en/latest/parameter.html#learning-task-parameters) in [XGboost](https://github.com/dmlc/xgboost/) for binary classification. +* If you think in neural networks, then LR is a neural network with one sigmoid-activated unit. LR is also often the last transformation in deep neural networks for binary classification. The deep neural network tries to unfold the data before it arrives to the LR, which throws a linear decision boundary at the data. The transformations find a data representation that is [linearly separable](https://en.wikipedia.org/wiki/Linear_separability) for the LR. Therefore, the purpose of the millions and billions of parameters is to serve the LR by finding a data transformation that the mighty LR can comprehend, also known as feature learning. I find it funny, it's like trying to build a simple joystick with one button to fly a massive passenger airplane. Anyway. + +Further topics, exercises and tasks for you: +* How would you measure the accuracy of your LR as a churn predictor for unseen data vectors? What is overfitting? What is cross-validation? +* What is regularization? What is the difference of \\(l_1\\) and \\(l_2\\) regularization? Derive the gradient \\(\nabla_wl(\vec{w} | \mathbf{X})\\) +when the likelihood function is re-defined as \\(l(\vec{w} | \mathbf{X}) - l_2(\vec{w})\\). +* Check out softmax function for generalizing LR to have a categorical output. Derive the gradient for this softmax multiclass LR. What is the intuition behind naming softmax as softmax? +* There are arguably more efficient algorithms available for the gradient ascend. Find some of them. +* What is the difference when a parameter vector is at a global optimum or at a local optimum? +* What is a greedy algorithm? Why is the gradient ascend a greedy algorithm? +* Why not to worry about ending up at a local optimum with LR, even when using the gradient ascend that is a greedy algorithm? +* Implement the LR evaluation and training from scratch in Python. Utilize some public dataset. Use NumPy for vectorizing the computations. +* What is entropy in information theory? How is \\(l(\vec{w} | \mathbf{X})\\) related to entropy and what is the intuition? Why is the negative of \\(l(\vec{w} | \mathbf{X})\\) often called binary cross-entropy loss? +* What is the connection of \\(L(\vec{w} | \mathbf{X})\\) and Bernoulli distribution? + +Peace and harmony. + diff --git a/content/post/refresher-customer-experience.md b/content/post/refresher-customer-experience.md new file mode 100644 index 0000000..3d96e54 --- /dev/null +++ b/content/post/refresher-customer-experience.md @@ -0,0 +1,6 @@ +--- +title: "[PDF] How To Measure Customer Experience" +date: 2021-01-19T02:02:47+02:00 +draft: true +--- + diff --git a/content/post/singular-value-decomposition-is-worth-learning.md b/content/post/singular-value-decomposition-is-worth-learning.md new file mode 100644 index 0000000..ad96b35 --- /dev/null +++ b/content/post/singular-value-decomposition-is-worth-learning.md @@ -0,0 +1,6 @@ +--- +title: "Singular Value Decomposition Is Worth Learning" +date: 2021-01-19T02:24:02+02:00 +draft: true +--- + diff --git a/content/post/teach-context.md b/content/post/teach-context.md new file mode 100644 index 0000000..7cd441d --- /dev/null +++ b/content/post/teach-context.md @@ -0,0 +1,15 @@ +--- +title: "Teach Context" +date: 2021-01-19T02:53:07+02:00 +draft: true +--- + +A math equation is a pinnacle of sweat, tears and exploration. There is a human story. + +A slight analog with show and tell in literature. + +It is your responsibility to make people interested and sell your ideas. + +Like the cliche says that there are no boring topics, just bored minds. + +Give context to your listeners. Help them to think and understand. diff --git a/static/css/basic_highlight.css b/static/css/basic_highlight.css new file mode 100644 index 0000000..c27e27f --- /dev/null +++ b/static/css/basic_highlight.css @@ -0,0 +1,145 @@ +/* PrismJS 1.23.0 +https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+bash+docker+python */ +/** + * prism.js default theme for JavaScript, CSS and HTML + * Based on dabblet (http://dabblet.com) + * @author Lea Verou + */ + +code[class*="language-"], +pre[class*="language-"] { + color: black; + background: none; + text-shadow: 0 1px white; + font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; + font-size: 1em; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} + +pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, +code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { + text-shadow: none; + background: #b3d4fc; + color: black; +} + +pre[class*="language-"]::selection, pre[class*="language-"] ::selection, +code[class*="language-"]::selection, code[class*="language-"] ::selection { + text-shadow: none; + background: #b3d4fc; + color: black; +} + +@media print { + code[class*="language-"], + pre[class*="language-"] { + text-shadow: none; + } +} + +/* Code blocks */ +pre[class*="language-"] { + padding: 1em; + margin: .5em 0; + overflow: auto; +} + +:not(pre) > code[class*="language-"], +pre[class*="language-"] { + background: white; +} + +/* Inline code */ +:not(pre) > code[class*="language-"] { + padding: .1em; + border-radius: .3em; + white-space: normal; +} + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: slategray; +} + +.token.punctuation { + color: #999; +} + +.token.namespace { + opacity: .7; +} + +.token.property, +.token.tag, +.token.boolean, +.token.number, +.token.constant, +.token.symbol, +.token.deleted { + color: #905; +} + +.token.selector, +.token.attr-name, +.token.string, +.token.char, +.token.builtin, +.token.inserted { + color: #690; +} + +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string { + color: #9a6e3a; + /* This background color was intended by the author of this theme. */ + background: hsla(0, 0%, 100%, .5); +} + +.token.atrule, +.token.attr-value, +.token.keyword { + color: #07a; +} + +.token.function, +.token.class-name { + color: #DD4A68; +} + +.token.regex, +.token.important, +.token.variable { + color: #e90; +} + +.token.important, +.token.bold { + font-weight: bold; +} +.token.italic { + font-style: italic; +} + +.token.entity { + cursor: help; +} + diff --git a/static/js/basic_highlight.js b/static/js/basic_highlight.js new file mode 100644 index 0000000..54aff7c --- /dev/null +++ b/static/js/basic_highlight.js @@ -0,0 +1,10 @@ +/* PrismJS 1.23.0 +https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+bash+docker+python */ +var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(u){var c=/\blang(?:uage)?-([\w-]+)\b/i,n=0,_={manual:u.Prism&&u.Prism.manual,disableWorkerMessageHandler:u.Prism&&u.Prism.disableWorkerMessageHandler,util:{encode:function e(n){return n instanceof M?new M(n.type,e(n.content),n.alias):Array.isArray(n)?n.map(e):n.replace(/&/g,"&").replace(/=l.reach);y+=m.value.length,m=m.next){var k=m.value;if(t.length>n.length)return;if(!(k instanceof M)){var b,x=1;if(h){if(!(b=W(p,y,n,f)))break;var w=b.index,A=b.index+b[0].length,P=y;for(P+=m.value.length;P<=w;)m=m.next,P+=m.value.length;if(P-=m.value.length,y=P,m.value instanceof M)continue;for(var S=m;S!==t.tail&&(Pl.reach&&(l.reach=N);var j=m.prev;O&&(j=z(t,j,O),y+=O.length),I(t,j,x);var C=new M(o,g?_.tokenize(E,g):E,d,E);m=z(t,j,C),L&&z(t,m,L),1"+a.content+""},!u.document)return u.addEventListener&&(_.disableWorkerMessageHandler||u.addEventListener("message",function(e){var n=JSON.parse(e.data),t=n.language,r=n.code,a=n.immediateClose;u.postMessage(_.highlight(r,_.languages[t],t)),a&&u.close()},!1)),_;var e=_.util.currentScript();function t(){_.manual||_.highlightAll()}if(e&&(_.filename=e.src,e.hasAttribute("data-manual")&&(_.manual=!0)),!_.manual){var r=document.readyState;"loading"===r||"interactive"===r&&e&&e.defer?document.addEventListener("DOMContentLoaded",t):window.requestAnimationFrame?window.requestAnimationFrame(t):window.setTimeout(t,16)}return _}(_self);"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism); +Prism.languages.markup={comment://,prolog:/<\?[\s\S]+?\?>/,doctype:{pattern:/"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^$|[[\]]/,"doctype-tag":/^DOCTYPE/,name:/[^\s<>'"]+/}},cdata://i,tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.languages.markup.doctype.inside["internal-subset"].inside=Prism.languages.markup,Prism.hooks.add("wrap",function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&/,"&"))}),Object.defineProperty(Prism.languages.markup.tag,"addInlined",{value:function(a,e){var s={};s["language-"+e]={pattern:/(^$)/i,lookbehind:!0,inside:Prism.languages[e]},s.cdata=/^$/i;var n={"included-cdata":{pattern://i,inside:s}};n["language-"+e]={pattern:/[\s\S]+/,inside:Prism.languages[e]};var t={};t[a]={pattern:RegExp("(<__[^>]*>)(?:))*\\]\\]>|(?!)".replace(/__/g,function(){return a}),"i"),lookbehind:!0,greedy:!0,inside:n},Prism.languages.insertBefore("markup","cdata",t)}}),Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup,Prism.languages.xml=Prism.languages.extend("markup",{}),Prism.languages.ssml=Prism.languages.xml,Prism.languages.atom=Prism.languages.xml,Prism.languages.rss=Prism.languages.xml; +!function(s){var e=/("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/;s.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:/@[\w-](?:[^;{\s]|\s+(?![\s{]))*(?:;|(?=\s*\{))/,inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/,lookbehind:!0,alias:"selector"},keyword:{pattern:/(^|[^\w-])(?:and|not|only|or)(?![\w-])/,lookbehind:!0}}},url:{pattern:RegExp("\\burl\\((?:"+e.source+"|(?:[^\\\\\r\n()\"']|\\\\[^])*)\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/,string:{pattern:RegExp("^"+e.source+"$"),alias:"url"}}},selector:RegExp("[^{}\\s](?:[^{};\"'\\s]|\\s+(?![\\s{])|"+e.source+")*(?=\\s*\\{)"),string:{pattern:e,greedy:!0},property:/(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i,important:/!important\b/i,function:/[-a-z0-9]+(?=\()/i,punctuation:/[(){};:,]/},s.languages.css.atrule.inside.rest=s.languages.css;var t=s.languages.markup;t&&(t.tag.addInlined("style","css"),s.languages.insertBefore("inside","attr-value",{"style-attr":{pattern:/(^|["'\s])style\s*=\s*(?:"[^"]*"|'[^']*')/i,lookbehind:!0,inside:{"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{style:{pattern:/(["'])[\s\S]+(?=["']$)/,lookbehind:!0,alias:"language-css",inside:s.languages.css},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}},"attr-name":/^style/i}}},t.tag))}(Prism); +Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|interface|extends|implements|trait|instanceof|new)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,boolean:/\b(?:true|false)\b/,function:/\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/}; +Prism.languages.javascript=Prism.languages.extend("clike",{"class-name":[Prism.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:prototype|constructor))/,lookbehind:!0}],keyword:[{pattern:/((?:^|})\s*)(?:catch|finally)\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|for|from|function|(?:get|set)(?=\s*[\[$\w\xA0-\uFFFF])|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],function:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,number:/\b(?:(?:0[xX](?:[\dA-Fa-f](?:_[\dA-Fa-f])?)+|0[bB](?:[01](?:_[01])?)+|0[oO](?:[0-7](?:_[0-7])?)+)n?|(?:\d(?:_\d)?)+n|NaN|Infinity)\b|(?:\b(?:\d(?:_\d)?)+\.?(?:\d(?:_\d)?)*|\B\.(?:\d(?:_\d)?)+)(?:[Ee][+-]?(?:\d(?:_\d)?)+)?/,operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),Prism.languages.javascript["class-name"][0].pattern=/(\b(?:class|interface|extends|implements|instanceof|new)\s+)[\w.\\]+/,Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/((?:^|[^$\w\xA0-\uFFFF."'\])\s]|\b(?:return|yield))\s*)\/(?:\[(?:[^\]\\\r\n]|\\.)*]|\\.|[^/\\\[\r\n])+\/[gimyus]{0,6}(?=(?:\s|\/\*(?:[^*]|\*(?!\/))*\*\/)*(?:$|[\r\n,.;:})\]]|\/\/))/,lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:Prism.languages.regex},"regex-flags":/[a-z]+$/,"regex-delimiter":/^\/|\/$/}},"function-variable":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,inside:Prism.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,lookbehind:!0,inside:Prism.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),Prism.languages.insertBefore("javascript","string",{"template-string":{pattern:/`(?:\\[\s\S]|\${(?:[^{}]|{(?:[^{}]|{[^}]*})*})+}|(?!\${)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\${(?:[^{}]|{(?:[^{}]|{[^}]*})*})+}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\${|}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}}}),Prism.languages.markup&&Prism.languages.markup.tag.addInlined("script","javascript"),Prism.languages.js=Prism.languages.javascript; +!function(e){var t="\\b(?:BASH|BASHOPTS|BASH_ALIASES|BASH_ARGC|BASH_ARGV|BASH_CMDS|BASH_COMPLETION_COMPAT_DIR|BASH_LINENO|BASH_REMATCH|BASH_SOURCE|BASH_VERSINFO|BASH_VERSION|COLORTERM|COLUMNS|COMP_WORDBREAKS|DBUS_SESSION_BUS_ADDRESS|DEFAULTS_PATH|DESKTOP_SESSION|DIRSTACK|DISPLAY|EUID|GDMSESSION|GDM_LANG|GNOME_KEYRING_CONTROL|GNOME_KEYRING_PID|GPG_AGENT_INFO|GROUPS|HISTCONTROL|HISTFILE|HISTFILESIZE|HISTSIZE|HOME|HOSTNAME|HOSTTYPE|IFS|INSTANCE|JOB|LANG|LANGUAGE|LC_ADDRESS|LC_ALL|LC_IDENTIFICATION|LC_MEASUREMENT|LC_MONETARY|LC_NAME|LC_NUMERIC|LC_PAPER|LC_TELEPHONE|LC_TIME|LESSCLOSE|LESSOPEN|LINES|LOGNAME|LS_COLORS|MACHTYPE|MAILCHECK|MANDATORY_PATH|NO_AT_BRIDGE|OLDPWD|OPTERR|OPTIND|ORBIT_SOCKETDIR|OSTYPE|PAPERSIZE|PATH|PIPESTATUS|PPID|PS1|PS2|PS3|PS4|PWD|RANDOM|REPLY|SECONDS|SELINUX_INIT|SESSION|SESSIONTYPE|SESSION_MANAGER|SHELL|SHELLOPTS|SHLVL|SSH_AUTH_SOCK|TERM|UID|UPSTART_EVENTS|UPSTART_INSTANCE|UPSTART_JOB|UPSTART_SESSION|USER|WINDOWID|XAUTHORITY|XDG_CONFIG_DIRS|XDG_CURRENT_DESKTOP|XDG_DATA_DIRS|XDG_GREETER_DATA_DIR|XDG_MENU_PREFIX|XDG_RUNTIME_DIR|XDG_SEAT|XDG_SEAT_PATH|XDG_SESSION_DESKTOP|XDG_SESSION_ID|XDG_SESSION_PATH|XDG_SESSION_TYPE|XDG_VTNR|XMODIFIERS)\\b",n={pattern:/(^(["']?)\w+\2)[ \t]+\S.*/,lookbehind:!0,alias:"punctuation",inside:null},a={bash:n,environment:{pattern:RegExp("\\$"+t),alias:"constant"},variable:[{pattern:/\$?\(\([\s\S]+?\)\)/,greedy:!0,inside:{variable:[{pattern:/(^\$\(\([\s\S]+)\)\)/,lookbehind:!0},/^\$\(\(/],number:/\b0x[\dA-Fa-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:[Ee]-?\d+)?/,operator:/--?|-=|\+\+?|\+=|!=?|~|\*\*?|\*=|\/=?|%=?|<<=?|>>=?|<=?|>=?|==?|&&?|&=|\^=?|\|\|?|\|=|\?|:/,punctuation:/\(\(?|\)\)?|,|;/}},{pattern:/\$\((?:\([^)]+\)|[^()])+\)|`[^`]+`/,greedy:!0,inside:{variable:/^\$\(|^`|\)$|`$/}},{pattern:/\$\{[^}]+\}/,greedy:!0,inside:{operator:/:[-=?+]?|[!\/]|##?|%%?|\^\^?|,,?/,punctuation:/[\[\]]/,environment:{pattern:RegExp("(\\{)"+t),lookbehind:!0,alias:"constant"}}},/\$(?:\w+|[#?*!@$])/],entity:/\\(?:[abceEfnrtv\\"]|O?[0-7]{1,3}|x[0-9a-fA-F]{1,2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8})/};e.languages.bash={shebang:{pattern:/^#!\s*\/.*/,alias:"important"},comment:{pattern:/(^|[^"{\\$])#.*/,lookbehind:!0},"function-name":[{pattern:/(\bfunction\s+)\w+(?=(?:\s*\(?:\s*\))?\s*\{)/,lookbehind:!0,alias:"function"},{pattern:/\b\w+(?=\s*\(\s*\)\s*\{)/,alias:"function"}],"for-or-select":{pattern:/(\b(?:for|select)\s+)\w+(?=\s+in\s)/,alias:"variable",lookbehind:!0},"assign-left":{pattern:/(^|[\s;|&]|[<>]\()\w+(?=\+?=)/,inside:{environment:{pattern:RegExp("(^|[\\s;|&]|[<>]\\()"+t),lookbehind:!0,alias:"constant"}},alias:"variable",lookbehind:!0},string:[{pattern:/((?:^|[^<])<<-?\s*)(\w+?)\s[\s\S]*?(?:\r?\n|\r)\2/,lookbehind:!0,greedy:!0,inside:a},{pattern:/((?:^|[^<])<<-?\s*)(["'])(\w+)\2\s[\s\S]*?(?:\r?\n|\r)\3/,lookbehind:!0,greedy:!0,inside:{bash:n}},{pattern:/(^|[^\\](?:\\\\)*)(["'])(?:\\[\s\S]|\$\([^)]+\)|\$(?!\()|`[^`]+`|(?!\2)[^\\`$])*\2/,lookbehind:!0,greedy:!0,inside:a}],environment:{pattern:RegExp("\\$?"+t),alias:"constant"},variable:a.variable,function:{pattern:/(^|[\s;|&]|[<>]\()(?:add|apropos|apt|aptitude|apt-cache|apt-get|aspell|automysqlbackup|awk|basename|bash|bc|bconsole|bg|bzip2|cal|cat|cfdisk|chgrp|chkconfig|chmod|chown|chroot|cksum|clear|cmp|column|comm|composer|cp|cron|crontab|csplit|curl|cut|date|dc|dd|ddrescue|debootstrap|df|diff|diff3|dig|dir|dircolors|dirname|dirs|dmesg|du|egrep|eject|env|ethtool|expand|expect|expr|fdformat|fdisk|fg|fgrep|file|find|fmt|fold|format|free|fsck|ftp|fuser|gawk|git|gparted|grep|groupadd|groupdel|groupmod|groups|grub-mkconfig|gzip|halt|head|hg|history|host|hostname|htop|iconv|id|ifconfig|ifdown|ifup|import|install|ip|jobs|join|kill|killall|less|link|ln|locate|logname|logrotate|look|lpc|lpr|lprint|lprintd|lprintq|lprm|ls|lsof|lynx|make|man|mc|mdadm|mkconfig|mkdir|mke2fs|mkfifo|mkfs|mkisofs|mknod|mkswap|mmv|more|most|mount|mtools|mtr|mutt|mv|nano|nc|netstat|nice|nl|nohup|notify-send|npm|nslookup|op|open|parted|passwd|paste|pathchk|ping|pkill|pnpm|popd|pr|printcap|printenv|ps|pushd|pv|quota|quotacheck|quotactl|ram|rar|rcp|reboot|remsync|rename|renice|rev|rm|rmdir|rpm|rsync|scp|screen|sdiff|sed|sendmail|seq|service|sftp|sh|shellcheck|shuf|shutdown|sleep|slocate|sort|split|ssh|stat|strace|su|sudo|sum|suspend|swapon|sync|tac|tail|tar|tee|time|timeout|top|touch|tr|traceroute|tsort|tty|umount|uname|unexpand|uniq|units|unrar|unshar|unzip|update-grub|uptime|useradd|userdel|usermod|users|uudecode|uuencode|v|vdir|vi|vim|virsh|vmstat|wait|watch|wc|wget|whereis|which|who|whoami|write|xargs|xdg-open|yarn|yes|zenity|zip|zsh|zypper)(?=$|[)\s;|&])/,lookbehind:!0},keyword:{pattern:/(^|[\s;|&]|[<>]\()(?:if|then|else|elif|fi|for|while|in|case|esac|function|select|do|done|until)(?=$|[)\s;|&])/,lookbehind:!0},builtin:{pattern:/(^|[\s;|&]|[<>]\()(?:\.|:|break|cd|continue|eval|exec|exit|export|getopts|hash|pwd|readonly|return|shift|test|times|trap|umask|unset|alias|bind|builtin|caller|command|declare|echo|enable|help|let|local|logout|mapfile|printf|read|readarray|source|type|typeset|ulimit|unalias|set|shopt)(?=$|[)\s;|&])/,lookbehind:!0,alias:"class-name"},boolean:{pattern:/(^|[\s;|&]|[<>]\()(?:true|false)(?=$|[)\s;|&])/,lookbehind:!0},"file-descriptor":{pattern:/\B&\d\b/,alias:"important"},operator:{pattern:/\d?<>|>\||\+=|==?|!=?|=~|<<[<-]?|[&\d]?>>|\d?[<>]&?|&[>&]?|\|[&|]?|<=?|>=?/,inside:{"file-descriptor":{pattern:/^\d/,alias:"important"}}},punctuation:/\$?\(\(?|\)\)?|\.\.|[{}[\];\\]/,number:{pattern:/(^|\s)(?:[1-9]\d*|0)(?:[.,]\d+)?\b/,lookbehind:!0}},n.inside=e.languages.bash;for(var s=["comment","function-name","for-or-select","assign-left","string","environment","function","keyword","builtin","boolean","file-descriptor","operator","punctuation","number"],i=a.variable[1].inside,o=0;o?]/},Prism.languages.dockerfile=Prism.languages.docker; +Prism.languages.python={comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0},"string-interpolation":{pattern:/(?:f|rf|fr)(?:("""|''')[\s\S]*?\1|("|')(?:\\.|(?!\2)[^\\\r\n])*\2)/i,greedy:!0,inside:{interpolation:{pattern:/((?:^|[^{])(?:{{)*){(?!{)(?:[^{}]|{(?!{)(?:[^{}]|{(?!{)(?:[^{}])+})+})+}/,lookbehind:!0,inside:{"format-spec":{pattern:/(:)[^:(){}]+(?=}$)/,lookbehind:!0},"conversion-option":{pattern:/![sra](?=[:}]$)/,alias:"punctuation"},rest:null}},string:/[\s\S]+/}},"triple-quoted-string":{pattern:/(?:[rub]|rb|br)?("""|''')[\s\S]*?\1/i,greedy:!0,alias:"string"},string:{pattern:/(?:[rub]|rb|br)?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/i,greedy:!0},function:{pattern:/((?:^|\s)def[ \t]+)[a-zA-Z_]\w*(?=\s*\()/g,lookbehind:!0},"class-name":{pattern:/(\bclass\s+)\w+/i,lookbehind:!0},decorator:{pattern:/(^\s*)@\w+(?:\.\w+)*/im,lookbehind:!0,alias:["annotation","punctuation"],inside:{punctuation:/\./}},keyword:/\b(?:and|as|assert|async|await|break|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|nonlocal|not|or|pass|print|raise|return|try|while|with|yield)\b/,builtin:/\b(?:__import__|abs|all|any|apply|ascii|basestring|bin|bool|buffer|bytearray|bytes|callable|chr|classmethod|cmp|coerce|compile|complex|delattr|dict|dir|divmod|enumerate|eval|execfile|file|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|intern|isinstance|issubclass|iter|len|list|locals|long|map|max|memoryview|min|next|object|oct|open|ord|pow|property|range|raw_input|reduce|reload|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|unichr|unicode|vars|xrange|zip)\b/,boolean:/\b(?:True|False|None)\b/,number:/(?:\b(?=\d)|\B(?=\.))(?:0[bo])?(?:(?:\d|0x[\da-f])[\da-f]*(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?j?\b/i,operator:/[-+%=]=?|!=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,punctuation:/[{}[\];(),.:]/},Prism.languages.python["string-interpolation"].inside.interpolation.inside.rest=Prism.languages.python,Prism.languages.py=Prism.languages.python; diff --git a/static/pdf/distance_metrics_refresher.pdf b/static/pdf/distance_metrics_refresher.pdf new file mode 100644 index 0000000..ad738bd Binary files /dev/null and b/static/pdf/distance_metrics_refresher.pdf differ diff --git a/themes/cupper-hugo-theme/.github/ISSUE_TEMPLATE/bug_report.md b/themes/cupper-hugo-theme/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..dd84ea7 --- /dev/null +++ b/themes/cupper-hugo-theme/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/themes/cupper-hugo-theme/.github/ISSUE_TEMPLATE/feature_request.md b/themes/cupper-hugo-theme/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..bbcbbe7 --- /dev/null +++ b/themes/cupper-hugo-theme/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/themes/cupper-hugo-theme/.gitignore b/themes/cupper-hugo-theme/.gitignore new file mode 100644 index 0000000..c33a9a7 --- /dev/null +++ b/themes/cupper-hugo-theme/.gitignore @@ -0,0 +1,2 @@ +exampleSite/public/ +resources/ \ No newline at end of file diff --git a/themes/cupper-hugo-theme/LICENSE b/themes/cupper-hugo-theme/LICENSE new file mode 100644 index 0000000..ae21e33 --- /dev/null +++ b/themes/cupper-hugo-theme/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2017 Heydon Pickering +Copyright (c) 2019 Zachary Betz + +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. diff --git a/themes/cupper-hugo-theme/README.md b/themes/cupper-hugo-theme/README.md new file mode 100644 index 0000000..474b365 --- /dev/null +++ b/themes/cupper-hugo-theme/README.md @@ -0,0 +1,109 @@ +# Cupper + +[![Netlify Status](https://api.netlify.com/api/v1/badges/bc8c4e51-37ee-419d-ad4f-b378010ee546/deploy-status)](https://app.netlify.com/sites/cupper-hugo-theme/deploys) + +An accessibility-friendly Hugo theme, ported from the [original Cupper](https://github.com/ThePacielloGroup/cupper) project. + +## Table of contents + +- [Demo](#demo) +- [Minimum Hugo version](#minimum-hugo-version) +- [Installation](#installation) +- [Updating](#updating) +- [Run example site](#run-example-site) +- [Configuration](#configuration) +- [Logo](#logo) +- [Favicons](#favicons) +- [Shortcodes](#shortcodes) +- [Syntax highlighting](#syntax-highlighting) +- [Disable toc for a blog post](#disable-toc-for-a-blog-post) +- [Localization](#localization) +- [Custom CSS and JS](#custom-css-and-js) +- [Getting help](#getting-help) +- [Credits](#credits) + +## Demo + +https://cupper-hugo-theme.netlify.com/ + +## Minimum Hugo version + +Hugo version `0.60.1` or higher is required. View the [Hugo releases](https://github.com/gohugoio/hugo/releases) and download the binary for your OS. + +## Installation + +From the root of your site: + +``` +git submodule add https://github.com/zwbetz-gh/cupper-hugo-theme.git themes/cupper-hugo-theme +``` + +## Updating + +From the root of your site: + +``` +git submodule update --remote --merge +``` + +## Run example site + +From the root of `themes/cupper-hugo-theme/exampleSite`: + +``` +hugo server --themesDir ../.. +``` + +## Configuration + +Copy `config.yaml` from the [`exampleSite`](https://github.com/zwbetz-gh/cupper-hugo-theme/tree/master/exampleSite), then edit as desired. + +## Logo + +Place your SVG logo at `static/images/logo.svg`. If you don't provide a logo, then the default theme logo will be used. + +## Favicons + +Upload your image to [RealFaviconGenerator](https://realfavicongenerator.net/) then copy-paste the generated favicon files under `static`. + +## Shortcodes + +See the [full list of supported shortcodes](https://cupper-hugo-theme.netlify.com/cupper-shortcodes/). + +## Syntax highlighting + +Syntax highlighting is provided by [Prism](https://prismjs.com/). See this [markdown code fences example](https://cupper-hugo-theme.netlify.com/cupper-shortcodes/#syntax-highlighting). + + +## Disable toc for a blog post + +Blog posts that have two or more subheadings (`

`s) automatically get a table of contents. To disable this set `toc` to `false`. For example: + +``` +--- +title: "My page with a few headings" +toc: false +--- +``` + +## Localization + +The strings in the templates of this theme can be localized. Make a copy of `/i18n/en.yaml` to `/i18n/.yaml`, and translate one by one, changing the `translation` field. + +[Here is a tutorial that goes more in depth about this.](https://regisphilibert.com/blog/2018/08/hugo-multilingual-part-2-i18n-string-localization/) + +## Custom CSS and JS + +You can provide an optional list of custom CSS files, which must be placed inside the `static` dir. These will load after the theme CSS loads. So, `static/css/custom_01.css` translates to `css/custom_01.css`. + +You can provide an optional list of custom JS files, which must be placed inside the `static` dir. These will load after the theme JS loads. So, `static/js/custom_01.js` translates to `js/custom_01.js`. + +See the [example site config file](https://github.com/zwbetz-gh/cupper-hugo-theme/blob/master/exampleSite/config.yaml) for sample usage. + +## Getting help + +If you run into an issue that isn't answered by this documentation or the [`exampleSite`](https://github.com/zwbetz-gh/cupper-hugo-theme/tree/master/exampleSite), then visit the [Hugo forum](https://discourse.gohugo.io/). The folks there are helpful and friendly. **Before** asking your question, be sure to read the [requesting help guidelines](https://discourse.gohugo.io/t/requesting-help/9132). + +## Credits + +Thank you to [Heydon Pickering](http://www.heydonworks.com) and [The Paciello Group](https://www.paciellogroup.com/) for creating the original Cupper project. diff --git a/themes/cupper-hugo-theme/archetypes/default.md b/themes/cupper-hugo-theme/archetypes/default.md new file mode 100644 index 0000000..d677d28 --- /dev/null +++ b/themes/cupper-hugo-theme/archetypes/default.md @@ -0,0 +1,6 @@ +--- +title: "{{ replace .Name "-" " " | title }}" +date: {{ .Date }} +tags: [] +--- + diff --git a/themes/cupper-hugo-theme/assets/css/search.css b/themes/cupper-hugo-theme/assets/css/search.css new file mode 100644 index 0000000..77a424b --- /dev/null +++ b/themes/cupper-hugo-theme/assets/css/search.css @@ -0,0 +1,8 @@ +#search { + height: 50px; + width: 100%; + padding: 8px; + border: 2px solid; + line-height: 1.6; + font-size: 1.25rem; +} diff --git a/themes/cupper-hugo-theme/assets/css/template-styles.css b/themes/cupper-hugo-theme/assets/css/template-styles.css new file mode 100644 index 0000000..9ce3042 --- /dev/null +++ b/themes/cupper-hugo-theme/assets/css/template-styles.css @@ -0,0 +1,1036 @@ +/* Fonts */ +@font-face { + font-family: 'Miriam Libre'; + src: url('{{ "css/fonts/miriamlibre-bold.woff2" | absURL }}') format('woff2'), url('{{ "css/fonts/miriamlibre-bold.woff" | absURL }}') format('woff'); + font-weight: bold; + font-style: normal; +} +*, +*::before, +*::after { + font-family: inherit; + background-color: inherit; + color: inherit; + margin: 0; + padding: 0; + box-sizing: border-box; +} +html { + font-size: calc(1em + 0.33vw); + font-family: Arial, Helvetica Neue, sans-serif; + line-height: 1.5; + color: #111; + background-color: #fefefe; +} +template { + display: none !important; +} +* + * { + margin-top: 2.25rem; +} +br, +dt, +dd, +th, +td, +option, +[hidden] + *, +li + li, +body, +.main-and-footer { + margin-top: 0; +} +p + p { + margin-top: 0.75rem; +} +.priority { + margin-top: 0; +} +a { + text-decoration: none; + border-bottom: 1px solid; +} +abbr { + text-decoration: none; + cursor: help; + border-bottom: 1px dashed; +} +img { + max-width: 100%; + max-height: 50vh; +} +.img-link { + border-bottom: none; +} +p img { + margin: 0.75rem 0; +} +figure p img { + margin: 0; +} +:focus:not([tabindex="-1"]), +[data-expands]:focus svg, +.patterns a:focus .text, +[for="themer"] :focus + [aria-hidden] { + outline: 4px solid #999; +} +a { + outline-offset: 2px; +} + +/* Katex math typesetting */ +.katex * { + margin-top: 0; + background-color: transparent; +} + +/* Fix for IE :( */ +[tabindex="-1"]:focus, +div:not([tabindex]):focus { + outline: none; +} +[hidden] { + display: none; +} + +/* Skip link */ +[href="#main"] { + display: block; + width: 100%; + padding: 0.75rem; + color: #fefefe; + background: #000; + position: absolute; + top: -3rem; + text-align: center; + z-index: 1; +} +[href="#main"]:focus { + top: 0; + outline: none; +} + +/* Text styles */ +h1, +h2, +h3, +h4 { + font-family: Miriam Libre, serif; + line-height: 1.125; +} +h1 { + font-size: 2rem; +} +h2 { + font-size: 1.66rem; +} +h3 { + font-size: 1.25rem; +} +h4, +h5 { + font-size: 1rem; + font-family: PT Sans, sans-serif; +} +h5 { + font-size: 0.85rem; + text-transform: uppercase; +} +kbd { + line-height: 1; + font-size: 0.66rem; + padding: 0.1rem 0.33rem; + border-radius: 0.25rem; + border: 2px solid; + box-shadow: 0.125rem 0.125rem 0 #111; + vertical-align: 0.33em; +} +pre, +.file-tree { + overflow-x: auto; + padding: 1.5rem; + border: 1px solid; +} +code { + font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; + font-size: 0.85em; +} +.cmd { + padding: 0.75rem; + background: #111; +} +.cmd code { + color: #fefefe; + white-space: pre-wrap; +} +.cmd code::before { + content: '$'; + font-weight: bold; + padding-right: 0.25em; +} + +/* Lists */ +main ul, +main ol { + margin-left: 2.25rem; +} +main li + li { + margin-top: 0.5rem; +} +main ul ul, main ol ol, main li ul, main li ol { + margin-top: 0.5rem; +} +ol ol { + list-style: lower-latin; +} +ol ol ol { + list-style: lower-roman; +} +main dt { + font-weight: bold; +} +main dd { + padding-left: 2rem; +} +dd ul { + margin-left: 0; +} +dd li + li { + margin: 0; +} + +/* Blockquotes */ +blockquote { + border-left: 0.5rem solid; + padding-left: 0.75rem; +} +blockquote .author { + font-size: 0.85rem; +} + +/* Buttons */ +button { + font-size: 1.25rem; + border-radius: 0.33em; + font-family: inherit; + background: #111; + color: #fefefe; + padding: 0.75rem; + border: 0; +} +[data-launch] { + font-size: 0.66rem !important; + padding: 0.5rem !important; + margin-top: 0 !important; + border-radius: 0 !important; + border-top-left-radius: 0.33rem !important; + box-shadow: none !important; + background: #111 !important; + color: #fefefe !important; + position: absolute !important; + right: 0 !important; + bottom: 0 !important; +} + +/* Forms */ +label { + display: inline-block; + font-weight: bold; +} +[for="themer"] { + background: #111; + border-radius: 0.33em; + color: #fefefe; + padding: 0.25em 0.75em; + margin: 0.5rem; +} +[for="themer"] [aria-hidden]::before { + content: 'off'; +} +[for="themer"] :checked + [aria-hidden]::before { + content: 'on'; +} + +/* Tables */ +table { + text-align: left; + table-layout: fixed; + width: 100%; + border-collapse: collapse; +} +th, +td { + border: 2px solid; + padding: 0.5rem; + line-height: 1.25; + margin: 0; +} +th { + font-weight: bold; +} +th:empty { + border: 0; +} + +/* Tested using... table */ +.tested { + text-align: center; + border: 1px solid #111; +} +.tested tr { + display: flex; + flex-flow: row wrap; +} +.tested td, .tested th { + vertical-align: middle; + overflow: hidden; + flex: 1 0 auto; + border: 1px solid #111; +} +.tested th { + width: 100%; + background-color: #111; + color: #fefefe; + outline-color: #111; +} +.tested img { + max-width: 3rem; +} +.tested span { + display: block; + margin: 0; +} +.tested .additional { + font-size: 0.85rem; +} +caption { + font-size: 1.125rem; + padding-bottom: 0.25rem; + font-style: italic; +} + +/* Page structure */ +.wrapper { + position: relative; + margin-top: 0; + overflow-x: hidden; +} +.intro-and-nav { + font-size: 0.8rem; + width: 15rem; + height: 100vh; + position: fixed; + top: 0; + left: 0; + border-right: 2px solid; +} +.intro-and-nav > div { + padding: 2.25rem; + display: flex; + flex-direction: column; + height: 100%; +} +.intro { + flex: 0 0 auto; +} +.patterns { + flex: 1 1 auto; +} +.logo { + border: 0; +} +.logo img { + width: 100%; + max-width: 12rem; +} +.library-desc { + margin-top: 0.5rem; + margin-left: auto; + margin-right: auto; + max-width: 25rem; +} +.main-and-footer { + margin-left: 15rem; +} +.main-and-footer > div { + max-width: 40rem; + margin: 0 auto; + padding: 2.25rem; +} +[role="contentinfo"] { + font-size: 0.85rem; + margin-top: 4rem; + text-align: center; +} + +/* Patterns navigation */ +.patterns { + overflow: auto; + margin-top: 1.5rem; + min-width: max-content; +} +.patterns * { + margin-top: 0; +} +.patterns h3 { + font-size: 1rem; +} +.patterns h3 + ul { + margin-top: 0.75rem; +} +.patterns li { + line-height: 1.125; + list-style: none; +} +.patterns li + li { +} +.patterns ul ul { + margin-left: 0.75rem; +} +.pattern a { + border: 0; + display: flex; + flex-wrap: nowrap; + align-items: baseline; + font-weight: bold; + padding: 0 1rem; + padding-top: 0.5em; + padding-bottom: 0.5em; +} +.pattern a:focus { + outline: none; +} +.pattern span { + margin-left: 0.125rem; +} + +/* After */ +.pattern [aria-current] { + background-color: #111; + clip-path: polygon(0% 0%, 90% 0%, 100% 50%, 90% 100%, 0% 100%); + color: #fefefe; +} + +/* Menu button */ +#menu-button { + display: none; + width: 100%; + text-align: center; +} +#menu-button:focus { + outline: none; + box-shadow: inset 0 0 0 0.25rem #999; +} + +/* Tables of contents */ +.toc { + font-size: 0.85rem; + border: 1px solid; + padding: 1.5rem; +} +.toc h2 { + font-size: 1rem; +} +.toc ol { + margin-left: 0.75rem; + margin-top: 0.5rem; +} + +/* Disqus Comments */ +#disqus-container { + text-align: center; +} +#disqus-button { + width: 100%; + padding: 0.25em 0.75em; +} +#disqus-comments { + display: none; +} + +/* Pattern lists */ +.patterns-list { + list-style: none; + margin-left: 0; +} +.patterns-list h2 { + font-size: 1.25rem; + line-height: 1.6; +} +.patterns-list li + li { + margin-top: 1rem; + padding-top: 1rem; + border-top: 2px solid; +} +.patterns-list a { + border: 0; +} + +/* Tags */ +.tags { + margin-top: 0; + font-size: 0.85rem; +} +.tags * { + display: inline; + margin: 0; +} +.tags strong { + margin-right: 0.25rem; +} +.tags li { + white-space: nowrap; + margin: 0 0.25rem 0 0; +} + +/* Date */ +.date { + margin-top: 0; + font-size: 0.85rem; +} + +/* Notes and warnings */ +.note { + border-left: 0.5rem solid; + font-size: 0.85rem; +} +.note .sign { + height: 2.25rem; + width: 2.25rem; +} +.note > div { + margin-left: 0.75rem; +} +.note > div > img:first-child { + height: 1.5rem; +} +.note > div >:first-child + * { + margin-top: 0; +} +.note.warning { + border-left: 0; + background-image: url({{ "css/images/stripe.svg" | absURL }}); + background-size: 0.5rem auto; + background-repeat: repeat-y; +} +.note.warning > div { + margin-left: 1.25rem; +} + +/* Tick lists */ +.ticks li { + list-style: none; + position: relative; +} +.ticks li::before { + content: ''; + display: inline-block; + width: 1rem; + height: 1rem; + margin-right: 0.25rem; + background-image: url({{ "css/images/icon-tick.svg" | absURL }}); + background-size: 100% auto; + position: absolute; + left: -1.25rem; + top: 0.25rem; +} + +/* Figures */ +figure { + text-align: center; +} +figcaption { + font-size: 0.85rem; + font-style: italic; + margin-top: 0.5rem; +} +main { + display: block; + counter-reset: fig; + min-height: 100vh; +} +figcaption::before { + counter-increment: fig; + content: 'Figure ' counter(fig) ':\0020'; + font-weight: bold; +} + +/* Code blocks */ +pre[class*=language-] { + background: none; + margin-top: 2.25rem; + margin-bottom: 0; + overflow-y: hidden; + overflow-x: auto; +} +code[class*="language-"], pre[class*="language-"] { + text-shadow: none; + /* filter: grayscale(100%); */ +} +pre[class*=language-][data-line] { + padding: 1em 0 0 2.25rem; +} +pre[class*=language-] code * { + margin-top: 0 !important; +} +[data-codeblock-shortcode], .code-annotated code { + display: inline-block; + margin-top: -1rem; +} +.code-annotated { + overflow-y: hidden; + overflow-x: auto; + padding: 1.5rem; + border: 1px solid; + white-space: pre; + counter-reset: annotation; +} +.highlight { + background: #ddd; + padding: 0.0625rem 0.33rem; + border-radius: 0.5rem; +} +.numbered .highlight::after, +.code-annotated.numbered + ol li::before { + counter-increment: annotation; + content: counter(annotation); + font-weight: bold; + font-size: 0.5rem; + background: #111; + color: #fefefe; + border-radius: 1rem; + margin-left: 0.25rem; + padding: 0.125em 0.5em; + vertical-align: 0.33em; +} +.code-annotated.numbered + ol { + list-style: none; + counter-reset: annotation; +} +.code-annotated.numbered + ol li::before { + font-size: 0.66em; + margin-right: 0.33em; + vertical-align: 0.25em; +} + +/* File tree lists */ +.file-tree { + overflow-x: auto; +} +.file-tree ul { + font-family: Courier, monospace; + margin: 0; + padding: 0; + padding-left: 3rem; + list-style: none; + line-height: 1.25; + position: relative; +} +.file-tree > ul { + padding-left: 0; + overflow-x: auto; + overflow-y: hidden; +} +.file-tree li { + background: #fefefe; + position: relative; + white-space: nowrap; +} +.file-tree li + li { + margin-top: 0; +} +.file-tree li:not(:last-child) > ul::before { + content: '\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020\2502\0020'; + position: absolute; + left: 0; + top: 0; + bottom: 0; + width: 1em; + white-space: normal; +} +.file-tree li::before { + content: '\251C\2500\2500\0020'; +} +.file-tree li:last-child::before { + content: '\2514\2500\2500\0020'; +} + +/* Expandable sections */ +.expandable-section { + border-top: 1px solid; + border-bottom: 1px solid; + padding: 0.75rem 0; +} +[id^="js-expandable-"] { + margin: 0; + padding: 1.5rem 0 0.75rem; +} +@media screen { + .expandable-section + .expandable-section { + margin-top: 0; + border-top: 0; + } +} +[data-expands] { + text-align: left; + color: #111; + border: 0; + background: none; + width: 100%; + padding: 0; + margin: 0; + display: flex; + align-items: center; + justify-content: space-between; + cursor: pointer; +} +[data-expands] svg { + margin-top: 0; + width: 1em; + height: 1em; +} +[data-expands][aria-expanded="true"] svg .up-strut { + display: none; +} +[data-expands]:focus { + outline: none; +} +p:empty { + display: none; +} +*:not(p) + p:empty + p { + margin-top: 2.25rem; +} + +/* WCAG and principles */ +.wcag li { + font-size: 0.85em; +} +.principles p { + font-size: 0.85rem; + margin-top: 0.75rem; +} +.principles.with-desc > li + li, +.wcag.with-desc > li + li { + border-top: 1px solid; + margin-top: 0.75rem; + padding-top: 0.75rem; +} + +/* Site errors */ +.site-error { + padding: 1.5rem; + background: #efefef; +} +.site-error strong { + color: #C83737; +} + +/* SVG icons */ +a svg, +button svg, +h1 svg, +th svg, +li > svg { + height: 0.75em; + width: 0.75em; + margin-right: 0.25em; +} +h1 svg { + margin-right: 0; + width: 0.85em; + height: 0.85em; +} +.wcag-icon { + width: 1.25em; +} +.bookmark-icon { + vertical-align: middle; +} +.link-icon { + width: 0.75em; + height: 0.75em; +} +.tags svg, .link-icon { + vertical-align: middle; +} +.balloon-icon { + width: 0.75em; + margin-right: 0; +} +.print { + white-space: nowrap; + font-style: normal; +} + +/* Color palettes */ +.colors { + display: flex; + height: 5rem; + margin: -0.25rem; + list-style: none; + flex-wrap: nowrap; +} +.colors li { + margin: 0.25rem; + flex: 1 0 auto; + position: relative; +} +.colors span { + line-height: 1; + background-color: #111; + color: #fefefe; + font-size: 0.75rem; + padding: 0.25rem; + position: absolute; + bottom: 0.25rem; + right: 0.25rem; +} + +/* Cross references */ +.pattern-link { + font-weight: bold; +} + +/* Inline demos */ +.demo-inner { + border-top: 1px solid; + border-bottom: 1px solid; + padding: 1.5rem 0; + position: relative; +} +[id^="js-demo-"] { + all: initial; + display: block; +} + +/* Section links */ +.h2-container { + position: relative; + font-size: 1.66rem; +} +.h2-container a { + position: absolute; + margin-top: 0; + top: 0; + line-height: 1; + left: -1em; + border: 0; +} + +/* Single page layout */ +.wrapper.print-version .main-and-footer { + margin-left: 0; +} +.wrapper.print-version .intro-and-nav { + position: static; + border: 0; + width: auto; + text-align: center; + display: flex; + align-items: center; + justify-content: center; +} +.wrapper.print-version main { + min-height: 0; +} +.wrapper.print-version .library-desc { + font-size: 1rem; +} +.wrapper.print-version .intro-and-nav > div { + height: auto; +} +.wrapper.print-version #patterns-list { + margin-left: 0; + margin-top: 1.5rem; + display: block; +} +.wrapper.print-version .toc { + font-size: 1rem; +} +.wrapper.print-version .toc h2 { + font-size: 1.66rem; +} +.wrapper.print-version #patterns-list h3 { + font-size: 1.25rem; +} +.wrapper.print-version .patterns { + margin-top: 0; +} +.pattern-section:not(:last-child) { + padding-bottom: 2.25rem; + border-bottom: 2px solid; +} +.pattern-section h1 { + padding: 0 !important; +} + +/* Custom 404 */ +.custom-404 { + text-align: center; +} +.custom-404 * { + margin: 0; +} +.custom-404 svg { + max-width: 100%; +} + +/* Utilities */ +.vh { + clip: rect(1px, 1px, 1px, 1px); + height: 1px; + overflow: hidden; + position: absolute; + white-space: nowrap; + width: 1px; +} +.gallery { + display: flex; + justify-content: center; +} + +/* Media queries */ +@media screen and (max-width: 45em) { + body a { + hyphens: auto; + } + [role="banner"] { + position: static; + width: auto; + height: auto; + } + .intro { + text-align: center; + } + .intro-and-nav { + border-right: none; + } + .intro-and-nav > div { + padding: 1.5rem; + } + .main-and-footer { + margin: 0; + } + main { + min-height: auto; + } + #patterns-list { + margin-top: 0.5rem; + border: 1px solid; + } + .patterns h3 { + font-size: 1.5rem; + padding: 1.5rem 1rem 0.75rem; + } + .patterns li:not(.pattern) { + margin-top: 0; + } + .patterns ul ul { + margin: 0; + } + .patterns li { + margin-top: 0; + } + .pattern { + font-size: 1rem; + } + .pattern a { + padding: 1rem; + } + .pattern [aria-current] { + clip-path: none; + padding: 1rem; + } + .pattern + .pattern { + border-top: 1px solid; + margin-top: 0; + } + #menu-button { + display: block; + } + #patterns-list { + display: none; + } + .toc-link { + display: none; + } + [aria-expanded="true"] + #patterns-list { + display: block; + } + code { + word-break: break-all; + } +} +@media print { + .wrapper:not(.print-version) .intro-and-nav, + .wrapper:not(.print-version) [role="contentinfo"] { + display: none; + } + .main-and-footer { + margin-left: 0; + } + a { + border: 0; + } + main a::after { + content: " (" attr(href) ")"; + word-break: break-word; + } + main nav a::after { + content: ''; + } + .cmd code { + background: #fefefe; + color: #111; + } + pre code { + white-space: pre-wrap !important; + } + .expandable-section { + border: 0; + padding: 0; + } + .expandable-section + p { + margin-top: 0.75rem; + } + [id^="js-expandable-"] { + display: block; + } + [data-expands] svg { + display: none; + } + main *:not(.with-desc) { + page-break-inside: avoid; + } + .note.warning { + border-left: 0.5rem solid; + background: none; + } +} +@media (-ms-high-contrast: active) { + img[src*=".svg"] { + background: #fefefe; + padding: 0.5rem; + } + .ticks li::before { + content: 'βœ“'; + background: none; + width: auto; + top: 0; + } + .note.warning { + border-left: 0.5rem solid; + background: none; + } +} + +::-moz-selection { /* Code for Firefox */ + background: rgba(0,0,0,.8); + color: #fefefe; +} + +::selection { + background: rgba(0,0,0,.8); + color: #fefefe; +} diff --git a/themes/cupper-hugo-theme/assets/js/search.js b/themes/cupper-hugo-theme/assets/js/search.js new file mode 100644 index 0000000..f760b85 --- /dev/null +++ b/themes/cupper-hugo-theme/assets/js/search.js @@ -0,0 +1,21 @@ +(function () { + function onEvent() { + var filter = search.value.toUpperCase(); + var list = document.getElementById("list"); + var listItems = list.getElementsByTagName("li"); + for (i = 0; i < listItems.length; i++) { + var item = listItems[i]; + var text = item.innerText.toUpperCase(); + if (text.indexOf(filter) > -1) { + item.style.display = ""; + } else { + item.style.display = "none"; + } + } + } + + var search = document.getElementById("search"); + if (search) { + search.addEventListener("keyup", onEvent); + } +})(); diff --git a/themes/cupper-hugo-theme/assets/js/template-dom-scripts.js b/themes/cupper-hugo-theme/assets/js/template-dom-scripts.js new file mode 100644 index 0000000..368ddfb --- /dev/null +++ b/themes/cupper-hugo-theme/assets/js/template-dom-scripts.js @@ -0,0 +1,150 @@ +/* Expandable sections */ +(function () { + function toggle (button, target) { + var expanded = button.getAttribute('aria-expanded') === 'true'; + button.setAttribute('aria-expanded', !expanded); + target.hidden = !target.hidden; + } + + var expanders = document.querySelectorAll('[data-expands]'); + + Array.prototype.forEach.call(expanders, function (expander) { + var target = document.getElementById(expander.getAttribute('data-expands')); + + expander.addEventListener('click', function () { + toggle(expander, target); + }) + }) +}()); + +/* Menu button */ +(function () { + var button = document.getElementById('menu-button'); + if (button) { + var menu = document.getElementById('patterns-list'); + button.addEventListener('click', function() { + var expanded = this.getAttribute('aria-expanded') === 'true'; + this.setAttribute('aria-expanded', !expanded); + }) + } +}()); + +/* Persist navigation scroll point */ +(function () { + window.onbeforeunload = function () { + var patternsNav = document.getElementById('patterns-nav'); + if (patternsNav) { + var scrollPoint = patternsNav.scrollTop; + localStorage.setItem('scrollPoint', scrollPoint); + } + } + + window.addEventListener('DOMContentLoaded', function () { + if (document.getElementById('patterns-nav')) { + if (window.location.href.indexOf('patterns/') !== -1) { + document.getElementById('patterns-nav').scrollTop = parseInt(localStorage.getItem('scrollPoint')); + } else { + document.getElementById('patterns-nav').scrollTop = 0; + } + } + }) +}()); + +{{ if not .Site.Params.hideHeaderLinks }} + /* Add "link here" links to

headings */ + (function () { + var headings = document.querySelectorAll('main > h2'); + + Array.prototype.forEach.call(headings, function (heading) { + var id = heading.getAttribute('id'); + + if (id) { + var newHeading = heading.cloneNode(true); + newHeading.setAttribute('tabindex', '-1'); + + var container = document.createElement('div'); + container.setAttribute('class', 'h2-container'); + container.appendChild(newHeading); + + heading.parentNode.insertBefore(container, heading); + + var link = document.createElement('a'); + link.setAttribute('href', '#' + id); + link.innerHTML = ''; + + container.appendChild(link); + + heading.parentNode.removeChild(heading); + } + }) + }()); +{{ end }} + +/* Enable scrolling by keyboard of code samples */ +(function () { + var codeBlocks = document.querySelectorAll('pre, .code-annotated'); + + Array.prototype.forEach.call(codeBlocks, function (block) { + if (block.querySelector('code')) { + block.setAttribute('role', 'region'); + block.setAttribute('aria-label', 'code sample'); + if (block.scrollWidth > block.clientWidth) { + block.setAttribute('tabindex', '0'); + } + } + }); +}()); + +/* Switch and persist theme */ +(function () { + var checkbox = document.getElementById('themer'); + + function persistTheme(val) { + localStorage.setItem('darkTheme', val); + } + + function applyDarkTheme() { + var rules = [ + '.intro-and-nav, .main-and-footer { filter: invert(100%); }', + '* { background-color: inherit; }', + 'img:not([src*=".svg"]), .colors, iframe, .demo-container { filter: invert(100%); }' + ]; + rules.forEach(function(rule) { + document.styleSheets[0].insertRule(rule); + }) + } + + function clearDarkTheme() { + for (let i = 0; i < document.styleSheets[0].cssRules.length; i++) { + document.styleSheets[0].deleteRule(i); + } + } + + checkbox.addEventListener('change', function () { + if (this.checked) { + applyDarkTheme(); + persistTheme('true'); + } else { + clearDarkTheme(); + persistTheme('false'); + } + }); + + function showTheme() { + if (localStorage.getItem('darkTheme') === 'true') { + applyDarkTheme(); + checkbox.checked = true; + } + } + + function showContent() { + document.body.style.visibility = 'visible'; + document.body.style.opacity = 1; + } + + window.addEventListener('DOMContentLoaded', function () { + showTheme(); + showContent(); + }); + +}()); diff --git a/themes/cupper-hugo-theme/data/principles.json b/themes/cupper-hugo-theme/data/principles.json new file mode 100644 index 0000000..9864138 --- /dev/null +++ b/themes/cupper-hugo-theme/data/principles.json @@ -0,0 +1,149 @@ +{ + "title": "Inclusive Design Principles", + "description": "

These Inclusive Design Principles are about putting people first. It's about designing for the needs of people with permanent, temporary, situational, or changing disabilities β€” all of us really.

They are intended to give anyone involved in the design and development of websites and applications - designers, user experience professionals, developers, product owners, idea makers, innovators, artists and thinkers - a broad approach to inclusive design.

For more background, read about the Inclusive Design Principles on the TPG blog.

", + "authors": [ + "LΓ©onie Watson", + "Henny Swan", + "Ian Pouncey", + "Heydon Pickering" + ], + "principles": [ + { + "title": "Provide comparable experience", + "strapline": "Ensure your interface provides a comparable experience for all so people can accomplish tasks in a way that suits their needs without undermining the quality of the content.", + "description": "Whether out of circumstance, choice, or context people are diverse. As people use different approaches and tools to read and operate interfaces, what the interface offers each user should be comparable in value, quality, and efficiency.", + "examples": [ + { + "title": "Content for alternatives", + "description": "Having a basic alternative, whether it's alt text, a transcript, audio description, or sign language, makes the content accessible but to be equivalent it needs to capture the essence of the original." + }, + { + "title": "Ergonomic features", + "description": "Providing synchronized closed captions makes your video accessible. But making them customizable, color coded, and repositionable provides a more comparable experience." + }, + { + "title": "Notifications", + "description": "Notifications that appear in an interface are visually obvious but require proactive discovery by screen reader users. A comparable experience for blind users, can be achieved by using a live region. The notification then requires no explicit action on the part of the user." + } + ] + }, + { + "title": "Consider situation", + "strapline": "People use your interface in different situations. Make sure your interface delivers a valuable experience to people regardless of their circumstances.", + "description": "People are first time users, established users, users at work, users at home, users on the move, and users under pressure. All of these situations can have an impact. For those who already find interaction challenging, such as those with disabilities, this impact may make usage particularly difficult.", + "examples": [ + { + "title": "Colour contrast", + "description": "When using an interface outdoors, good contrast lessens the impact of bright sunshine." + }, + { + "title": "Context sensitive help", + "description": "Users may need help when they first encounter a complex form or interaction. This help may become redundant, even distracting, as a user becomes more familiar with the form or interaction. Context sensitive help provides the user with choice as to when they access help and better control over the page." + }, + { + "title": "Captions on the go", + "description": "You're aware that the video content you are providing will be consumed on mobile devices, which may be in public spaces where people might prefer to consume the content without being antisocial. For smaller viewports, sound is switched off and captions activated by default." + } + ] + }, + { + "title": "Be consistent", + "strapline": "Use familiar conventions and apply them consistently.", + "description": "Familiar interfaces borrow from well-established patterns. These should be used consistently within the interface to reinforce their meaning and purpose. This should be applied to functionality, behavior, editorial, and presentation. You should say the same things in the same way and users should be able to do the same things in the same way.", + "examples": [ + { + "title": "Consistent design patterns", + "description": "Use consistent web and platform design patterns to help build familiarity and understanding." + }, + { + "title": "Consistent editorial", + "description": "Use plain language consistently across platforms including editorial that is relied on by screen reader users such as text alternatives, headings, labels for buttons and so on. Keeping editorial style consistent is also important e.g. making sure the top of the article always has a clearly marked summary paragraph, making sure bullets always start with a bolded definition." + }, + { + "title": "Consistent page architecture", + "description": "Use consistent page architecture across templates to help people scan and navigate key content." + } + ] + }, + { + "title": "Give control", + "strapline": "Ensure people are in control. People should be able to access and interact with content in their preferred way.", + "description": "Do not suppress or disable the ability to change standard browser and platform settings such as orientation, font size, zoom, and contrast. In addition, avoid content changes that have not been initiated by the user unless there is a way to control it.", + "examples": [ + { + "title": "Scrolling control", + "description": "'Infinite scrolling' can be problematic, especially for users navigating by keyboard because they can't get past the stream of refreshing content. Give the option to turn off this feature and replace it with a 'load more' button." + }, + { + "title": "Make it stop", + "description": "Some users find that animations or parallax scrolling cause nausea, and others find them plain distracting. Where they play automatically, they should at least be easy to stop, by providing prominent playback controls." + }, + { + "title": "Allow zoom", + "description": "There are many reasons why a user may want to operate the pinch-to-zoom gesture on their touch device. Make sure it is not suppressed, and that the content does not get obscured when it is put to use." + } + ] + }, + { + "title": "Offer choice", + "strapline": "Consider providing different ways for people to complete tasks, especially those that are complex or non standard.", + "description": "There is often more than one way to complete a task. You cannot assume what someone's preferred way might be. By providing alternatives for layout and task completion, you offer people choices that suit them and their circumstances at the time.", + "examples": [ + { + "title" : "Multiple ways to complete an action", + "description": "Where appropriate, provide multiple ways to complete an action. On mobile swipe to delete an item can be supported together with an edit button that allows you to select items then delete. An example of this is in iOS mail." + }, + { + "title": "Layout", + "description": "Where there are long lists of content consider offering a grid or list layout option. This supports people who may want larger images on screen or smaller rows." + }, + { + "title": "Accessible alternatives", + "description": "Alternative ways of presenting data, such as data tables for info graphics, should be available to all users, as an option rather than a hidden link just for screen reader users. Accessible alternatives can benefit not just a specific target group but all users as long as we offer the choice." + } + ] + }, + { + "title": "Prioritise content", + "strapline": "Help users focus on core tasks, features, and information by prioritising them within the content and layout.", + "description": "Interfaces can be difficult to understand when core features are not clearly exposed and prioritised. A site or application may provide lots of information and functionality, but people should be able to focus on one thing at a time. Identify the core purpose of the interface, and then the content and features needed to fulfill that purpose.", + "examples": [ + { + "title": "Keep task focused", + "description": "Progressively reveal features and content when needed, not all in one go." + }, + { + "title": "Prioritising tasks", + "description": "An email application is principally for writing and reading email. The 'compose' button is, therefore, present on all screens, and early in the focus order. The inbox is prioritised over other lists of email, such as 'sent' and 'spam' messages. Less used features such as tagging or organizing email into folders appear later in the focus order, as they will generally only be used when the primary task of reading the email is complete." + }, + { + "title": "Prioritising content", + "description": "The primary content on a news article page is the story, therefore it should come before other content, both visually and in the source order. Related content, such as similar articles, should follow it, and unrelated content after that." + }, + { + "title": "Prioritising editorial", + "description": "Editorial for links, headings and buttons should use plain language and put the primary text first. This applies to both visible and hidden text. This makes the text easy to scan both visually and audibly for screen reader users. Plain language also benefits non native speakers and is easier to translate." + } + ] + }, + { + "title": "Add value", + "strapline": "Consider the value of features and how they improve the experience for different users.", + "description": "Features should add value to the user experience by providing efficient and diverse ways to find and interact with content. Consider device features such as voice, geolocation, camera and vibration API's, and how integration with connected devices or a second screen could provide choice.", + "examples": [ + { + "title": "Integration with connected devices or second screen", + "description": "Using voice interfaces to control multimedia, search for content, output from music or TV adds value for people who struggle to use other interfaces." + }, + { + "title": "Integration with platform APIs", + "description": "Enhance functionality using platform features. The vibration API makes notifications more usable by deaf and hard of hearing people while the geolocation API makes it easier for people with mobility impairments to use location based services." + }, + { + "title": "Make task completion easier", + "description": "Add a 'Show password' button to input fields so users can verify they have correctly inputted text, or add touch identification for password protected areas." + } + ] + } + ] +} diff --git a/themes/cupper-hugo-theme/data/wcag.json b/themes/cupper-hugo-theme/data/wcag.json new file mode 100644 index 0000000..8192898 --- /dev/null +++ b/themes/cupper-hugo-theme/data/wcag.json @@ -0,0 +1,1427 @@ +[ + { + "ref_id": "1.1.1", + "title": "Non-text Content", + "description": "All non-text content that is presented to the user has a text alternative that serves the equivalent purpose, except for the situations listed below. (", + "url": "https://www.w3.org/TR/WCAG20/#text-equiv-all", + "level": "A", + "special_cases": [ + { + "type": "exception", + "title": "Controls, Input", + "description": "If non-text content is a control or accepts user input, then it has a name that describes its purpose. (Refer to Guideline 4.1 for additional requirements for controls and content that accepts user input.)" + }, + { + "type": "exception", + "title": "Time-Based Media", + "description": "If non-text content is time-based media, then text alternatives at least provide descriptive identification of the non-text content. (Refer to Guideline 1.2 for additional requirements for media.)" + }, + { + "type": "exception", + "title": "Test", + "description": "If non-text content is a test or exercise that would be invalid if presented in text, then text alternatives at least provide descriptive identification of the non-text content." + }, + { + "type": "exception", + "title": "Sensory", + "description": "If non-text content is primarily intended to create a specific sensory experience, then text alternatives at least provide descriptive identification of the non-text content." + }, + { + "type": "exception", + "title": "CAPTCHA", + "description": "If the purpose of non-text content is to confirm that content is being accessed by a person rather than a computer, then text alternatives that identify and describe the purpose of the non-text content are provided, and alternative forms of CAPTCHA using output modes for different types of sensory perception are provided to accommodate different disabilities." + }, + { + "type": "exception", + "title": "Decoration, Formatting, Invisible", + "description": "If non-text content is pure decoration, is used only for visual formatting, or is not presented to users, then it is implemented in a way that it can be ignored by assistive technology." + } + ], + "notes": null, + "references": [ + { + "title": "How to Meet 1.1.1", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-text-equiv-all" + }, + { + "title": "Understanding 1.1.1", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/text-equiv-all.html" + } + ] + }, + { + "ref_id": "1.2.1", + "title": "Audio-only and Video-only (Prerecorded", + "description": "For prerecorded audio-only and prerecorded video-only media, the following are true, except when the audio or video is a media alternative for text and is clearly labeled as such.", + "url": "https://www.w3.org/TR/WCAG20/", + "level": "A", + "special_cases": [ + { + "type": "all_true", + "title": "Prerecorded Audio-only", + "description": "An alternative for time-based media is provided that presents equivalent information for prerecorded audio-only content." + }, + { + "type": "all_true", + "title": "Prerecorded Video-only", + "description": "Either an alternative for time-based media or an audio track is provided that presents equivalent information for prerecorded video-only content" + } + ], + "notes": null, + "references": [ + { + "title": "How to Meet 1.2.1", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-media-equiv-av-only-alt" + }, + { + "title": "Understanding 1.2.1", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/media-equiv-av-only-alt.html" + } + ] + }, + { + "ref_id": "1.2.2", + "title": "Captions (Prerecorded)", + "description": "Captions are provided for all prerecorded audio content in synchronized media, except when the media is a media alternative for text and is clearly labeled as such", + "url": "https://www.w3.org/TR/WCAG20/#media-equiv-captions", + "level": "A", + "special_cases": null, + "notes": null, + "references": [ + { + "title": "How to Meet 1.2.2", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-media-equiv-captions" + }, + { + "title": "Understanding 1.2.2", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/media-equiv-captions.html" + } + ] + }, + { + "ref_id": "1.2.3", + "title": "Audio Description or Media Alternative (Prerecorded)", + "description": "An alternative for time-based media or audio description of the prerecorded video content is provided for synchronized media, except when the media is a media alternative for text and is clearly labeled as such", + "url": "https://www.w3.org/TR/WCAG20/#media-equiv-audio-desc", + "level": "A", + "special_cases": null, + "notes": null, + "references": [ + { + "title": "How to Meet 1.2.3", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-media-equiv-audio-desc" + }, + { + "title": "Understanding 1.2.3", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/media-equiv-audio-desc.html" + } + ] + }, + { + "ref_id": "1.2.4", + "title": "Captions (Live)", + "description": "Captions are provided for all live audio content in synchronized media", + "url": "https://www.w3.org/TR/WCAG20/#media-equiv-real-time-captions", + "level": "AA", + "special_cases": null, + "notes": null, + "references": [ + { + "title": "How to Meet 1.2.4", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-media-equiv-real-time-captions" + }, + { + "title": "Understanding 1.2.4", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/media-equiv-real-time-captions.html" + } + ] + }, + { + "ref_id": "1.2.5", + "title": "Audio Description (Prerecorded)", + "description": "Audio description is provided for all prerecorded video content in synchronized media", + "url": "https://www.w3.org/TR/WCAG20/#media-equiv-audio-desc-only", + "level": "AA", + "special_cases": null, + "notes": null, + "references": [ + { + "title": "How to Meet 1.2.5", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-media-equiv-audio-desc-only" + }, + { + "title": "Understanding 1.2.5", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/media-equiv-audio-desc-only.html" + } + ] + }, + { + "ref_id": "1.2.6", + "title": "Sign Language (Prerecorded)", + "description": "Sign language interpretation is provided for all prerecorded audio content in synchronized media.", + "url": "https://www.w3.org/TR/WCAG20/#media-equiv-sign", + "level": "AAA", + "special_cases": null, + "notes": null, + "references": [ + { + "title": "How to Meet 1.2.6", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-media-equiv-sign" + }, + { + "title": "Understanding 1.2.6", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/media-equiv-sign.html" + } + ] + }, + { + "ref_id": "1.2.7", + "title": "Extended Audio Description (Prerecorded)", + "description": "Where pauses in foreground audio are insufficient to allow audio descriptions to convey the sense of the video, extended audio description is provided for all prerecorded video content in synchronized media", + "url": "https://www.w3.org/TR/WCAG20/#media-equiv-extended-ad", + "level": "AAA", + "special_cases": null, + "notes": null, + "references": [ + { + "title": "How to Meet 1.2.7", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-media-equiv-extended-ad" + }, + { + "title": "Understanding 1.2.7", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/media-equiv-extended-ad.html" + } + ] + }, + { + "ref_id": "1.2.8", + "title": "Media Alternative (Prerecorded)", + "description": "An alternative for time-based media is provided for all prerecorded synchronized media and for all prerecorded video-only media", + "url": "https://www.w3.org/TR/WCAG20/#media-equiv-text-doc", + "level": "AAA", + "special_cases": null, + "notes": null, + "references": [ + { + "title": "How to Meet 1.2.8", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-media-equiv-text-doc" + }, + { + "title": "Understanding 1.2.8", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/media-equiv-text-doc.html" + } + ] + }, + { + "ref_id": "1.2.9", + "title": "Audio-only (Live)", + "description": " An alternative for time-based media that presents equivalent information for live audio-only content is provided.", + "url": "https://www.w3.org/TR/WCAG20/#media-equiv-live-audio-only", + "level": "AAA", + "special_cases": null, + "notes": null, + "references": [ + { + "title": "How to Meet 1.2.9", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-media-equiv-live-audio-only" + }, + { + "title": "Understanding 1.2.9", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/media-equiv-live-audio-only.html" + } + ] + }, + { + "ref_id": "1.3.1", + "title": "Info and Relationships", + "description": "Information, structure, and relationships conveyed through presentation can be programmatically determined or are available in text.", + "url": "https://www.w3.org/TR/WCAG20/#content-structure-separation-programmatic", + "level": "A", + "special_cases": null, + "notes": null, + "references": [ + { + "title": "How to Meet 1.3.1", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-content-structure-separation-programmatic" + }, + { + "title": "Understanding 1.3.1", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/content-structure-separation-programmatic.html" + } + ] + }, + { + "ref_id": "1.3.2", + "title": "Meaningful Sequence", + "description": "When the sequence in which content is presented affects its meaning, a correct reading sequence can be programmatically determined.", + "url": "https://www.w3.org/TR/WCAG20/#content-structure-separation-sequence", + "level": "A", + "special_cases": null, + "notes": null, + "references": [ + { + "title": "How to Meet 1.3.2", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-content-structure-separation-sequence" + }, + { + "title": "Understanding 1.3.2", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/content-structure-separation-sequence.html" + } + ] + }, + { + "ref_id": "1.3.3", + "title": "Sensory Characteristics", + "description": "Instructions provided for understanding and operating content do not rely solely on sensory characteristics of components such as shape, size, visual location, orientation, or sound", + "url": "https://www.w3.org/TR/WCAG20/#content-structure-separation-understanding", + "level": "A", + "special_cases": null, + "notes": [ + { + "content": "For requirements related to color, refer to Guideline 1.4." + } + ], + "references": [ + { + "title": "How to Meet 1.3.3", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-content-structure-separation-understanding" + }, + { + "title": "Understanding 1.3.3", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/content-structure-separation-understanding.html" + } + ] + }, + { + "ref_id": "1.4.1", + "title": "Use of Color", + "description": "Color is not used as the only visual means of conveying information, indicating an action, prompting a response, or distinguishing a visual element.", + "url": "https://www.w3.org/TR/WCAG20/#visual-audio-contrast-without-color", + "level": "A", + "special_cases": null, + "notes": [ + { + "content": "This success criterion addresses color perception specifically. Other forms of perception are covered in Guideline 1.3 including programmatic access to color and other visual presentation coding." + } + ], + "references": [ + { + "title": "How to Meet 1.4.1", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-visual-audio-contrast-without-color" + }, + { + "title": "Understanding 1.4.1", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-without-color.html" + } + ] + }, + { + "ref_id": "1.4.2", + "title": "Audio Control", + "description": "If any audio on a Web page plays automatically for more than 3 seconds, either a mechanism is available to pause or stop the audio, or a mechanism is available to control audio volume independently from the overall system volume level. (Level A)", + "url": "https://www.w3.org/TR/WCAG20/#visual-audio-contrast-dis-audio", + "level": "A", + "special_cases": null, + "notes": [ + { + "content": "Note: Since any content that does not meet this success criterion can interfere with a user's ability to use the whole page, all content on the Web page (whether or not it is used to meet other success criteria) must meet this success criterion. See Conformance Requirement 5: Non-Interference. (https://www.w3.org/TR/WCAG20/#cc5)" + } + ], + "references": [ + { + "title": "How to Meet 1.4.2", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-visual-audio-contrast-dis-audio" + }, + { + "title": "Understanding 1.4.2", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-dis-audio.html" + } + ] + }, + { + "ref_id": "1.4.3", + "title": "Contrast (Minimum)", + "description": " The visual presentation of text and images of text has a contrast ratio of at least 4.5:1, except for the following: ", + "url": "https://www.w3.org/TR/WCAG20/#visual-audio-contrast-contrast", + "level": "AA", + "special_cases": [ + { + "type": "exception", + "title": "Large Text", + "description": "Large-scale text and images of large-scale text have a contrast ratio of at least 3:1" + }, + { + "type": "exception", + "title": "Incidental", + "description": "Text or images of text that are part of an inactive user interface component, that are pure decoration, that are not visible to anyone, or that are part of a picture that contains significant other visual content, have no contrast requirement." + }, + { + "type": "exception", + "title": "Logotypes", + "description": "Text that is part of a logo or brand name has no minimum contrast requirement." + } + ], + "notes": null, + "references": [ + { + "title": "How to Meet 1.4.3", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-visual-audio-contrast-contrast" + }, + { + "title": "Understanding 1.4.3", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-contrast.html" + } + ] + }, + { + "ref_id": "1.4.4", + "title": "Resize text", + "description": "Except for captions and images of text, text can be resized without assistive technology up to 200 percent without loss of content or functionality", + "url": "https://www.w3.org/TR/WCAG20/#visual-audio-contrast-scale", + "level": "AA", + "special_cases": null, + "notes": null, + "references": [ + { + "title": "How to Meet 1.4.4", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-visual-audio-contrast-scale" + }, + { + "title": "Understanding 1.4.4", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-scale.html" + } + ] + }, + { + "ref_id": "1.4.5", + "title": "Images of Text", + "description": "If the technologies being used can achieve the visual presentation, text is used to convey information rather than images of text except for the following", + "url": "https://www.w3.org/TR/WCAG20/#visual-audio-contrast-text-presentation", + "level": "AA", + "special_cases": [ + { + "type": "exception", + "title": "Customizable", + "description": "The image of text can be visually customized to the user's requirements" + }, + { + "type": "exception", + "title": "Essential", + "description": "A particular presentation of text is essential to the information being conveyed" + } + ], + "notes": [ + { + "content": "Logotypes (text that is part of a logo or brand name) are considered essential" + } + ], + "references": [ + { + "title": "How to Meet 1.4.5", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-visual-audio-contrast-text-presentation" + }, + { + "title": "Understanding 1.4.5", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-text-presentation.html" + } + ] + }, + { + "ref_id": "1.4.6", + "title": "Contrast (Enhanced)", + "description": "The visual presentation of text and images of text has a contrast ratio of at least 7:1, except for the following: ", + "url": "https://www.w3.org/TR/WCAG20/#visual-audio-contrast7", + "level": "AAA", + "special_cases": [ + { + "type": "exception", + "title": "Large Text", + "description": "Large-scale text and images of large-scale text have a contrast ratio of at least 4.5:1" + }, + { + "type": "exception", + "title": "Text or images of text that are part of an inactive user interface component, that are pure decoration, that are not visible to anyone, or that are part of a picture that contains significant other visual content, have no contrast requirement.", + "description": "Incidental" + }, + { + "type": "exception", + "title": "Logotypes", + "description": "Text that is part of a logo or brand name has no minimum contrast requirement." + } + ], + "notes": null, + "references": [ + { + "title": "How to Meet 1.4.6", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-visual-audio-contrast7" + }, + { + "title": "Understanding 1.4.6", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast7.html" + } + ] + }, + { + "ref_id": "1.4.7", + "title": "Low or No Background Audio", + "description": "For prerecorded audio-only content that (1) contains primarily speech in the foreground, (2) is not an audio CAPTCHA or audio logo, and (3) is not vocalization intended to be primarily musical expression such as singing or rapping, at least one of the following is true: (Level AAA)", + "url": "https://www.w3.org/TR/WCAG20/#visual-audio-contrast-noaudio", + "level": "AAA", + "special_cases": [ + { + "type": "at_least_one", + "title": "No Background", + "description": "The audio does not contain background sounds." + }, + { + "type": "at_least_one", + "title": "Turn Off", + "description": "The background sounds can be turned off." + }, + { + "type": "at_least_one", + "title": "20 dB", + "description": "The background sounds are at least 20 decibels lower than the foreground speech content, with the exception of occasional sounds that last for only one or two seconds." + } + ], + "notes": [ + { + "content": "Per the definition of 'decibel,' background sound that meets this requirement will be approximately four times quieter than the foreground speech content." + } + ], + "references": [ + { + "title": "How to Meet 1.4.7", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-visual-audio-contrast-noaudio" + }, + { + "title": "Understanding 1.4.7" + } + ] + }, + { + "ref_id": "1.4.8", + "title": "Visual Presentation", + "description": "For the visual presentation of blocks of text, a mechanism is available to achieve the following:", + "url": "https://www.w3.org/TR/WCAG20/#visual-audio-contrast-visual-presentation", + "level": "AAA", + "special_cases": [ + { + "type": "all_true", + "title": "Foreground and background colors can be selected by the user." + }, + { + "type": "all_true", + "title": "Width is no more than 80 characters or glyphs (40 if CJK)" + }, + { + "type": "all_true", + "title": "Text is not justified (aligned to both the left and the right margins)." + }, + { + "type": "all_true", + "title": "Line spacing (leading) is at least space-and-a-half within paragraphs, and paragraph spacing is at least 1.5 times larger than the line spacing." + }, + { + "type": "all_true", + "title": "Text can be resized without assistive technology up to 200 percent in a way that does not require the user to scroll horizontally to read a line of text on a full-screen window." + } + ], + "notes": null, + "references": [ + { + "title": "How to Meet 1.4.8", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-visual-audio-contrast-visual-presentation" + }, + { + "title": "Understanding 1.4.8", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-visual-presentation.html" + } + ] + }, + { + "ref_id": "1.4.9", + "title": "Images of Text (No Exception)", + "description": "Images of text are only used for pure decoration or where a particular presentation of text is essential to the information being conveyed.", + "url": "https://www.w3.org/TR/WCAG20/", + "level": "AAA", + "special_cases": null, + "notes": [ + { + "content": "Logotypes (text that is part of a logo or brand name) are considered essential." + } + ], + "references": [ + { + "title": "How to Meet 1.4.9", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-visual-audio-contrast-text-images" + }, + { + "title": "Understanding 1.4.9", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-text-images.html" + } + ] + }, + { + "ref_id": "2.1.1", + "title": "Keyboard", + "description": "All functionality of the content is operable through a keyboard interface without requiring specific timings for individual keystrokes, except where the underlying function requires input that depends on the path of the user's movement and not just the endpoints", + "url": "https://www.w3.org/TR/WCAG20/#keyboard-operation-keyboard-operable", + "level": "A", + "special_cases": null, + "notes": [ + { + "content": "This exception relates to the underlying function, not the input technique. For example, if using handwriting to enter text, the input technique (handwriting) requires path-dependent input but the underlying function (text input) does not." + }, + { + "content": "This does not forbid and should not discourage providing mouse input or other input methods in addition to keyboard operation." + } + ], + "references": [ + { + "title": "How to Meet 2.1.1", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-keyboard-operation-keyboard-operable" + }, + { + "title": "Understanding 2.1.1", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/keyboard-operation-keyboard-operable.html" + } + ] + }, + { + "ref_id": "2.1.2", + "title": "No Keyboard Trap", + "description": "If keyboard focus can be moved to a component of the page using a keyboard interface, then focus can be moved away from that component using only a keyboard interface, and, if it requires more than unmodified arrow or tab keys or other standard exit methods, the user is advised of the method for moving focus away.", + "url": "https://www.w3.org/TR/WCAG20/#keyboard-operation-trapping", + "level": "A", + "special_cases": null, + "notes": [ + { + "content": "Since any content that does not meet this success criterion can interfere with a user's ability to use the whole page, all content on the Web page (whether it is used to meet other success criteria or not) must meet this success criterion. See Conformance Requirement 5: Non-Interference. (https://www.w3.org/TR/WCAG20/#cc5)" + } + ], + "references": [ + { + "title": "How to Meet 2.1.2", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-keyboard-operation-trapping" + }, + { + "title": "Understanding 2.1.2", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/keyboard-operation-trapping.html" + } + ] + }, + { + "ref_id": "2.1.3", + "title": "Keyboard (No Exception)", + "description": "All functionality of the content is operable through a keyboard interface without requiring specific timings for individual keystrokes.", + "url": "https://www.w3.org/TR/WCAG20/#keyboard-operation-all-funcs", + "level": "AAA", + "special_cases": null, + "notes": null, + "references": [ + { + "title": "How to Meet 2.1.3", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-keyboard-operation-all-funcs" + }, + { + "title": "Understanding 2.1.3", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/keyboard-operation-all-funcs.html" + } + ] + }, + { + "ref_id": "2.2.1", + "title": "Timing Adjustable", + "description": "For each time limit that is set by the content, at least one of the following is true:", + "url": "https://www.w3.org/TR/WCAG20/#time-limits-required-behaviors", + "level": "A", + "special_cases": [ + { + "type": "at_least_one", + "title": "Turn off", + "description": "The user is allowed to turn off the time limit before encountering it; or" + }, + { + "type": "at_least_one", + "title": "Adjust", + "description": "The user is allowed to adjust the time limit before encountering it over a wide range that is at least ten times the length of the default setting; or" + }, + { + "type": "at_least_one", + "title": "Extend", + "description": "The user is warned before time expires and given at least 20 seconds to extend the time limit with a simple action (for example, 'press the space bar'), and the user is allowed to extend the time limit at least ten times; or" + }, + { + "type": "at_least_one", + "title": "Real-time Exception", + "description": "The time limit is a required part of a real-time event (for example, an auction), and no alternative to the time limit is possible; or" + }, + { + "type": "at_least_one", + "title": "Essential Exception", + "description": "The time limit is essential and extending it would invalidate the activity; or" + }, + { + "type": "at_least_one", + "title": "20 Hour Exception", + "description": "The time limit is longer than 20 hours." + } + ], + "notes": [ + { + "content": "This success criterion helps ensure that users can complete tasks without unexpected changes in content or context that are a result of a time limit. This success criterion should be considered in conjunction with Success Criterion 3.2.1 (https://www.w3.org/TR/WCAG20/#consistent-behavior-receive-focus), which puts limits on changes of content or context as a result of user action." + } + ], + "references": [ + { + "title": "How to Meet 2.2.1", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-time-limits-required-behaviors" + }, + { + "title": "Understanding 2.2.1", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/time-limits-required-behaviors.html" + } + ] + }, + { + "ref_id": "2.2.2", + "title": "Pause, Stop, Hide", + "description": "For moving, blinking, scrolling, or auto-updating information, all of the following are true:", + "url": "https://www.w3.org/TR/WCAG20/#time-limits-pause", + "level": "A", + "special_cases": [ + { + "type": "all_true", + "title": "Moving, blinking, scrolling", + "description": "For any moving, blinking or scrolling information that (1) starts automatically, (2) lasts more than five seconds, and (3) is presented in parallel with other content, there is a mechanism for the user to pause, stop, or hide it unless the movement, blinking, or scrolling is part of an activity where it is essential; and" + }, + { + "type": "all_true", + "title": "Auto-updating", + "description": "For any auto-updating information that (1) starts automatically and (2) is presented in parallel with other content, there is a mechanism for the user to pause, stop, or hide it or to control the frequency of the update unless the auto-updating is part of an activity where it is essential." + } + ], + "notes": [ + { + "content": "For requirements related to flickering or flashing content, refer to Guideline 2.3. (https://www.w3.org/TR/WCAG20/#seizure)" + }, + { + "content": "Since any content that does not meet this success criterion can interfere with a user's ability to use the whole page, all content on the Web page (whether it is used to meet other success criteria or not) must meet this success criterion. See Conformance Requirement 5: Non-Interference (https://www.w3.org/TR/WCAG20/#cc5)" + }, + { + "content": "Content that is updated periodically by software or that is streamed to the user agent is not required to preserve or present information that is generated or received between the initiation of the pause and resuming presentation, as this may not be technically possible, and in many situations could be misleading to do so." + }, + { + "content": "An animation that occurs as part of a preload phase or similar situation can be considered essential if interaction cannot occur during that phase for all users and if not indicating progress could confuse users or cause them to think that content was frozen or broken." + } + ], + "references": [ + { + "title": "How to Meet 2.2.2", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-time-limits-pause" + }, + { + "title": "Understanding 2.2.2", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/time-limits-pause.html" + } + ] + }, + { + "ref_id": "2.2.3", + "title": "No Timing", + "description": "Timing is not an essential part of the event or activity presented by the content, except for non-interactive synchronized media and real-time events", + "url": "https://www.w3.org/TR/WCAG20/#time-limits-no-exceptions", + "level": "AAA", + "special_cases": null, + "notes": null, + "references": [ + { + "title": "How to Meet 2.2.3", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-time-limits-no-exceptions" + }, + { + "title": "Understanding 2.2.3", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/time-limits-no-exceptions.html" + } + ] + }, + { + "ref_id": "2.2.4", + "title": "Interruptions", + "description": "Interruptions can be postponed or suppressed by the user, except interruptions involving an emergency.", + "url": "https://www.w3.org/TR/WCAG20/#time-limits-postponed", + "level": "AAA", + "special_cases": null, + "notes": null, + "references": [ + { + "title": "How to Meet 2.2.4", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-time-limits-postponed" + }, + { + "title": "Understanding 2.2.4", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/time-limits-postponed.html" + } + ] + }, + { + "ref_id": "2.2.5", + "title": "Re-authenticating", + "description": "When an authenticated session expires, the user can continue the activity without loss of data after re-authenticating.", + "url": "https://www.w3.org/TR/WCAG20/#time-limits-server-timeout", + "level": "AAA", + "special_cases": null, + "notes": null, + "references": [ + { + "title": "How to Meet 2.2.5", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-time-limits-server-timeout" + }, + { + "title": "Understanding 2.2.5", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/time-limits-server-timeout.html" + } + ] + }, + { + "ref_id": "2.3.1", + "title": "Three Flashes or Below Threshold", + "description": "Web pages do not contain anything that flashes more than three times in any one second period, or the flash is below the general flash and red flash thresholds", + "url": "https://www.w3.org/TR/WCAG20/#seizure-does-not-violate", + "level": "A", + "special_cases": null, + "notes": [ + { + "content": "Since any content that does not meet this success criterion can interfere with a user's ability to use the whole page, all content on the Web page (whether it is used to meet other success criteria or not) must meet this success criterion. See Conformance Requirement 5: Non-Interference. (https://www.w3.org/TR/WCAG20/#cc5)" + } + ], + "references": [ + { + "title": "How to Meet 2.3.1", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-seizure-does-not-violate" + }, + { + "title": "Understanding 2.3.1", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/seizure-does-not-violate.html" + } + ] + }, + { + "ref_id": "2.3.2", + "title": "Three Flashes", + "description": "Web pages do not contain anything that flashes more than three times in any one second period.", + "url": "https://www.w3.org/TR/WCAG20/#seizure-three-times", + "level": "AAA", + "special_cases": null, + "notes": null, + "references": [ + { + "title": "How to Meet 2.3.2", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-seizure-three-times" + }, + { + "title": "Understanding 2.3.2", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/seizure-three-times.html" + } + ] + }, + { + "ref_id": "2.4.1", + "title": "Bypass Blocks", + "description": "A mechanism is available to bypass blocks of content that are repeated on multiple Web pages.", + "url": "https://www.w3.org/TR/WCAG20/#navigation-mechanisms-skip", + "level": "A", + "special_cases": null, + "notes": null, + "references": [ + { + "title": "How to Meet 2.4.1", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-navigation-mechanisms-skip" + }, + { + "title": "Understanding 2.4.1", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/navigation-mechanisms-skip.html" + } + ] + }, + { + "ref_id": "2.4.2", + "title": "Page Titled", + "description": "Web pages have titles that describe topic or purpose.", + "url": "https://www.w3.org/TR/WCAG20/#navigation-mechanisms-title", + "level": "A", + "special_cases": null, + "notes": null, + "references": [ + { + "title": "How to Meet 2.4.2", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-navigation-mechanisms-title" + }, + { + "title": "Understanding 2.4.2", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/navigation-mechanisms-title.html" + } + ] + }, + { + "ref_id": "2.4.3", + "title": "Focus Order", + "description": " If a Web page can be navigated sequentially and the navigation sequences affect meaning or operation, focusable components receive focus in an order that preserves meaning and operability", + "url": "https://www.w3.org/TR/WCAG20/#navigation-mechanisms-focus-order", + "level": "A", + "special_cases": null, + "notes": null, + "references": [ + { + "title": "How to Meet 2.4.3", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-navigation-mechanisms-focus-order" + }, + { + "title": "Understanding 2.4.3", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/navigation-mechanisms-focus-order.html" + } + ] + }, + { + "ref_id": "2.4.4", + "title": "Link Purpose (In Context)", + "description": "The purpose of each link can be determined from the link text alone or from the link text together with its programmatically determined link context, except where the purpose of the link would be ambiguous to users in general", + "url": "https://www.w3.org/TR/WCAG20/#navigation-mechanisms-refs", + "level": "A", + "special_cases": null, + "notes": null, + "references": [ + { + "title": "How to Meet 2.4.4", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-navigation-mechanisms-refs" + }, + { + "title": "Understanding 2.4.4", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/navigation-mechanisms-refs.html" + } + ] + }, + { + "ref_id": "2.4.5", + "title": "Multiple Ways", + "description": "More than one way is available to locate a Web page within a set of Web pages except where the Web Page is the result of, or a step in, a process.", + "url": "https://www.w3.org/TR/WCAG20/#navigation-mechanisms-mult-loc", + "level": "AA", + "special_cases": null, + "notes": null, + "references": [ + { + "title": "How to Meet 2.4.5", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-navigation-mechanisms-mult-loc" + }, + { + "title": "Understanding 2.4.5", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/navigation-mechanisms-mult-loc.html" + } + ] + }, + { + "ref_id": "2.4.6", + "title": "Headings and Labels", + "description": "Headings and labels describe topic or purpose.", + "url": "https://www.w3.org/TR/WCAG20/#navigation-mechanisms-descriptive", + "level": "AA", + "special_cases": null, + "notes": null, + "references": [ + { + "title": "How to Meet 2.4.6", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-navigation-mechanisms-descriptive" + }, + { + "title": "Understanding 2.4.6", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/navigation-mechanisms-descriptive.html" + } + ] + }, + { + "ref_id": "2.4.7", + "title": "Focus Visible", + "description": "Any keyboard operable user interface has a mode of operation where the keyboard focus indicator is visible.", + "url": "https://www.w3.org/TR/WCAG20/#navigation-mechanisms-focus-visible", + "level": "AA", + "special_cases": null, + "notes": null, + "references": [ + { + "title": "How to Meet 2.4.7", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-navigation-mechanisms-focus-visible" + }, + { + "title": "Understanding 2.4.7", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/navigation-mechanisms-focus-visible.html" + } + ] + }, + { + "ref_id": "2.4.8", + "title": "Location", + "description": "Information about the user's location within a set of Web pages is available", + "url": "https://www.w3.org/TR/WCAG20/#navigation-mechanisms-location", + "level": "AAA", + "special_cases": null, + "notes": null, + "references": [ + { + "title": "How to Meet 2.4.8", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-navigation-mechanisms-location" + }, + { + "title": "Understanding 2.4.8", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/navigation-mechanisms-location.html" + } + ] + }, + { + "ref_id": "2.4.9", + "title": "Link Purpose (Link Only)", + "description": "A mechanism is available to allow the purpose of each link to be identified from link text alone, except where the purpose of the link would be ambiguous to users in general.", + "url": "https://www.w3.org/TR/WCAG20/#navigation-mechanisms-link", + "level": "AAA", + "special_cases": null, + "notes": null, + "references": [ + { + "title": "How to Meet 2.4.9", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-navigation-mechanisms-link" + }, + { + "title": "Understanding 2.4.9", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/navigation-mechanisms-link.html" + } + ] + }, + { + "ref_id": "2.4.10", + "title": "Section Headings", + "description": "Section headings are used to organize the content", + "url": "https://www.w3.org/TR/WCAG20/#navigation-mechanisms-headings", + "level": "AAA", + "special_cases": null, + "notes": [ + { + "content": "'Heading' is used in its general sense and includes titles and other ways to add a heading to different types of content." + }, + { + "content": "This success criterion covers sections within writing, not user interface components. User Interface components are covered under Success Criterion 4.1.2. (http://www.w3.org/TR/2008/REC-WCAG20-20081211/#ensure-compat-rsv)" + } + ], + "references": [ + { + "title": "How to Meet 2.4.10", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-navigation-mechanisms-headings" + }, + { + "title": "Understanding 2.4.10", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/navigation-mechanisms-headings.html" + } + ] + }, + { + "ref_id": "3.1.1", + "title": "Language of Page", + "description": "The default human language of each Web page can be programmatically determined.", + "url": "https://www.w3.org/TR/WCAG20/#meaning-doc-lang-id", + "level": "A", + "special_cases": null, + "notes": null, + "references": [ + { + "title": "How to Meet 3.1.1", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-meaning-doc-lang-id" + }, + { + "title": "Understanding 3.1.1", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/meaning-doc-lang-id.html" + } + ] + }, + { + "ref_id": "3.1.2", + "title": "Language of Parts", + "description": "The human language of each passage or phrase in the content can be programmatically determined except for proper names, technical terms, words of indeterminate language, and words or phrases that have become part of the vernacular of the immediately surrounding text.", + "url": "https://www.w3.org/TR/WCAG20/#meaning-other-lang-id", + "level": "AA", + "special_cases": null, + "notes": null, + "references": [ + { + "title": "How to Meet 3.1.2", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-meaning-other-lang-id" + }, + { + "title": "Understanding 3.1.2", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/meaning-other-lang-id.html" + } + ] + }, + { + "ref_id": "3.1.3", + "title": "Unusual Words", + "description": "A mechanism is available for identifying specific definitions of words or phrases used in an unusual or restricted way, including idioms and jargon", + "url": "https://www.w3.org/TR/WCAG20/#meaning-idioms", + "level": "AAA", + "special_cases": null, + "notes": null, + "references": [ + { + "title": "How to Meet 3.1.3", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-meaning-idioms" + }, + { + "title": "Understanding 3.1.3", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/meaning-idioms.html" + } + ] + }, + { + "ref_id": "3.1.4", + "title": "Abbreviations", + "description": "A mechanism for identifying the expanded form or meaning of abbreviations is available.", + "url": "https://www.w3.org/TR/WCAG20/#meaning-located", + "level": "AAA", + "special_cases": null, + "notes": null, + "references": [ + { + "title": "How to Meet 3.1.4", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-meaning-located" + }, + { + "title": "Understanding 3.1.4", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/meaning-located.html" + } + ] + }, + { + "ref_id": "3.1.5", + "title": "Reading Level", + "description": "When text requires reading ability more advanced than the lower secondary education level after removal of proper names and titles, supplemental content, or a version that does not require reading ability more advanced than the lower secondary education level, is available.", + "url": "https://www.w3.org/TR/WCAG20/#meaning-supplements", + "level": "AAA", + "special_cases": null, + "notes": null, + "references": [ + { + "title": "How to Meet 3.1.5", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-meaning-supplements" + }, + { + "title": "Understanding 3.1.5", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/meaning-supplements.html" + } + ] + }, + { + "ref_id": "3.1.6", + "title": "Pronunciation", + "description": "A mechanism is available for identifying specific pronunciation of words where meaning of the words, in context, is ambiguous without knowing the pronunciation", + "url": "https://www.w3.org/TR/WCAG20/#meaning-pronunciation", + "level": "AAA", + "special_cases": null, + "notes": null, + "references": [ + { + "title": "How to Meet 3.1.6", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-meaning-pronunciation" + }, + { + "title": "Understanding 3.1.6", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/meaning-pronunciation.html" + } + ] + }, + { + "ref_id": "3.2.1", + "title": "On Focus", + "description": "When any component receives focus, it does not initiate a change of context", + "url": "https://www.w3.org/TR/WCAG20/#consistent-behavior-receive-focus", + "level": "A", + "special_cases": null, + "notes": null, + "references": [ + { + "title": "How to Meet 3.2.1", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-consistent-behavior-receive-focus" + }, + { + "title": "Understanding 3.2.1", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/consistent-behavior-receive-focus.html" + } + ] + }, + { + "ref_id": "3.2.2", + "title": "On Input", + "description": "Changing the setting of any user interface component does not automatically cause a change of context unless the user has been advised of the behavior before using the component.", + "url": "https://www.w3.org/TR/WCAG20/#consistent-behavior-unpredictable-change", + "level": "A", + "special_cases": null, + "notes": null, + "references": [ + { + "title": "How to Meet 3.2.2", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-consistent-behavior-unpredictable-change" + }, + { + "title": "Understanding 3.2.2", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/consistent-behavior-unpredictable-change.html" + } + ] + }, + { + "ref_id": "3.2.3", + "title": "Consistent Navigation", + "description": "Navigational mechanisms that are repeated on multiple Web pages within a set of Web pages occur in the same relative order each time they are repeated, unless a change is initiated by the user", + "url": "https://www.w3.org/TR/WCAG20/#consistent-behavior-consistent-locations", + "level": "AA", + "special_cases": null, + "notes": null, + "references": [ + { + "title": "How to Meet 3.2.3", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-consistent-behavior-consistent-locations" + }, + { + "title": "Understanding 3.2.3", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/consistent-behavior-consistent-locations.html" + } + ] + }, + { + "ref_id": "3.2.4", + "title": "Consistent Identification", + "description": "Components that have the same functionality within a set of Web pages are identified consistently.", + "url": "https://www.w3.org/TR/WCAG20/#consistent-behavior-consistent-functionality", + "level": "AA", + "special_cases": null, + "notes": null, + "references": [ + { + "title": "How to Meet 3.2.4", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-consistent-behavior-consistent-functionality" + }, + { + "title": "Understanding 3.2.4", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/consistent-behavior-consistent-functionality.html" + } + ] + }, + { + "ref_id": "3.2.5", + "title": "Change on Request", + "description": "Changes of context are initiated only by user request or a mechanism is available to turn off such changes", + "url": "https://www.w3.org/TR/WCAG20/#consistent-behavior-no-extreme-changes-context", + "level": "AAA", + "special_cases": null, + "notes": null, + "references": [ + { + "title": "How to Meet 3.2.5", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-consistent-behavior-no-extreme-changes-context" + }, + { + "title": "Understanding 3.2.5", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/consistent-behavior-no-extreme-changes-context.html" + } + ] + }, + { + "ref_id": "3.3.1", + "title": "Error Identification", + "description": "If an input error is automatically detected, the item that is in error is identified and the error is described to the user in text", + "url": "https://www.w3.org/TR/WCAG20/#minimize-error-identified", + "level": "A", + "special_cases": null, + "notes": null, + "references": [ + { + "title": "How to Meet 3.3.1", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-minimize-error-identified" + }, + { + "title": "Understanding 3.3.1", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/minimize-error-identified.html" + } + ] + }, + { + "ref_id": "3.3.2", + "title": "Labels or Instructions", + "description": "Labels or instructions are provided when content requires user input.", + "url": "https://www.w3.org/TR/WCAG20/#minimize-error-cues", + "level": "A", + "special_cases": null, + "notes": null, + "references": [ + { + "title": "How to Meet 3.3.2", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-minimize-error-cues" + }, + { + "title": "Understanding 3.3.2", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/minimize-error-cues.html" + } + ] + }, + { + "ref_id": "3.3.3", + "title": "Error Suggestion", + "description": "If an input error is automatically detected and suggestions for correction are known, then the suggestions are provided to the user, unless it would jeopardize the security or purpose of the content", + "url": "https://www.w3.org/TR/WCAG20/#minimize-error-suggestions", + "level": "AA", + "special_cases": null, + "notes": null, + "references": [ + { + "title": "How to Meet 3.3.3", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-minimize-error-suggestions" + }, + { + "title": "Understanding 3.3.3", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/minimize-error-suggestions.html" + } + ] + }, + { + "ref_id": "3.3.4", + "title": "Error Prevention (Legal, Financial, Data", + "description": " For Web pages that cause legal commitments or financial transactions for the user to occur, that modify or delete user-controllable data in data storage systems, or that submit user test responses, at least one of the following is true:", + "url": "https://www.w3.org/TR/WCAG20/#minimize-error-reversible", + "level": "AA", + "special_cases": [ + { + "type": "at_least_one", + "title": "Reversible", + "description": "Submissions are reversible." + }, + { + "type": "at_least_one", + "title": "Checked", + "description": "Data entered by the user is checked for input errors and the user is provided an opportunity to correct them." + }, + { + "type": "at_least_one", + "title": "Confirmed", + "description": "A mechanism is available for reviewing, confirming, and correcting information before finalizing the submission." + } + ], + "notes": null, + "references": [ + { + "title": "How to Meet 3.3.4", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-minimize-error-reversible" + }, + { + "title": "Understanding 3.3.4", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/minimize-error-reversible.html" + } + ] + }, + { + "ref_id": "3.3.5", + "title": "Help", + "description": "Context-sensitive help is available.", + "url": "https://www.w3.org/TR/WCAG20/#minimize-error-context-help", + "level": "AAA", + "special_cases": null, + "notes": null, + "references": [ + { + "title": "How to Meet 3.3.5", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-minimize-error-context-help" + }, + { + "title": "Understanding 3.3.5", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/minimize-error-context-help.html" + } + ] + }, + { + "ref_id": "3.3.6", + "title": "Error Prevention (All)", + "description": "For Web pages that require the user to submit information, at least one of the following is true:", + "url": "https://www.w3.org/TR/WCAG20/#minimize-error-reversible-all", + "level": "AAA", + "special_cases": [ + { + "type": "at_least_one", + "title": "Reversible", + "description": "Submissions are reversible." + }, + { + "type": "at_least_one", + "title": "Checked", + "description": "Data entered by the user is checked for input errors and the user is provided an opportunity to correct them." + }, + { + "type": "at_least_one", + "title": "Confirmed", + "description": "A mechanism is available for reviewing, confirming, and correcting information before finalizing the submission." + } + ], + "notes": null, + "references": [ + { + "title": "How to Meet 3.3.6", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-minimize-error-reversible-all" + }, + { + "title": "Understanding 3.3.6", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/minimize-error-reversible-all.html" + } + ] + }, + { + "ref_id": "4.1.1", + "title": "Parsing", + "description": "In content implemented using markup languages, elements have complete start and end tags, elements are nested according to their specifications, elements do not contain duplicate attributes, and any IDs are unique, except where the specifications allow these features", + "url": "https://www.w3.org/TR/WCAG20/#ensure-compat-parses", + "level": "A", + "special_cases": null, + "notes": [ + { + "content": "Start and end tags that are missing a critical character in their formation, such as a closing angle bracket or a mismatched attribute value quotation mark are not complete." + } + ], + "references": [ + { + "title": "How to Meet 4.1.1", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-ensure-compat-parses" + }, + { + "title": "Understanding 4.1.1", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/ensure-compat-parses.html" + } + ] + }, + { + "ref_id": "4.1.2", + "title": "Name, Role, Value", + "description": "For all user interface components (including but not limited to: form elements, links and components generated by scripts), the name and role can be programmatically determined; states, properties, and values that can be set by the user can be programmatically set; and notification of changes to these items is available to user agents, including assistive technologies.", + "url": "https://www.w3.org/TR/WCAG20/#ensure-compat-rsv", + "level": "A", + "special_cases": null, + "notes": [ + { + "content": "This success criterion is primarily for Web authors who develop or script their own user interface components. For example, standard HTML controls already meet this success criterion when used according to specification." + } + ], + "references": [ + { + "title": "How to Meet 4.1.2", + "url": "http://www.w3.org/WAI/WCAG20/quickref/#qr-ensure-compat-rsv" + }, + { + "title": "Understanding 4.1.2", + "url": "http://www.w3.org/TR/UNDERSTANDING-WCAG20/ensure-compat-rsv.html" + } + ] + } +] diff --git a/themes/cupper-hugo-theme/i18n/en.yaml b/themes/cupper-hugo-theme/i18n/en.yaml new file mode 100644 index 0000000..3db3cda --- /dev/null +++ b/themes/cupper-hugo-theme/i18n/en.yaml @@ -0,0 +1,28 @@ +- id: skip_to_content + translation: skip to content +- id: nav_main_navigation + translation: Main navigation +- id: nav_button_menu + translation: Menu +- id: discuss_show_comments_button + translation: Show comments +- id: discuss_comments_disabled + translation: Disqus comments are disabled. +- id: discuss_js_disabled + translation: Enable JavaScript to view Disqus comments. +- id: dark_theme + translation: "dark theme:" +- id: table_of_contents + translation: Table of contents +- id: publish_date + translation: "Publish date:" +- id: last_updated + translation: "Last updated:" +- id: tags + translation: "Tags:" +- id: aria_label_tags + translation: tags +- id: search_placeholder + translation: Search by title... +- id: search_aria_label + translation: Search by title diff --git a/themes/cupper-hugo-theme/images/screenshot.png b/themes/cupper-hugo-theme/images/screenshot.png new file mode 100644 index 0000000..6450c2a Binary files /dev/null and b/themes/cupper-hugo-theme/images/screenshot.png differ diff --git a/themes/cupper-hugo-theme/images/tn.png b/themes/cupper-hugo-theme/images/tn.png new file mode 100644 index 0000000..922449f Binary files /dev/null and b/themes/cupper-hugo-theme/images/tn.png differ diff --git a/themes/cupper-hugo-theme/layouts/404.html b/themes/cupper-hugo-theme/layouts/404.html new file mode 100644 index 0000000..e69de29 diff --git a/themes/cupper-hugo-theme/layouts/_default/baseof.html b/themes/cupper-hugo-theme/layouts/_default/baseof.html new file mode 100644 index 0000000..0ce6d4c --- /dev/null +++ b/themes/cupper-hugo-theme/layouts/_default/baseof.html @@ -0,0 +1,22 @@ + + + {{ partial "head.html" . }} + + {{ T "skip_to_content" }} + {{ partial "noscript.html" . }} + {{ partial "svg.html" . }} +
+ {{ partial "header.html" . }} + +
+ {{ partial "script.html" . }} + {{ partial "katex.html" . }} + + diff --git a/themes/cupper-hugo-theme/layouts/_default/list.html b/themes/cupper-hugo-theme/layouts/_default/list.html new file mode 100644 index 0000000..ece8203 --- /dev/null +++ b/themes/cupper-hugo-theme/layouts/_default/list.html @@ -0,0 +1,32 @@ +{{ define "main" }} +
+

{{ .Title }}

+ {{ if site.Params.search }} + + {{ end }} + +
+{{ end }} diff --git a/themes/cupper-hugo-theme/layouts/_default/single.html b/themes/cupper-hugo-theme/layouts/_default/single.html new file mode 100644 index 0000000..ecd5d7d --- /dev/null +++ b/themes/cupper-hugo-theme/layouts/_default/single.html @@ -0,0 +1,6 @@ +{{ define "main" }} +
+

{{ .Title }}

+ {{ .Content }} +
+{{ end }} diff --git a/themes/cupper-hugo-theme/layouts/_default/terms.html b/themes/cupper-hugo-theme/layouts/_default/terms.html new file mode 100644 index 0000000..d3f541f --- /dev/null +++ b/themes/cupper-hugo-theme/layouts/_default/terms.html @@ -0,0 +1,19 @@ +{{ define "main" }} +
+

{{ .Title }}

+ +
+{{ end }} diff --git a/themes/cupper-hugo-theme/layouts/index.html b/themes/cupper-hugo-theme/layouts/index.html new file mode 100644 index 0000000..ecd5d7d --- /dev/null +++ b/themes/cupper-hugo-theme/layouts/index.html @@ -0,0 +1,6 @@ +{{ define "main" }} +
+

{{ .Title }}

+ {{ .Content }} +
+{{ end }} diff --git a/themes/cupper-hugo-theme/layouts/partials/disqus-js-common.js b/themes/cupper-hugo-theme/layouts/partials/disqus-js-common.js new file mode 100644 index 0000000..1d69d8d --- /dev/null +++ b/themes/cupper-hugo-theme/layouts/partials/disqus-js-common.js @@ -0,0 +1,6 @@ +// Remove button +var disqusButton = document.getElementById('disqus-button'); +disqusButton.parentNode.removeChild(disqusButton); +// Un-hide comments +var disqusComments = document.getElementById('disqus-comments'); +disqusComments.style.display = 'block'; \ No newline at end of file diff --git a/themes/cupper-hugo-theme/layouts/partials/disqus-js-main.js b/themes/cupper-hugo-theme/layouts/partials/disqus-js-main.js new file mode 100644 index 0000000..74bff17 --- /dev/null +++ b/themes/cupper-hugo-theme/layouts/partials/disqus-js-main.js @@ -0,0 +1,10 @@ +// Config +var disqus_config = function () { +}; +// Build and append comments +var d = document; +var s = d.createElement('script'); +s.async = true; +s.src = '//' + "{{ . }}" + '.disqus.com/embed.js'; +s.setAttribute('data-timestamp', + new Date()); +(d.head || d.body).appendChild(s); \ No newline at end of file diff --git a/themes/cupper-hugo-theme/layouts/partials/disqus.html b/themes/cupper-hugo-theme/layouts/partials/disqus.html new file mode 100644 index 0000000..ccf0340 --- /dev/null +++ b/themes/cupper-hugo-theme/layouts/partials/disqus.html @@ -0,0 +1,27 @@ +
+ {{ with .Site.DisqusShortname }} + +
+ {{ $isDummyName := eq . "yourdiscussshortname" }} + {{ $isServer := $.Site.IsServer }} + {{ if or $isDummyName $isServer }} +

{{ T "discuss_comments_disabled" }}

+ + {{ else }} +
+
+ + {{ end }} + +
+ {{ end }} +
diff --git a/themes/cupper-hugo-theme/layouts/partials/footer.html b/themes/cupper-hugo-theme/layouts/partials/footer.html new file mode 100644 index 0000000..faec94f --- /dev/null +++ b/themes/cupper-hugo-theme/layouts/partials/footer.html @@ -0,0 +1,14 @@ +
+
+ +
+ {{ with .Site.Params.footer }} + {{ . | markdownify }} + {{ end }} +
diff --git a/themes/cupper-hugo-theme/layouts/partials/google-analytics-async.html b/themes/cupper-hugo-theme/layouts/partials/google-analytics-async.html new file mode 100644 index 0000000..ecc2767 --- /dev/null +++ b/themes/cupper-hugo-theme/layouts/partials/google-analytics-async.html @@ -0,0 +1,10 @@ +{{ if not .Site.IsServer }} + {{ with .Site.GoogleAnalytics }} + + + {{ end }} +{{ end }} \ No newline at end of file diff --git a/themes/cupper-hugo-theme/layouts/partials/head.html b/themes/cupper-hugo-theme/layouts/partials/head.html new file mode 100644 index 0000000..0b9f6a3 --- /dev/null +++ b/themes/cupper-hugo-theme/layouts/partials/head.html @@ -0,0 +1,58 @@ + + + + + + {{ hugo.Generator }} + + + {{ if .IsHome }} + {{ with .Site.Params.homeMetaContent }} + + {{ end }} + {{ end }} + + {{ if not .Site.IsServer }} + {{ with .Site.GoogleAnalytics }} + + + {{ end }} + {{ end }} + + + + + + + + + + + + + + {{ $templateStyles := resources.Get "css/template-styles.css" }} + {{ $styles := $templateStyles | resources.ExecuteAsTemplate "css/styles.css" . }} + + + {{ range .Site.Params.customCss }} + + {{ end }} + + {{ $title := print .Title " | " .Site.Title }} + {{ if .IsHome }} + {{ $title = .Site.Title }} + {{ end }} + {{ $title }} + diff --git a/themes/cupper-hugo-theme/layouts/partials/header.html b/themes/cupper-hugo-theme/layouts/partials/header.html new file mode 100644 index 0000000..05f45c8 --- /dev/null +++ b/themes/cupper-hugo-theme/layouts/partials/header.html @@ -0,0 +1,20 @@ + diff --git a/themes/cupper-hugo-theme/layouts/partials/katex.html b/themes/cupper-hugo-theme/layouts/partials/katex.html new file mode 100644 index 0000000..7410749 --- /dev/null +++ b/themes/cupper-hugo-theme/layouts/partials/katex.html @@ -0,0 +1,5 @@ +{{ if eq $.Site.Params.katex true }} + + + +{{ end }} \ No newline at end of file diff --git a/themes/cupper-hugo-theme/layouts/partials/nav.html b/themes/cupper-hugo-theme/layouts/partials/nav.html new file mode 100644 index 0000000..ca80808 --- /dev/null +++ b/themes/cupper-hugo-theme/layouts/partials/nav.html @@ -0,0 +1,26 @@ + \ No newline at end of file diff --git a/themes/cupper-hugo-theme/layouts/partials/noscript.html b/themes/cupper-hugo-theme/layouts/partials/noscript.html new file mode 100644 index 0000000..40ae776 --- /dev/null +++ b/themes/cupper-hugo-theme/layouts/partials/noscript.html @@ -0,0 +1,8 @@ + diff --git a/themes/cupper-hugo-theme/layouts/partials/script.html b/themes/cupper-hugo-theme/layouts/partials/script.html new file mode 100644 index 0000000..6f59c7d --- /dev/null +++ b/themes/cupper-hugo-theme/layouts/partials/script.html @@ -0,0 +1,16 @@ +{{ $templateDomScripts := resources.Get "js/template-dom-scripts.js" }} +{{ $domScripts := $templateDomScripts | resources.ExecuteAsTemplate "js/dom-scripts.js" . }} + + + + +{{ if site.Params.search }} +{{ $searchJs := resources.Get "js/search.js" | fingerprint }} + +{{ $searchCss := resources.Get "css/search.css" | fingerprint }} + +{{ end }} + +{{ range .Site.Params.customJs }} + +{{ end }} diff --git a/themes/cupper-hugo-theme/layouts/partials/svg.html b/themes/cupper-hugo-theme/layouts/partials/svg.html new file mode 100644 index 0000000..7c34d04 --- /dev/null +++ b/themes/cupper-hugo-theme/layouts/partials/svg.html @@ -0,0 +1,88 @@ + + + + + + + + + W3C + SVG + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/themes/cupper-hugo-theme/layouts/partials/toc.html b/themes/cupper-hugo-theme/layouts/partials/toc.html new file mode 100644 index 0000000..55a2ccb --- /dev/null +++ b/themes/cupper-hugo-theme/layouts/partials/toc.html @@ -0,0 +1,19 @@ +{{ $headings := findRE "(.|\n])+?

" .Content }} +{{ if ge (len $headings) 2 }} + +{{ end }} diff --git a/themes/cupper-hugo-theme/layouts/post/single.html b/themes/cupper-hugo-theme/layouts/post/single.html new file mode 100644 index 0000000..9003db6 --- /dev/null +++ b/themes/cupper-hugo-theme/layouts/post/single.html @@ -0,0 +1,46 @@ +{{ define "main" }} +
+

+ + {{ .Title }} +

+ +
+ {{ $dateFormat := $.Site.Params.dateFormat | default "Jan 2, 2006" }} + {{ $publishDate := .PublishDate }} + {{ T "publish_date" }} {{ $publishDate.Format $dateFormat }} + {{ with .Lastmod }} + {{ if gt . $publishDate }} +
+ {{ T "last_updated" }} {{ .Format $dateFormat }} + {{ end }} + {{ end }} +
+ + {{ with .Params.tags }} +
+ {{ T "tags" }} +
    + {{ range . }} +
  • + + {{ $href := print ("tags/" | relLangURL) (. | urlize) "/" }} + {{ . }} +
  • + {{ end }} +
+
+ {{ end }} + + {{ if ne .Params.toc false }} + {{ partial "toc" . }} + {{ end }} + + {{ .Content }} +
+ {{ partial "disqus.html" . }} +{{ end }} diff --git a/themes/cupper-hugo-theme/layouts/shortcodes/blockquote.html b/themes/cupper-hugo-theme/layouts/shortcodes/blockquote.html new file mode 100644 index 0000000..7bc9551 --- /dev/null +++ b/themes/cupper-hugo-theme/layouts/shortcodes/blockquote.html @@ -0,0 +1,11 @@ +{{ $quote := .Inner }} + +
+

+ {{ $quote | markdownify }} + {{ with (.Get "author") }} +
+ — {{ . }} + {{ end }} +

+
\ No newline at end of file diff --git a/themes/cupper-hugo-theme/layouts/shortcodes/cmd.html b/themes/cupper-hugo-theme/layouts/shortcodes/cmd.html new file mode 100644 index 0000000..ccadc07 --- /dev/null +++ b/themes/cupper-hugo-theme/layouts/shortcodes/cmd.html @@ -0,0 +1,7 @@ +{{ $trimmed := (trim .Inner "\n") }} +{{ $lines := split $trimmed "\n" }} +
+  {{ range $lines }}
+  {{ . }}
+  {{ end }}
+
diff --git a/themes/cupper-hugo-theme/layouts/shortcodes/code.html b/themes/cupper-hugo-theme/layouts/shortcodes/code.html new file mode 100644 index 0000000..a993b61 --- /dev/null +++ b/themes/cupper-hugo-theme/layouts/shortcodes/code.html @@ -0,0 +1,5 @@ +{{ $code := .Inner | htmlEscape }} +{{ $code := replace $code "[[[" "" }} +{{ $code := replace $code "]]]" "" }} +{{ $numbered := .Get "numbered" }} +
{{ $code | safeHTML }}
diff --git a/themes/cupper-hugo-theme/layouts/shortcodes/codePen.html b/themes/cupper-hugo-theme/layouts/shortcodes/codePen.html new file mode 100644 index 0000000..b608953 --- /dev/null +++ b/themes/cupper-hugo-theme/layouts/shortcodes/codePen.html @@ -0,0 +1,10 @@ +{{ $pen := .Get 0 }} +{{ with .Site.Params.codePenUser }} + +{{ else }} +

Site error: The codePenUser param has not been set in config.toml

+{{ end }} diff --git a/themes/cupper-hugo-theme/layouts/shortcodes/colors.html b/themes/cupper-hugo-theme/layouts/shortcodes/colors.html new file mode 100644 index 0000000..b46486f --- /dev/null +++ b/themes/cupper-hugo-theme/layouts/shortcodes/colors.html @@ -0,0 +1,11 @@ +{{ $colorString := replace (.Get 0) " " "" | upper }} +{{ $colors := split $colorString "," }} +
+
    + {{ range $colors }} +
  • + {{ . }} +
  • + {{ end }} +
+
diff --git a/themes/cupper-hugo-theme/layouts/shortcodes/expandable.html b/themes/cupper-hugo-theme/layouts/shortcodes/expandable.html new file mode 100644 index 0000000..5b752a9 --- /dev/null +++ b/themes/cupper-hugo-theme/layouts/shortcodes/expandable.html @@ -0,0 +1,25 @@ +
+ {{ if .Get "level" }} + + {{ end }} + {{/* 1. Get the md5 hash of the expandable inner text */}} + {{/* 2. Split the hash string into an array */}} + {{/* 3. Shuffle the array */}} + {{/* 4. Convert the array back into a string */}} + {{ $random := delimit (shuffle (split (md5 .Inner) "" )) "" }} + + {{ if .Get "level" }} + + {{ end }} +
+ {{ .Inner | markdownify }} +
+
diff --git a/themes/cupper-hugo-theme/layouts/shortcodes/figureCupper.html b/themes/cupper-hugo-theme/layouts/shortcodes/figureCupper.html new file mode 100644 index 0000000..51a0a09 --- /dev/null +++ b/themes/cupper-hugo-theme/layouts/shortcodes/figureCupper.html @@ -0,0 +1,28 @@ +{{ $img := .Get "img" }} +{{ $caption := .Get "caption" }} +{{ $command := .Get "command" }} +{{ $options := .Get "options" }} + +{{ $original := .Page.Resources.GetMatch (printf "*%s*" $img) }} +{{ $new := "" }} + +{{ if eq $command "Fit" }} + {{ $new = $original.Fit $options }} +{{ else if eq $command "Fill" }} + {{ $new = $original.Fill $options }} +{{ else if eq $command "Resize" }} + {{ $new = $original.Resize $options }} +{{ else if eq $command "Original" }} + {{ $new = $original }} +{{ else }} + {{ errorf "Invalid image processing command: Must be one of Fit, Fill, Resize, Original." }} +{{ end }} + +
+ + + +
+ {{ $caption | markdownify }} +
+
diff --git a/themes/cupper-hugo-theme/layouts/shortcodes/fileTree.html b/themes/cupper-hugo-theme/layouts/shortcodes/fileTree.html new file mode 100644 index 0000000..3e8d95d --- /dev/null +++ b/themes/cupper-hugo-theme/layouts/shortcodes/fileTree.html @@ -0,0 +1,3 @@ +
+ {{ .Inner | markdownify }} +
diff --git a/themes/cupper-hugo-theme/layouts/shortcodes/gallery.html b/themes/cupper-hugo-theme/layouts/shortcodes/gallery.html new file mode 100644 index 0000000..d1c7be0 --- /dev/null +++ b/themes/cupper-hugo-theme/layouts/shortcodes/gallery.html @@ -0,0 +1,29 @@ +{{ $command := .Get "command" }} +{{ $options := .Get "options" }} + +{{ with .Page.Resources.ByType "image" }} + {{ range . }} + + {{ $original := . }} + {{ $new := "" }} + + {{ if eq $command "Fit" }} + {{ $new = $original.Fit $options }} + {{ else if eq $command "Fill" }} + {{ $new = $original.Fill $options }} + {{ else if eq $command "Resize" }} + {{ $new = $original.Resize $options }} + {{ else if eq $command "Original" }} + {{ $new = $original }} + {{ else }} + {{ errorf "Invalid image processing command: Must be one of Fit, Fill, Resize, Original." }} + {{ end }} + + + + {{ end }} +{{ end }} diff --git a/themes/cupper-hugo-theme/layouts/shortcodes/note.html b/themes/cupper-hugo-theme/layouts/shortcodes/note.html new file mode 100644 index 0000000..5671b72 --- /dev/null +++ b/themes/cupper-hugo-theme/layouts/shortcodes/note.html @@ -0,0 +1,8 @@ + diff --git a/themes/cupper-hugo-theme/layouts/shortcodes/principles.html b/themes/cupper-hugo-theme/layouts/shortcodes/principles.html new file mode 100644 index 0000000..ca846ac --- /dev/null +++ b/themes/cupper-hugo-theme/layouts/shortcodes/principles.html @@ -0,0 +1,24 @@ +{{ $JSON := $.Site.Data.principles }} +{{ $included := replace (.Get "include") ", " "," }} +{{ $included := apply (split $included ",") "lower" "." }} +{{ $descriptions := .Get "descriptions" }} +
    + {{ range $JSON.principles }} + {{ if in $included (lower .title) }} +
  • + + + + {{ .title }} + : + + {{ .strapline }} + {{ if and ($descriptions) (ne $descriptions "false") }} +

    {{ .description }}

    + {{ end }} +
  • + {{ end }} + {{ end }} +
diff --git a/themes/cupper-hugo-theme/layouts/shortcodes/tested.html b/themes/cupper-hugo-theme/layouts/shortcodes/tested.html new file mode 100644 index 0000000..0e8d9d3 --- /dev/null +++ b/themes/cupper-hugo-theme/layouts/shortcodes/tested.html @@ -0,0 +1,28 @@ +{{ $tested := replace (.Get "using") ", " "," }} +{{ $tested := replace $tested " + " "+" }} +{{ $tested := split $tested "," }} + + + + {{ range $tested }} + + {{ end }} + +
+ + Tested using + + {{ $browser := findRE "^[a-zA-Z ]+" . }} + {{ $browser := index $browser 0 }} + {{ $version := findRE "[0-9]+$" . }} + {{ $slug := replace $browser " " "-" | lower }} + + {{ $browser }} {{ index $version 0 }} + {{ if in . "+" }} + {{ $parts := split . "+" }} + {{ $additional := index $parts 1 }} + with {{ $additional }} + {{ end }} +
diff --git a/themes/cupper-hugo-theme/layouts/shortcodes/ticks.html b/themes/cupper-hugo-theme/layouts/shortcodes/ticks.html new file mode 100644 index 0000000..21518d4 --- /dev/null +++ b/themes/cupper-hugo-theme/layouts/shortcodes/ticks.html @@ -0,0 +1,3 @@ +
+ {{ .Inner | markdownify }} +
diff --git a/themes/cupper-hugo-theme/layouts/shortcodes/warning.html b/themes/cupper-hugo-theme/layouts/shortcodes/warning.html new file mode 100644 index 0000000..69d1bad --- /dev/null +++ b/themes/cupper-hugo-theme/layouts/shortcodes/warning.html @@ -0,0 +1,8 @@ + diff --git a/themes/cupper-hugo-theme/layouts/shortcodes/wcag.html b/themes/cupper-hugo-theme/layouts/shortcodes/wcag.html new file mode 100644 index 0000000..574af64 --- /dev/null +++ b/themes/cupper-hugo-theme/layouts/shortcodes/wcag.html @@ -0,0 +1,30 @@ +{{ $JSON := $.Site.Data.wcag }} +{{ $included := replace (.Get "include") ", " "," }} +{{ $included := split $included "," }} +{{ $descriptions := .Get "descriptions" }} +
    + {{ range $JSON }} + {{ if in $included .ref_id }} +
  • + + + {{ .ref_id }} {{ .title }} (level {{ .level }}){{ if $descriptions }}:{{ end }} + + {{ if and ($descriptions) (ne $descriptions "false") }} + {{ .description }} + {{ if .special_cases }} +
      + {{ range .special_cases }} +
    • {{ .title }}: + {{ .description }} +
    • + {{ end }} +
    + {{ end }} + {{ end }} +
  • + {{ end }} + {{ end }} +
diff --git a/themes/cupper-hugo-theme/local_git_config.sh b/themes/cupper-hugo-theme/local_git_config.sh new file mode 100755 index 0000000..d510958 --- /dev/null +++ b/themes/cupper-hugo-theme/local_git_config.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +git config --local user.name "zwbetz-gh" +git config --local user.email "zwbetz@gmail.com" + +git config --local --list diff --git a/themes/cupper-hugo-theme/netlify.toml b/themes/cupper-hugo-theme/netlify.toml new file mode 100644 index 0000000..044b094 --- /dev/null +++ b/themes/cupper-hugo-theme/netlify.toml @@ -0,0 +1,13 @@ +[build] + publish = "exampleSite/public" + command = "cd exampleSite && hugo --themesDir ../.." + +[build.environment] + HUGO_VERSION = "0.72.0" + HUGO_THEME = "repo" + HUGO_BASEURL = "https://cupper-hugo-theme.netlify.app" + +[[headers]] + for = "/*" + [headers.values] + Access-Control-Allow-Origin = "*" diff --git a/themes/cupper-hugo-theme/static/android-chrome-192x192.png b/themes/cupper-hugo-theme/static/android-chrome-192x192.png new file mode 100644 index 0000000..9138e12 Binary files /dev/null and b/themes/cupper-hugo-theme/static/android-chrome-192x192.png differ diff --git a/themes/cupper-hugo-theme/static/android-chrome-512x512.png b/themes/cupper-hugo-theme/static/android-chrome-512x512.png new file mode 100644 index 0000000..7e493ee Binary files /dev/null and b/themes/cupper-hugo-theme/static/android-chrome-512x512.png differ diff --git a/themes/cupper-hugo-theme/static/apple-touch-icon.png b/themes/cupper-hugo-theme/static/apple-touch-icon.png new file mode 100644 index 0000000..c20e3b5 Binary files /dev/null and b/themes/cupper-hugo-theme/static/apple-touch-icon.png differ diff --git a/themes/cupper-hugo-theme/static/browserconfig.xml b/themes/cupper-hugo-theme/static/browserconfig.xml new file mode 100644 index 0000000..b3930d0 --- /dev/null +++ b/themes/cupper-hugo-theme/static/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #da532c + + + diff --git a/themes/cupper-hugo-theme/static/css/fonts/miriamlibre-bold.woff b/themes/cupper-hugo-theme/static/css/fonts/miriamlibre-bold.woff new file mode 100644 index 0000000..54f9900 Binary files /dev/null and b/themes/cupper-hugo-theme/static/css/fonts/miriamlibre-bold.woff differ diff --git a/themes/cupper-hugo-theme/static/css/fonts/miriamlibre-bold.woff2 b/themes/cupper-hugo-theme/static/css/fonts/miriamlibre-bold.woff2 new file mode 100644 index 0000000..9ab4b85 Binary files /dev/null and b/themes/cupper-hugo-theme/static/css/fonts/miriamlibre-bold.woff2 differ diff --git a/themes/cupper-hugo-theme/static/css/images/arrow_effect.svg b/themes/cupper-hugo-theme/static/css/images/arrow_effect.svg new file mode 100644 index 0000000..2d669ef --- /dev/null +++ b/themes/cupper-hugo-theme/static/css/images/arrow_effect.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/themes/cupper-hugo-theme/static/css/images/icon-tick.svg b/themes/cupper-hugo-theme/static/css/images/icon-tick.svg new file mode 100644 index 0000000..20f9650 --- /dev/null +++ b/themes/cupper-hugo-theme/static/css/images/icon-tick.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/themes/cupper-hugo-theme/static/css/images/stripe.svg b/themes/cupper-hugo-theme/static/css/images/stripe.svg new file mode 100644 index 0000000..48bf5f9 --- /dev/null +++ b/themes/cupper-hugo-theme/static/css/images/stripe.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/themes/cupper-hugo-theme/static/css/prism.css b/themes/cupper-hugo-theme/static/css/prism.css new file mode 100644 index 0000000..6ae40c7 --- /dev/null +++ b/themes/cupper-hugo-theme/static/css/prism.css @@ -0,0 +1,183 @@ +/* PrismJS 1.15.0 +https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript&plugins=line-numbers */ +/** + * prism.js default theme for JavaScript, CSS and HTML + * Based on dabblet (http://dabblet.com) + * @author Lea Verou + */ + +code[class*="language-"], +pre[class*="language-"] { + color: black; + background: none; + text-shadow: 0 1px white; + font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} + +pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, +code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { + text-shadow: none; + background: #b3d4fc; +} + +pre[class*="language-"]::selection, pre[class*="language-"] ::selection, +code[class*="language-"]::selection, code[class*="language-"] ::selection { + text-shadow: none; + background: #b3d4fc; +} + +@media print { + code[class*="language-"], + pre[class*="language-"] { + text-shadow: none; + } +} + +/* Code blocks */ +pre[class*="language-"] { + padding: 1em; + margin: .5em 0; + overflow: auto; +} + +:not(pre) > code[class*="language-"], +pre[class*="language-"] { + background: #f5f2f0; +} + +/* Inline code */ +:not(pre) > code[class*="language-"] { + padding: .1em; + border-radius: .3em; + white-space: normal; +} + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: slategray; +} + +.token.punctuation { + color: #999; +} + +.namespace { + opacity: .7; +} + +.token.property, +.token.tag, +.token.boolean, +.token.number, +.token.constant, +.token.symbol, +.token.deleted { + color: #905; +} + +.token.selector, +.token.attr-name, +.token.string, +.token.char, +.token.builtin, +.token.inserted { + color: #690; +} + +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string { + color: #9a6e3a; + background: hsla(0, 0%, 100%, .5); +} + +.token.atrule, +.token.attr-value, +.token.keyword { + color: #07a; +} + +.token.function, +.token.class-name { + color: #DD4A68; +} + +.token.regex, +.token.important, +.token.variable { + color: #e90; +} + +.token.important, +.token.bold { + font-weight: bold; +} +.token.italic { + font-style: italic; +} + +.token.entity { + cursor: help; +} + +pre[class*="language-"].line-numbers { + position: relative; + padding-left: 3.8em; + counter-reset: linenumber; +} + +pre[class*="language-"].line-numbers > code { + position: relative; + white-space: inherit; +} + +.line-numbers .line-numbers-rows { + position: absolute; + pointer-events: none; + top: 0; + font-size: 100%; + left: -3.8em; + width: 3em; /* works for line-numbers below 1000 lines */ + letter-spacing: -1px; + border-right: 1px solid #999; + + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + +} + + .line-numbers-rows > span { + pointer-events: none; + display: block; + counter-increment: linenumber; + } + + .line-numbers-rows > span:before { + content: counter(linenumber); + color: #999; + display: block; + padding-right: 0.8em; + text-align: right; + } + diff --git a/themes/cupper-hugo-theme/static/favicon-16x16.png b/themes/cupper-hugo-theme/static/favicon-16x16.png new file mode 100644 index 0000000..27a5613 Binary files /dev/null and b/themes/cupper-hugo-theme/static/favicon-16x16.png differ diff --git a/themes/cupper-hugo-theme/static/favicon-32x32.png b/themes/cupper-hugo-theme/static/favicon-32x32.png new file mode 100644 index 0000000..390625e Binary files /dev/null and b/themes/cupper-hugo-theme/static/favicon-32x32.png differ diff --git a/themes/cupper-hugo-theme/static/favicon-96x96.png b/themes/cupper-hugo-theme/static/favicon-96x96.png new file mode 100644 index 0000000..74546ad Binary files /dev/null and b/themes/cupper-hugo-theme/static/favicon-96x96.png differ diff --git a/themes/cupper-hugo-theme/static/favicon.ico b/themes/cupper-hugo-theme/static/favicon.ico new file mode 100644 index 0000000..6506f1d Binary files /dev/null and b/themes/cupper-hugo-theme/static/favicon.ico differ diff --git a/themes/cupper-hugo-theme/static/images/browser-chrome-android.svg b/themes/cupper-hugo-theme/static/images/browser-chrome-android.svg new file mode 100644 index 0000000..5ecb985 --- /dev/null +++ b/themes/cupper-hugo-theme/static/images/browser-chrome-android.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/themes/cupper-hugo-theme/static/images/browser-chrome.svg b/themes/cupper-hugo-theme/static/images/browser-chrome.svg new file mode 100644 index 0000000..02d8389 --- /dev/null +++ b/themes/cupper-hugo-theme/static/images/browser-chrome.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/themes/cupper-hugo-theme/static/images/browser-edge.svg b/themes/cupper-hugo-theme/static/images/browser-edge.svg new file mode 100644 index 0000000..a755a9d --- /dev/null +++ b/themes/cupper-hugo-theme/static/images/browser-edge.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/themes/cupper-hugo-theme/static/images/browser-firefox-android.svg b/themes/cupper-hugo-theme/static/images/browser-firefox-android.svg new file mode 100644 index 0000000..f9c0e66 --- /dev/null +++ b/themes/cupper-hugo-theme/static/images/browser-firefox-android.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/themes/cupper-hugo-theme/static/images/browser-firefox.svg b/themes/cupper-hugo-theme/static/images/browser-firefox.svg new file mode 100644 index 0000000..1a80b65 --- /dev/null +++ b/themes/cupper-hugo-theme/static/images/browser-firefox.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/themes/cupper-hugo-theme/static/images/browser-ie.svg b/themes/cupper-hugo-theme/static/images/browser-ie.svg new file mode 100644 index 0000000..8636924 --- /dev/null +++ b/themes/cupper-hugo-theme/static/images/browser-ie.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/themes/cupper-hugo-theme/static/images/browser-opera.svg b/themes/cupper-hugo-theme/static/images/browser-opera.svg new file mode 100644 index 0000000..f127290 --- /dev/null +++ b/themes/cupper-hugo-theme/static/images/browser-opera.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/themes/cupper-hugo-theme/static/images/browser-safari-ios.svg b/themes/cupper-hugo-theme/static/images/browser-safari-ios.svg new file mode 100644 index 0000000..da98377 --- /dev/null +++ b/themes/cupper-hugo-theme/static/images/browser-safari-ios.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/themes/cupper-hugo-theme/static/images/browser-safari.svg b/themes/cupper-hugo-theme/static/images/browser-safari.svg new file mode 100644 index 0000000..12f0c76 --- /dev/null +++ b/themes/cupper-hugo-theme/static/images/browser-safari.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/themes/cupper-hugo-theme/static/images/icon-info.svg b/themes/cupper-hugo-theme/static/images/icon-info.svg new file mode 100644 index 0000000..1e2384f --- /dev/null +++ b/themes/cupper-hugo-theme/static/images/icon-info.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/themes/cupper-hugo-theme/static/images/icon-tag.svg b/themes/cupper-hugo-theme/static/images/icon-tag.svg new file mode 100644 index 0000000..0d8d3af --- /dev/null +++ b/themes/cupper-hugo-theme/static/images/icon-tag.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/themes/cupper-hugo-theme/static/images/icon-warning.svg b/themes/cupper-hugo-theme/static/images/icon-warning.svg new file mode 100644 index 0000000..49d20fc --- /dev/null +++ b/themes/cupper-hugo-theme/static/images/icon-warning.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/themes/cupper-hugo-theme/static/images/logo.svg b/themes/cupper-hugo-theme/static/images/logo.svg new file mode 100644 index 0000000..b0a4220 --- /dev/null +++ b/themes/cupper-hugo-theme/static/images/logo.svg @@ -0,0 +1 @@ + diff --git a/themes/cupper-hugo-theme/static/js/prism.js b/themes/cupper-hugo-theme/static/js/prism.js new file mode 100644 index 0000000..9461593 --- /dev/null +++ b/themes/cupper-hugo-theme/static/js/prism.js @@ -0,0 +1,8 @@ +/* PrismJS 1.15.0 +https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript&plugins=line-numbers */ +var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(){var e=/\blang(?:uage)?-([\w-]+)\b/i,t=0,n=_self.Prism={manual:_self.Prism&&_self.Prism.manual,disableWorkerMessageHandler:_self.Prism&&_self.Prism.disableWorkerMessageHandler,util:{encode:function(e){return e instanceof a?new a(e.type,n.util.encode(e.content),e.alias):"Array"===n.util.type(e)?e.map(n.util.encode):e.replace(/&/g,"&").replace(/e.length)return;if(!(w instanceof s)){if(m&&b!=t.length-1){f.lastIndex=k;var _=f.exec(e);if(!_)break;for(var j=_.index+(d?_[1].length:0),P=_.index+_[0].length,A=b,O=k,x=t.length;x>A&&(P>O||!t[A].type&&!t[A-1].greedy);++A)O+=t[A].length,j>=O&&(++b,k=O);if(t[b]instanceof s)continue;I=A-b,w=e.slice(k,O),_.index-=k}else{f.lastIndex=0;var _=f.exec(w),I=1}if(_){d&&(p=_[1]?_[1].length:0);var j=_.index+p,_=_[0].slice(p),P=j+_.length,N=w.slice(0,j),S=w.slice(P),E=[b,I];N&&(++b,k+=N.length,E.push(N));var C=new s(u,h?n.tokenize(_,h):_,y,_,m);if(E.push(C),S&&E.push(S),Array.prototype.splice.apply(t,E),1!=I&&n.matchGrammar(e,t,a,b,k,!0,u),i)break}else if(i)break}}}}},tokenize:function(e,t){var a=[e],r=t.rest;if(r){for(var l in r)t[l]=r[l];delete t.rest}return n.matchGrammar(e,a,t,0,0,!1),a},hooks:{all:{},add:function(e,t){var a=n.hooks.all;a[e]=a[e]||[],a[e].push(t)},run:function(e,t){var a=n.hooks.all[e];if(a&&a.length)for(var r,l=0;r=a[l++];)r(t)}}},a=n.Token=function(e,t,n,a,r){this.type=e,this.content=t,this.alias=n,this.length=0|(a||"").length,this.greedy=!!r};if(a.stringify=function(e,t,r){if("string"==typeof e)return e;if("Array"===n.util.type(e))return e.map(function(n){return a.stringify(n,t,e)}).join("");var l={type:e.type,content:a.stringify(e.content,t,r),tag:"span",classes:["token",e.type],attributes:{},language:t,parent:r};if(e.alias){var i="Array"===n.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(l.classes,i)}n.hooks.run("wrap",l);var o=Object.keys(l.attributes).map(function(e){return e+'="'+(l.attributes[e]||"").replace(/"/g,""")+'"'}).join(" ");return"<"+l.tag+' class="'+l.classes.join(" ")+'"'+(o?" "+o:"")+">"+l.content+""},!_self.document)return _self.addEventListener?(n.disableWorkerMessageHandler||_self.addEventListener("message",function(e){var t=JSON.parse(e.data),a=t.language,r=t.code,l=t.immediateClose;_self.postMessage(n.highlight(r,n.languages[a],a)),l&&_self.close()},!1),_self.Prism):_self.Prism;var r=document.currentScript||[].slice.call(document.getElementsByTagName("script")).pop();return r&&(n.filename=r.src,n.manual||r.hasAttribute("data-manual")||("loading"!==document.readyState?window.requestAnimationFrame?window.requestAnimationFrame(n.highlightAll):window.setTimeout(n.highlightAll,16):document.addEventListener("DOMContentLoaded",n.highlightAll))),_self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism); +Prism.languages.markup={comment://,prolog:/<\?[\s\S]+?\?>/,doctype://i,cdata://i,tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s'">=]+))?)*\s*\/?>/i,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/i,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"attr-value":{pattern:/=(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s'">=]+)/i,inside:{punctuation:[/^=/,{pattern:/(^|[^\\])["']/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:/&#?[\da-z]{1,8};/i},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.hooks.add("wrap",function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&/,"&"))}),Prism.languages.xml=Prism.languages.extend("markup",{}),Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup; +Prism.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:/@[\w-]+?[\s\S]*?(?:;|(?=\s*\{))/i,inside:{rule:/@[\w-]+/}},url:/url\((?:(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1|.*?)\)/i,selector:/[^{}\s][^{};]*?(?=\s*\{)/,string:{pattern:/("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},property:/[-_a-z\xA0-\uFFFF][-\w\xA0-\uFFFF]*(?=\s*:)/i,important:/!important\b/i,"function":/[-a-z0-9]+(?=\()/i,punctuation:/[(){};:,]/},Prism.languages.css.atrule.inside.rest=Prism.languages.css,Prism.languages.markup&&(Prism.languages.insertBefore("markup","tag",{style:{pattern:/()[\s\S]*?(?=<\/style>)/i,lookbehind:!0,inside:Prism.languages.css,alias:"language-css",greedy:!0}}),Prism.languages.insertBefore("inside","attr-value",{"style-attr":{pattern:/\s*style=("|')(?:\\[\s\S]|(?!\1)[^\\])*\1/i,inside:{"attr-name":{pattern:/^\s*style/i,inside:Prism.languages.markup.tag.inside},punctuation:/^\s*=\s*['"]|['"]\s*$/,"attr-value":{pattern:/.+/i,inside:Prism.languages.css}},alias:"language-css"}},Prism.languages.markup.tag)); +Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,"boolean":/\b(?:true|false)\b/,"function":/\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/,punctuation:/[{}[\];(),.:]/}; +Prism.languages.javascript=Prism.languages.extend("clike",{"class-name":[Prism.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])[_$A-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\.(?:prototype|constructor))/,lookbehind:!0}],keyword:[{pattern:/((?:^|})\s*)(?:catch|finally)\b/,lookbehind:!0},/\b(?:as|async|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/],number:/\b(?:(?:0[xX][\dA-Fa-f]+|0[bB][01]+|0[oO][0-7]+)n?|\d+n|NaN|Infinity)\b|(?:\b\d+\.?\d*|\B\.\d+)(?:[Ee][+-]?\d+)?/,"function":/[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*\(|\.(?:apply|bind|call)\()/,operator:/-[-=]?|\+[+=]?|!=?=?|<>?>?=?|=(?:==?|>)?|&[&=]?|\|[|=]?|\*\*?=?|\/=?|~|\^=?|%=?|\?|\.{3}/}),Prism.languages.javascript["class-name"][0].pattern=/(\b(?:class|interface|extends|implements|instanceof|new)\s+)[\w.\\]+/,Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/((?:^|[^$\w\xA0-\uFFFF."'\])\s])\s*)\/(\[(?:[^\]\\\r\n]|\\.)*]|\\.|[^\/\\\[\r\n])+\/[gimyu]{0,5}(?=\s*($|[\r\n,.;})\]]))/,lookbehind:!0,greedy:!0},"function-variable":{pattern:/[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+[_$A-Za-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*)?\s*\(\s*)(?!\s)(?:[^()]|\([^()]*\))+?(?=\s*\))/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/[_$a-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*=>)/i,inside:Prism.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()]|\([^()]*\))+?(?=\s*\)\s*=>)/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:[_$A-Za-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*\s*)\(\s*)(?!\s)(?:[^()]|\([^()]*\))+?(?=\s*\)\s*\{)/,lookbehind:!0,inside:Prism.languages.javascript}],constant:/\b[A-Z][A-Z\d_]*\b/}),Prism.languages.insertBefore("javascript","string",{"template-string":{pattern:/`(?:\\[\s\S]|\${[^}]+}|[^\\`])*`/,greedy:!0,inside:{interpolation:{pattern:/\${[^}]+}/,inside:{"interpolation-punctuation":{pattern:/^\${|}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}}}),Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/()[\s\S]*?(?=<\/script>)/i,lookbehind:!0,inside:Prism.languages.javascript,alias:"language-javascript",greedy:!0}}),Prism.languages.js=Prism.languages.javascript; +!function(){if("undefined"!=typeof self&&self.Prism&&self.document){var e="line-numbers",t=/\n(?!$)/g,n=function(e){var n=r(e),s=n["white-space"];if("pre-wrap"===s||"pre-line"===s){var l=e.querySelector("code"),i=e.querySelector(".line-numbers-rows"),a=e.querySelector(".line-numbers-sizer"),o=l.textContent.split(t);a||(a=document.createElement("span"),a.className="line-numbers-sizer",l.appendChild(a)),a.style.display="block",o.forEach(function(e,t){a.textContent=e||"\n";var n=a.getBoundingClientRect().height;i.children[t].style.height=n+"px"}),a.textContent="",a.style.display="none"}},r=function(e){return e?window.getComputedStyle?getComputedStyle(e):e.currentStyle||null:null};window.addEventListener("resize",function(){Array.prototype.forEach.call(document.querySelectorAll("pre."+e),n)}),Prism.hooks.add("complete",function(e){if(e.code){var r=e.element.parentNode,s=/\s*\bline-numbers\b\s*/;if(r&&/pre/i.test(r.nodeName)&&(s.test(r.className)||s.test(e.element.className))&&!e.element.querySelector(".line-numbers-rows")){s.test(e.element.className)&&(e.element.className=e.element.className.replace(s," ")),s.test(r.className)||(r.className+=" line-numbers");var l,i=e.code.match(t),a=i?i.length+1:1,o=new Array(a+1);o=o.join(""),l=document.createElement("span"),l.setAttribute("aria-hidden","true"),l.className="line-numbers-rows",l.innerHTML=o,r.hasAttribute("data-start")&&(r.style.counterReset="linenumber "+(parseInt(r.getAttribute("data-start"),10)-1)),e.element.appendChild(l),n(r),Prism.hooks.run("line-numbers",e)}}}),Prism.hooks.add("line-numbers",function(e){e.plugins=e.plugins||{},e.plugins.lineNumbers=!0}),Prism.plugins.lineNumbers={getLine:function(t,n){if("PRE"===t.tagName&&t.classList.contains(e)){var r=t.querySelector(".line-numbers-rows"),s=parseInt(t.getAttribute("data-start"),10)||1,l=s+(r.children.length-1);s>n&&(n=s),n>l&&(n=l);var i=n-s;return r.children[i]}}}}}(); diff --git a/themes/cupper-hugo-theme/static/js/service-worker-registration.js b/themes/cupper-hugo-theme/static/js/service-worker-registration.js new file mode 100644 index 0000000..bbcc757 --- /dev/null +++ b/themes/cupper-hugo-theme/static/js/service-worker-registration.js @@ -0,0 +1,62 @@ +/** + * Copyright 2015 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* eslint-env browser */ +'use strict'; + +if ('serviceWorker' in navigator) { + // Delay registration until after the page has loaded, to ensure that our + // precaching requests don't degrade the first visit experience. + // See https://developers.google.com/web/fundamentals/instant-and-offline/service-worker/registration + window.addEventListener('load', function() { + // Your service-worker.js *must* be located at the top-level directory relative to your site. + // It won't be able to control pages unless it's located at the same level or higher than them. + // *Don't* register service worker file in, e.g., a scripts/ sub-directory! + // See https://github.com/slightlyoff/ServiceWorker/issues/468 + navigator.serviceWorker.register('service-worker.js').then(function(reg) { + // updatefound is fired if service-worker.js changes. + reg.onupdatefound = function() { + // The updatefound event implies that reg.installing is set; see + // https://w3c.github.io/ServiceWorker/#service-worker-registration-updatefound-event + var installingWorker = reg.installing; + + installingWorker.onstatechange = function() { + switch (installingWorker.state) { + case 'installed': + if (navigator.serviceWorker.controller) { + // At this point, the old content will have been purged and the fresh content will + // have been added to the cache. + // It's the perfect time to display a "New content is available; please refresh." + // message in the page's interface. + console.log('New or updated content is available.'); + } else { + // At this point, everything has been precached. + // It's the perfect time to display a "Content is cached for offline use." message. + console.log('Content is now available offline!'); + } + break; + + case 'redundant': + console.error('The installing service worker became redundant.'); + break; + } + }; + }; + }).catch(function(e) { + console.error('Error during service worker registration:', e); + }); + }); +} diff --git a/themes/cupper-hugo-theme/static/mstile-150x150.png b/themes/cupper-hugo-theme/static/mstile-150x150.png new file mode 100644 index 0000000..2f0b1de Binary files /dev/null and b/themes/cupper-hugo-theme/static/mstile-150x150.png differ diff --git a/themes/cupper-hugo-theme/static/safari-pinned-tab.svg b/themes/cupper-hugo-theme/static/safari-pinned-tab.svg new file mode 100644 index 0000000..ad367e2 --- /dev/null +++ b/themes/cupper-hugo-theme/static/safari-pinned-tab.svg @@ -0,0 +1,78 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + + + + + + + + diff --git a/themes/cupper-hugo-theme/static/site.webmanifest b/themes/cupper-hugo-theme/static/site.webmanifest new file mode 100644 index 0000000..b20abb7 --- /dev/null +++ b/themes/cupper-hugo-theme/static/site.webmanifest @@ -0,0 +1,19 @@ +{ + "name": "", + "short_name": "", + "icons": [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} diff --git a/themes/cupper-hugo-theme/task_serve.sh b/themes/cupper-hugo-theme/task_serve.sh new file mode 100755 index 0000000..80281fd --- /dev/null +++ b/themes/cupper-hugo-theme/task_serve.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +cd exampleSite +hugo serve --themesDir ../.. +cd .. diff --git a/themes/cupper-hugo-theme/theme.toml b/themes/cupper-hugo-theme/theme.toml new file mode 100644 index 0000000..8d8ef0c --- /dev/null +++ b/themes/cupper-hugo-theme/theme.toml @@ -0,0 +1,17 @@ +name = "Cupper" +license = "MIT" +licenselink = "https://github.com/zwbetz-gh/cupper-hugo-theme/blob/master/LICENSE" +description = "An accessibility-friendly Hugo theme, ported from the original Cupper project." +homepage = "https://github.com/zwbetz-gh/cupper-hugo-theme" +tags = ["accessible", "blog", "documentation", "dark"] +features = ["accessible", "blog", "documentation", "dark"] +min_version = "0.60.1" + +[author] + name = "Zachary Betz" + homepage = "https://zwbetz.com/" + +[original] + name = "Cupper" + homepage = "https://thepaciellogroup.github.io/cupper/" + repo = "https://github.com/ThePacielloGroup/cupper" \ No newline at end of file