+
+
{% set contrast_wcag = '' %}
{% if color_fg and color_bg %}
{% set contrast_wcag = color_bg.contrast(color_fg, 'WCAG21') %}
diff --git a/docs/_layouts/palette.njk b/docs/_layouts/palette.njk
index f122dec0a..bd32aa7dd 100644
--- a/docs/_layouts/palette.njk
+++ b/docs/_layouts/palette.njk
@@ -1,17 +1,24 @@
{% set hasSidebar = true %}
{% set hasOutline = true %}
-{# {% set forceTheme = page.fileSlug %} #}
+{% set paletteId = page.fileSlug %}
{% extends '../_layouts/block.njk' %}
-{% set paletteId = page.fileSlug %}
+{% block head %}
+
+
+{% endblock %}
{% block afterContent %}
-
{% set tints = ["95", "90", "80", "70", "60", "50", "40", "30", "20", "10", "05"] %}
+{% set maxChroma = 0 %}
+
+
-
+
|
@@ -21,17 +28,57 @@
{%- endfor %}
+{# Initialize to last hue before gray #}
+{%- set hueBefore = hues[hues|length - 2] -%}
{% for hue in hues -%}
-
- | {{ hue | capitalize }} |
-
-
+{%- set coreColor = palettes[paletteId][hue][palettes[paletteId][hue].maxChromaTint] -%}
+{%- set maxChroma = coreColor.c if coreColor.c > maxChroma else maxChroma -%}
+
+ |
+ {{ hue | capitalize }}
+ |
+
+ {% if hue !== 'gray' %}
+ {%- set hueAfter = hues[loop.index0 + 1] -%}
+ {%- set hueAfter = hues[0] if hueAfter == 'gray' else hueAfter -%}
+ {%- set minShift = hueRanges[hue].min - coreColor.h | round -%}
+ {%- set maxShift = hueRanges[hue].max - coreColor.h | round -%}
+
+
{{ palettes[paletteId][hue].maxChromaTint }}
-
+
+ `
+
+ {%- set hueBefore = hue -%}
+ {% else %}
+
+ {{ palettes[paletteId][hue].maxChromaTint }}
+
+ {% endif %}
|
{% for tint in tints -%}
-
+ {%- set color = palettes[paletteId][hue][tint] -%}
+ |
@@ -41,6 +88,37 @@
{%- endfor %}
| |
+{% set chromaScaleBounds = [
+(0.08 / maxChroma) | number({maximumFractionDigits: 2}),
+(0.3 / maxChroma]) | number({maximumFractionDigits: 2}) -%}
+
+
+
+ Overall colorfulness
+
+
+
+ More muted
+ More vibrant
+
+
+
+
+
+
+
+
Used By
@@ -65,6 +143,7 @@ A difference of `40` ensures a minimum **3:1** contrast ratio, suitable for larg
{% endmarkdown %}
{% set difference = 40 %}
+{% set minContrast = 3 %}
{% include "contrast-table.njk" %}
{% markdown %}
@@ -84,6 +163,7 @@ A difference of `50` ensures a minimum **4.5:1** contrast ratio, suitable for no
{% endmarkdown %}
{% set difference = 50 %}
+{% set minContrast = 4.5 %}
{% include "contrast-table.njk" %}
{% markdown %}
@@ -102,6 +182,7 @@ A difference of `60` ensures a minimum **7:1** contrast ratio, suitable for all
{% endmarkdown %}
{% set difference = 60 %}
+{% set minContrast = 7 %}
{% include "contrast-table.njk" %}
{% markdown %}
@@ -114,7 +195,7 @@ This also goes for a difference of `65`:
{% include "contrast-table.njk" %}
{% markdown %}
-## How to use this palette
+## How to use this palette { #usage }
If you are using a Web Awesome theme that uses this palette, it will already be included.
To use a different palette than a theme default, or to use it in a custom theme, you can import this palette directly from the Web Awesome CDN.
diff --git a/docs/assets/scripts/prism-downloaded.js b/docs/assets/scripts/prism-downloaded.js
new file mode 100644
index 000000000..be9653dd6
--- /dev/null
+++ b/docs/assets/scripts/prism-downloaded.js
@@ -0,0 +1,8 @@
+/* PrismJS 1.29.0
+https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript&plugins=custom-class */
+var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(e){var n=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,t=0,r={},a={manual:e.Prism&&e.Prism.manual,disableWorkerMessageHandler:e.Prism&&e.Prism.disableWorkerMessageHandler,util:{encode:function e(n){return n instanceof i?new i(n.type,e(n.content),n.alias):Array.isArray(n)?n.map(e):n.replace(/&/g,"&").replace(/=g.reach);A+=w.value.length,w=w.next){var E=w.value;if(n.length>e.length)return;if(!(E instanceof i)){var P,L=1;if(y){if(!(P=l(b,A,e,m))||P.index>=e.length)break;var S=P.index,O=P.index+P[0].length,j=A;for(j+=w.value.length;S>=j;)j+=(w=w.next).value.length;if(A=j-=w.value.length,w.value instanceof i)continue;for(var C=w;C!==n.tail&&(jg.reach&&(g.reach=W);var z=w.prev;if(_&&(z=u(n,z,_),A+=_.length),c(n,z,L),w=u(n,z,new i(f,p?a.tokenize(N,p):N,k,N)),M&&u(n,w,M),L>1){var I={cause:f+","+d,reach:W};o(e,n,t,w.prev,A,I),g&&I.reach>g.reach&&(g.reach=I.reach)}}}}}}function s(){var e={value:null,prev:null,next:null},n={value:null,prev:e,next:null};e.next=n,this.head=e,this.tail=n,this.length=0}function u(e,n,t){var r=n.next,a={value:t,prev:n,next:r};return n.next=a,r.prev=a,e.length++,a}function c(e,n,t){for(var r=n.next,a=0;a"+i.content+""+i.tag+">"},!e.document)return e.addEventListener?(a.disableWorkerMessageHandler||e.addEventListener("message",(function(n){var t=JSON.parse(n.data),r=t.language,i=t.code,l=t.immediateClose;e.postMessage(a.highlight(i,a.languages[r],r)),l&&e.close()}),!1),a):a;var g=a.util.currentScript();function f(){a.manual||a.highlightAll()}if(g&&(a.filename=g.src,g.hasAttribute("data-manual")&&(a.manual=!0)),!a.manual){var h=document.readyState;"loading"===h||"interactive"===h&&g&&g.defer?document.addEventListener("DOMContentLoaded",f):window.requestAnimationFrame?window.requestAnimationFrame(f):window.setTimeout(f,16)}return a}(_self);"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism);
+Prism.languages.markup={comment:{pattern://,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},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/i,name:/[^\s<>'"]+/}},cdata:{pattern://i,greedy:!0},tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/?[\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 t={"included-cdata":{pattern://i,inside:s}};t["language-"+e]={pattern:/[\s\S]+/,inside:Prism.languages[e]};var n={};n[a]={pattern:RegExp("(<__[^>]*>)(?:))*\\]\\]>|(?!)".replace(/__/g,(function(){return a})),"i"),lookbehind:!0,greedy:!0,inside:t},Prism.languages.insertBefore("markup","cdata",n)}}),Object.defineProperty(Prism.languages.markup.tag,"addAttribute",{value:function(a,e){Prism.languages.markup.tag.inside["special-attr"].push({pattern:RegExp("(^|[\"'\\s])(?:"+a+")\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s'\">=]+(?=[\\s>]))","i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[e,"language-"+e],inside:Prism.languages[e]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),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])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/;s.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:RegExp("@[\\w-](?:[^;{\\s\"']|\\s+(?!\\s)|"+e.source+")*?(?:;|(?=\\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:{pattern:RegExp("(^|[{}\\s])[^{}\\s](?:[^{};\"'\\s]|\\s+(?![\\s{])|"+e.source+")*(?=\\s*\\{)"),lookbehind:!0},string:{pattern:e,greedy:!0},property:{pattern:/(^|[^-\w\xA0-\uFFFF])(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i,lookbehind:!0},important:/!important\b/i,function:{pattern:/(^|[^-a-z0-9])[-a-z0-9]+(?=\()/i,lookbehind:!0},punctuation:/[(){};:,]/},s.languages.css.atrule.inside.rest=s.languages.css;var t=s.languages.markup;t&&(t.tag.addInlined("style","css"),t.tag.addAttribute("style","css"))}(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|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\b/,boolean:/\b(?:false|true)\b/,function:/\b\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])*(?=\.(?:constructor|prototype))/,lookbehind:!0}],keyword:[{pattern:/((?:^|\})\s*)catch\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|assert(?=\s*\{)|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|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:{pattern:RegExp("(^|[^\\w$])(?:NaN|Infinity|0[bB][01]+(?:_[01]+)*n?|0[oO][0-7]+(?:_[0-7]+)*n?|0[xX][\\dA-Fa-f]+(?:_[\\dA-Fa-f]+)*n?|\\d+(?:_\\d+)*n|(?:\\d+(?:_\\d+)*(?:\\.(?:\\d+(?:_\\d+)*)?)?|\\.\\d+(?:_\\d+)*)(?:[Ee][+-]?\\d+(?:_\\d+)*)?)(?![\\w$])"),lookbehind:!0},operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),Prism.languages.javascript["class-name"][0].pattern=/(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/,Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:RegExp("((?:^|[^$\\w\\xA0-\\uFFFF.\"'\\])\\s]|\\b(?:return|yield))\\s*)/(?:(?:\\[(?:[^\\]\\\\\r\n]|\\\\.)*\\]|\\\\.|[^/\\\\\\[\r\n])+/[dgimyus]{0,7}|(?:\\[(?:[^[\\]\\\\\r\n]|\\\\.|\\[(?:[^[\\]\\\\\r\n]|\\\\.|\\[(?:[^[\\]\\\\\r\n]|\\\\.)*\\])*\\])*\\]|\\\\.|[^/\\\\\\[\r\n])+/[dgimyus]{0,7}v[dgimyus]{0,7})(?=(?:\\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-delimiter":/^\/|\/$/,"regex-flags":/^[a-z]+$/}},"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:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,lookbehind:!0,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",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:"comment"},"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]+/}},"string-property":{pattern:/((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m,lookbehind:!0,greedy:!0,alias:"property"}}),Prism.languages.insertBefore("javascript","operator",{"literal-property":{pattern:/((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,lookbehind:!0,alias:"property"}}),Prism.languages.markup&&(Prism.languages.markup.tag.addInlined("script","javascript"),Prism.languages.markup.tag.addAttribute("on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)","javascript")),Prism.languages.js=Prism.languages.javascript;
+!function(){if("undefined"!=typeof Prism){var n,s,a="";Prism.plugins.customClass={add:function(s){n=s},map:function(n){s="function"==typeof n?n:function(s){return n[s]||s}},prefix:function(n){a=n||""},apply:t},Prism.hooks.add("wrap",(function(e){if(n){var u=n({content:e.content,type:e.type,language:e.language});Array.isArray(u)?e.classes.push.apply(e.classes,u):u&&e.classes.push(u)}(s||a)&&(e.classes=e.classes.map((function(n){return t(n,e.language)})))}))}function t(n,t){return a+(s?s(n,t):n)}}();
diff --git a/docs/assets/scripts/prism.js b/docs/assets/scripts/prism.js
index be9653dd6..215b8092d 100644
--- a/docs/assets/scripts/prism.js
+++ b/docs/assets/scripts/prism.js
@@ -1,8 +1,8 @@
-/* PrismJS 1.29.0
-https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript&plugins=custom-class */
-var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(e){var n=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,t=0,r={},a={manual:e.Prism&&e.Prism.manual,disableWorkerMessageHandler:e.Prism&&e.Prism.disableWorkerMessageHandler,util:{encode:function e(n){return n instanceof i?new i(n.type,e(n.content),n.alias):Array.isArray(n)?n.map(e):n.replace(/&/g,"&").replace(/=g.reach);A+=w.value.length,w=w.next){var E=w.value;if(n.length>e.length)return;if(!(E instanceof i)){var P,L=1;if(y){if(!(P=l(b,A,e,m))||P.index>=e.length)break;var S=P.index,O=P.index+P[0].length,j=A;for(j+=w.value.length;S>=j;)j+=(w=w.next).value.length;if(A=j-=w.value.length,w.value instanceof i)continue;for(var C=w;C!==n.tail&&(jg.reach&&(g.reach=W);var z=w.prev;if(_&&(z=u(n,z,_),A+=_.length),c(n,z,L),w=u(n,z,new i(f,p?a.tokenize(N,p):N,k,N)),M&&u(n,w,M),L>1){var I={cause:f+","+d,reach:W};o(e,n,t,w.prev,A,I),g&&I.reach>g.reach&&(g.reach=I.reach)}}}}}}function s(){var e={value:null,prev:null,next:null},n={value:null,prev:e,next:null};e.next=n,this.head=e,this.tail=n,this.length=0}function u(e,n,t){var r=n.next,a={value:t,prev:n,next:r};return n.next=a,r.prev=a,e.length++,a}function c(e,n,t){for(var r=n.next,a=0;a"+i.content+""+i.tag+">"},!e.document)return e.addEventListener?(a.disableWorkerMessageHandler||e.addEventListener("message",(function(n){var t=JSON.parse(n.data),r=t.language,i=t.code,l=t.immediateClose;e.postMessage(a.highlight(i,a.languages[r],r)),l&&e.close()}),!1),a):a;var g=a.util.currentScript();function f(){a.manual||a.highlightAll()}if(g&&(a.filename=g.src,g.hasAttribute("data-manual")&&(a.manual=!0)),!a.manual){var h=document.readyState;"loading"===h||"interactive"===h&&g&&g.defer?document.addEventListener("DOMContentLoaded",f):window.requestAnimationFrame?window.requestAnimationFrame(f):window.setTimeout(f,16)}return a}(_self);"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism);
-Prism.languages.markup={comment:{pattern://,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},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/i,name:/[^\s<>'"]+/}},cdata:{pattern://i,greedy:!0},tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/?[\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 t={"included-cdata":{pattern://i,inside:s}};t["language-"+e]={pattern:/[\s\S]+/,inside:Prism.languages[e]};var n={};n[a]={pattern:RegExp("(<__[^>]*>)(?:))*\\]\\]>|(?!)".replace(/__/g,(function(){return a})),"i"),lookbehind:!0,greedy:!0,inside:t},Prism.languages.insertBefore("markup","cdata",n)}}),Object.defineProperty(Prism.languages.markup.tag,"addAttribute",{value:function(a,e){Prism.languages.markup.tag.inside["special-attr"].push({pattern:RegExp("(^|[\"'\\s])(?:"+a+")\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s'\">=]+(?=[\\s>]))","i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[e,"language-"+e],inside:Prism.languages[e]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),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])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/;s.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:RegExp("@[\\w-](?:[^;{\\s\"']|\\s+(?!\\s)|"+e.source+")*?(?:;|(?=\\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:{pattern:RegExp("(^|[{}\\s])[^{}\\s](?:[^{};\"'\\s]|\\s+(?![\\s{])|"+e.source+")*(?=\\s*\\{)"),lookbehind:!0},string:{pattern:e,greedy:!0},property:{pattern:/(^|[^-\w\xA0-\uFFFF])(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i,lookbehind:!0},important:/!important\b/i,function:{pattern:/(^|[^-a-z0-9])[-a-z0-9]+(?=\()/i,lookbehind:!0},punctuation:/[(){};:,]/},s.languages.css.atrule.inside.rest=s.languages.css;var t=s.languages.markup;t&&(t.tag.addInlined("style","css"),t.tag.addAttribute("style","css"))}(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|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\b/,boolean:/\b(?:false|true)\b/,function:/\b\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])*(?=\.(?:constructor|prototype))/,lookbehind:!0}],keyword:[{pattern:/((?:^|\})\s*)catch\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|assert(?=\s*\{)|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|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:{pattern:RegExp("(^|[^\\w$])(?:NaN|Infinity|0[bB][01]+(?:_[01]+)*n?|0[oO][0-7]+(?:_[0-7]+)*n?|0[xX][\\dA-Fa-f]+(?:_[\\dA-Fa-f]+)*n?|\\d+(?:_\\d+)*n|(?:\\d+(?:_\\d+)*(?:\\.(?:\\d+(?:_\\d+)*)?)?|\\.\\d+(?:_\\d+)*)(?:[Ee][+-]?\\d+(?:_\\d+)*)?)(?![\\w$])"),lookbehind:!0},operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),Prism.languages.javascript["class-name"][0].pattern=/(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/,Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:RegExp("((?:^|[^$\\w\\xA0-\\uFFFF.\"'\\])\\s]|\\b(?:return|yield))\\s*)/(?:(?:\\[(?:[^\\]\\\\\r\n]|\\\\.)*\\]|\\\\.|[^/\\\\\\[\r\n])+/[dgimyus]{0,7}|(?:\\[(?:[^[\\]\\\\\r\n]|\\\\.|\\[(?:[^[\\]\\\\\r\n]|\\\\.|\\[(?:[^[\\]\\\\\r\n]|\\\\.)*\\])*\\])*\\]|\\\\.|[^/\\\\\\[\r\n])+/[dgimyus]{0,7}v[dgimyus]{0,7})(?=(?:\\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-delimiter":/^\/|\/$/,"regex-flags":/^[a-z]+$/}},"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:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,lookbehind:!0,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",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:"comment"},"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]+/}},"string-property":{pattern:/((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m,lookbehind:!0,greedy:!0,alias:"property"}}),Prism.languages.insertBefore("javascript","operator",{"literal-property":{pattern:/((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,lookbehind:!0,alias:"property"}}),Prism.languages.markup&&(Prism.languages.markup.tag.addInlined("script","javascript"),Prism.languages.markup.tag.addAttribute("on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)","javascript")),Prism.languages.js=Prism.languages.javascript;
-!function(){if("undefined"!=typeof Prism){var n,s,a="";Prism.plugins.customClass={add:function(s){n=s},map:function(n){s="function"==typeof n?n:function(s){return n[s]||s}},prefix:function(n){a=n||""},apply:t},Prism.hooks.add("wrap",(function(e){if(n){var u=n({content:e.content,type:e.type,language:e.language});Array.isArray(u)?e.classes.push.apply(e.classes,u):u&&e.classes.push(u)}(s||a)&&(e.classes=e.classes.map((function(n){return t(n,e.language)})))}))}function t(n,t){return a+(s?s(n,t):n)}}();
+globalThis.Prism = globalThis.Prism || {};
+globalThis.Prism.manual = true;
+
+await import('./prism-downloaded.js');
+
+Prism.plugins.customClass.prefix('code-');
+
+export default Prism;
diff --git a/docs/assets/scripts/remix.js b/docs/assets/scripts/remix.js
deleted file mode 100644
index fb99d19df..000000000
--- a/docs/assets/scripts/remix.js
+++ /dev/null
@@ -1,37 +0,0 @@
-/**
- * Get import code for remixed themes.
- */
-export const urls = {
- colors: id => `styles/themes/${id}/color.css`,
- palette: id => `styles/color/${id}.css`,
- brand: id => `styles/brand/${id}.css`,
- typography: id => `styles/themes/${id}/typography.css`,
-};
-
-function getImport(url, options = {}) {
- let { language = 'html', cdnUrl = '/dist/', attributes } = options;
- url = cdnUrl + url;
-
- if (language === 'css') {
- return `@import url('${url}');`;
- } else {
- attributes = attributes ? ` ${attributes}` : '';
- return ``;
- }
-}
-
-export function getCode(base, params, options) {
- let ret = [];
-
- if (base) {
- ret.push(`styles/themes/${base}.css`);
- }
-
- ret.push(
- ...Object.entries(params)
- .filter(([aspect, id]) => Boolean(id))
- .map(([aspect, id]) => urls[aspect](id)),
- );
-
- return ret.map(url => getImport(url, options)).join('\n');
-}
diff --git a/docs/assets/scripts/tweak.js b/docs/assets/scripts/tweak.js
new file mode 100644
index 000000000..3d70494e1
--- /dev/null
+++ b/docs/assets/scripts/tweak.js
@@ -0,0 +1,6 @@
+/**
+ * Get import code for remixed themes and tweaked palettes.
+ */
+export { palette as getPaletteCode, theme as getThemeCode } from './tweak/code.js';
+export { cdnUrl, hueRanges, hues, selectors, tints, urls } from './tweak/data.js';
+export { default as Permalink } from './tweak/permalink.js';
diff --git a/docs/assets/scripts/tweak/code.js b/docs/assets/scripts/tweak/code.js
new file mode 100644
index 000000000..5fdab34c4
--- /dev/null
+++ b/docs/assets/scripts/tweak/code.js
@@ -0,0 +1,109 @@
+/**
+ * Get import code for remixed themes and tweaked palettes.
+ */
+import { selectors, urls } from './data.js';
+
+export function cssImport(url, options = {}) {
+ let { language = 'html', cdnUrl = '/dist/', attributes } = options;
+ url = cdnUrl + url;
+
+ if (language === 'css') {
+ return `@import url('${url}');`;
+ } else {
+ attributes = attributes ? ` ${attributes}` : '';
+ return ``;
+ }
+}
+
+export function cssLiteral(value, options = {}) {
+ let { language = 'html' } = options;
+
+ if (language === 'css') {
+ return value;
+ } else {
+ return ``;
+ }
+}
+
+export function theme(base, params, options) {
+ let ret = [];
+
+ if (base) {
+ ret.push(urls.theme(base));
+ }
+
+ ret.push(
+ ...Object.entries(params)
+ .filter(([aspect, id]) => Boolean(id))
+ .map(([aspect, id]) => urls[aspect](id)),
+ );
+
+ return ret.map(url => cssImport(url, options)).join('\n');
+}
+
+export function palette(paletteId, tweaks, options) {
+ let imports = [];
+
+ if (paletteId) {
+ imports.push(urls.palette(paletteId));
+ }
+
+ let css = '';
+
+ if (tweaks) {
+ let { hueShifts, chromaScale = 1 } = tweaks;
+ let declarations = [];
+
+ if (chromaScale !== 1) {
+ declarations.push(`--wa-chroma-scale: ${chromaScale};`);
+ }
+
+ if (hueShifts || chromaScale !== 1) {
+ let element = document.querySelector(`.wa-palette-${paletteId}`) ?? document.documentElement;
+ let cs = getComputedStyle(element);
+
+ for (let hue in hueShifts) {
+ let shift = hueShifts[hue];
+
+ if ((!shift && chromaScale === 1) || hue === 'orange') {
+ continue;
+ }
+
+ let shiftCode = shift > 0 ? `+ ${shift}` : `- ${-shift}`;
+ let c = chromaScale === 1 ? 'c' : `calc(c * var(--wa-chroma-scale))`;
+ let h = shift ? `calc(h ${shiftCode})` : 'h';
+ declarations.push(`--wa-color-${hue}-tweak: l ${c} ${h});`);
+
+ for (let suffix of ['', '-05', '-10', '-20', '-30', '-40', '-50', '-60', '-70', '-80', '-90', '-95']) {
+ let baseColor = cs.getPropertyValue(`--wa-color-${hue}${suffix}`);
+ // Work around https://bugs.webkit.org/show_bug.cgi?id=287637
+ let colorSpace = ['-95', '-90'].includes(suffix) ? ' lch' : 'oklch';
+ let value = `${colorSpace}(from ${baseColor.padEnd(7)} var(--wa-color-${hue}-tweak))`;
+ suffix = (suffix + ':').padEnd(4);
+ declarations.push(`--wa-color-${hue}${suffix} ${value};`);
+ }
+
+ declarations.push('');
+ }
+ }
+
+ if (declarations.length > 0) {
+ css += cssRule(selectors.palette(paletteId), declarations);
+ }
+ }
+
+ let ret = imports.map(url => cssImport(url, options)).join('\n');
+
+ if (css) {
+ ret += `\n\n${cssLiteral(css, options)}`;
+ }
+
+ return ret;
+}
+
+export function cssRule(selector, declarations, { indent = ' ' } = {}) {
+ selector = Array.isArray(selector) ? selector.flat().join(',\n') : selector;
+ declarations = Array.isArray(declarations) ? declarations.flat() : declarations;
+ declarations = declarations.map(declaration => indent + declaration.trim()).join('\n');
+ return `${selector} {\n${declarations.trimEnd()}\n}`;
+}
diff --git a/docs/assets/scripts/tweak/data.js b/docs/assets/scripts/tweak/data.js
new file mode 100644
index 000000000..41ef8efe3
--- /dev/null
+++ b/docs/assets/scripts/tweak/data.js
@@ -0,0 +1,34 @@
+/**
+ * Data related to theme remixing and palette tweaking
+ * Must work in both browser and Node.js
+ */
+export const cdnUrl = globalThis.document ? document.documentElement.dataset.cdnUrl : '/dist/';
+
+export const urls = {
+ theme: id => `styles/themes/${id}.css`,
+ colors: id => `styles/themes/${id}/color.css`,
+ palette: id => `styles/color/${id}.css`,
+ brand: id => `styles/brand/${id}.css`,
+ typography: id => `styles/themes/${id}/typography.css`,
+};
+
+export const selectors = {
+ palette: id =>
+ [':where(:root)', ':host', ":where([class^='wa-theme-'], [class*=' wa-theme-'])", `.wa-palette-${id}`].join(',\n'),
+};
+
+export const hueRanges = {
+ red: { min: 5, max: 35 }, // 30
+ orange: { min: 35, max: 60 }, // 25
+ yellow: { min: 60, max: 112 }, // 45
+ green: { min: 112, max: 170 }, // 55
+ cyan: { min: 170, max: 220 }, // 50
+ blue: { min: 220, max: 265 }, // 45
+ indigo: { min: 265, max: 290 }, // 25
+ purple: { min: 290, max: 320 }, // 30
+ pink: { min: 320, max: 365 }, // 45
+};
+
+export const hues = Object.keys(hueRanges);
+
+export const tints = ['05', '10', '20', '30', '40', '50', '60', '70', '80', '90', '95'];
diff --git a/docs/assets/scripts/tweak/permalink.js b/docs/assets/scripts/tweak/permalink.js
new file mode 100644
index 000000000..b462a4e23
--- /dev/null
+++ b/docs/assets/scripts/tweak/permalink.js
@@ -0,0 +1,79 @@
+const IDENTITY = x => x;
+
+export default class Permalink extends URLSearchParams {
+ /** Params changed since last URL I/O */
+ changed = false;
+
+ constructor(params) {
+ super(location.search);
+ this.params = params;
+ }
+
+ toJSON() {
+ return Object.fromEntries(this.entries());
+ }
+
+ #mappings = new WeakMap();
+
+ mapObject(obj, mapping = {}) {
+ this.#mappings.set(obj, mapping);
+ }
+
+ readFrom(obj) {
+ let mapping = this.#mappings.get(obj) ?? {};
+ let { keyFrom = IDENTITY, valueFrom = IDENTITY } = mapping;
+
+ for (let key in obj) {
+ let value = obj[key];
+ let mappedValue = valueFrom(value);
+ let mappedKey = keyFrom(key);
+ this.set(mappedKey, mappedValue);
+ }
+ }
+
+ writeTo(obj) {
+ let mapping = this.#mappings.get(obj) ?? {};
+ let { keyTo = IDENTITY, valueTo = IDENTITY, canExtend = false } = mapping;
+
+ for (let [key, value] of this) {
+ let mappedKey = keyTo(key);
+ let mappedValue = valueTo(value);
+
+ if (canExtend || mappedKey in obj) {
+ obj[mappedKey] = mappedValue;
+ }
+ }
+ }
+
+ set(key, value, defaultValue) {
+ let oldValue = this.get(key);
+
+ if (!value || value == defaultValue) {
+ super.delete(key);
+
+ if (oldValue) {
+ this.changed = true;
+ }
+ } else {
+ super.set(key, value);
+
+ if (String(value) !== String(oldValue)) {
+ this.changed = true;
+ }
+ }
+ }
+
+ /**
+ * Update page URL if it has changed since last time
+ */
+ updateLocation() {
+ if (this.changed) {
+ // If there’s already a search, replace it.
+ // We don’t want to clog the user’s history while they iterate
+ let search = this.toString();
+ let historyAction = location.search && search ? 'replaceState' : 'pushState';
+ history[historyAction](null, '', `?${search}`);
+ this.changed = false;
+ }
+ }
+}
diff --git a/docs/assets/styles/docs.css b/docs/assets/styles/docs.css
index 7491fc306..c30575678 100644
--- a/docs/assets/styles/docs.css
+++ b/docs/assets/styles/docs.css
@@ -400,9 +400,18 @@ wa-page > main:has(> .index-grid) {
&.color {
border-color: transparent;
+ transition: background var(--wa-transition-slow);
+ background: linear-gradient(var(--color-2, transparent) 0% 100%) no-repeat border-box var(--color,);
+ background-position: var(--color-2-position, bottom);
+ background-size: var(--color-2-width, 100%) var(--color-2-height, 50%);
+
+ &.contrast-fail {
+ outline: 1px dashed var(--wa-color-red);
+ outline-offset: calc(-1 * var(--wa-space-2xs));
+ }
}
- wa-copy-button {
+ > wa-copy-button {
position: absolute;
top: 0;
left: 0;
@@ -463,6 +472,34 @@ table.colors {
}
}
+.value-up,
+.value-down {
+ position: relative;
+
+ &::after {
+ content: ' ' var(--icon);
+ position: absolute;
+ margin-inline-start: 3em;
+ scale: 1 0.6;
+ color: color-mix(in oklch, oklch(from var(--icon-color) none c h) 0%, oklch(from currentColor l none none));
+ font-size: 90%;
+ }
+}
+
+.value-down {
+ --icon: '▼';
+ --icon-color: var(--wa-color-danger-fill-quiet);
+
+ &::after {
+ margin-block-end: -0.2em;
+ }
+}
+
+.value-up {
+ --icon: '▲';
+ --icon-color: var(--wa-color-success-fill-quiet);
+}
+
/* Layout Examples */
.layout-example-boundary {
border: var(--wa-border-width-s) dashed var(--wa-color-neutral-border-normal);
diff --git a/docs/docs/palettes/tweak.css b/docs/docs/palettes/tweak.css
new file mode 100644
index 000000000..82222242a
--- /dev/null
+++ b/docs/docs/palettes/tweak.css
@@ -0,0 +1,140 @@
+:root {
+ --fa-sliders-simple: '\f1de';
+}
+
+.core-column {
+ position: relative;
+
+ > wa-dropdown {
+ min-width: 100%;
+ }
+}
+
+wa-dropdown > .color.swatch {
+ cursor: pointer;
+}
+
+.decorated-slider {
+ display: grid;
+ grid-template-columns: auto 1fr auto;
+ margin-block-end: var(--wa-space-xl);
+
+ wa-slider {
+ grid-column: 1 / -1;
+ --track-height: 1em;
+ --track-color-inactive: transparent;
+ --track-color-active: transparent;
+ --thumb-color: var(--color-tweaked, var(--color));
+
+ &::part(base) {
+ background: linear-gradient(to right in oklch, var(--color-1), var(--color-2));
+ }
+ }
+
+ [slot='label'] {
+ min-height: 1.1lh;
+ }
+
+ .clear-button {
+ vertical-align: middle;
+ font-size: var(--wa-font-size-xs);
+
+ &:not(.tweaked *) {
+ display: none;
+ }
+ }
+
+ .label-min,
+ .label-max {
+ font-size: var(--wa-font-size-xs);
+ }
+
+ .label-min {
+ grid-column: 1;
+ margin-inline-start: 0.15em;
+ }
+
+ .label-max {
+ grid-column: 3;
+ margin-inline-end: 0.1em;
+ }
+}
+
+.hue-shift-slider {
+ --color-1: oklch(from var(--color) l c calc(h + var(--min, 0)));
+ --color-2: oklch(from var(--color) l c calc(h + var(--max, 0)));
+}
+
+.chroma-scale-slider {
+ --color: var(--wa-color-brand);
+ --color-1: oklch(from var(--color) l calc(c * var(--min)) h);
+ --color-2: oklch(from var(--color) l calc(c * var(--max)) h);
+ --color-tweaked: oklch(from var(--color) l calc(c * var(--chroma-scale)) h);
+}
+
+.popup {
+ background: var(--wa-color-surface-default);
+ border: 1px solid var(--wa-color-surface-border);
+ padding: var(--wa-space-xl);
+ border-radius: var(--wa-border-radius-m);
+
+ code {
+ white-space: nowrap;
+ }
+}
+
+.color-scale {
+ th {
+ white-space: nowrap;
+ }
+
+ td:not([data-hue='gray'] *) {
+ --tweak-c: calc(c * var(--chroma-scale, 1));
+ --tweak-h: calc(h + var(--hue-shift, 0));
+ --color-tweaked: oklch(from var(--color) l var(--tweak-c) var(--tweak-h));
+ --color-tweaked-no-chroma-scale: oklch(from var(--color) l c var(--tweak-h));
+ --color-tweaked-no-hue-shift: oklch(from var(--color) l var(--tweak-c) h);
+
+ &:is([data-tint='90'], [data-tint='95']) {
+ /* Work around https://bugs.webkit.org/show_bug.cgi?id=287637 */
+ --color-tweaked: lch(from var(--color) l var(--tweak-c) var(--tweak-h));
+ --color-tweaked-no-chroma-scale: lch(from var(--color) l c var(--tweak-h));
+ --color-tweaked-no-hue-shift: lch(from var(--color) l var(--tweak-c) h);
+
+ /* outline: 1px dashed red; */
+ }
+ }
+
+ .color.swatch {
+ --color-2: var(--color-tweaked);
+ --color-2-height: 100%;
+
+ &:is(.tweaking *) {
+ --color-2-height: 70%;
+
+ &:is(.tweaking-chroma *) {
+ --color: var(--color-tweaked-no-chroma-scale);
+ }
+
+ &:is(.tweaking-hue *) {
+ --color: var(--color-tweaked-no-hue-shift);
+ }
+ }
+ }
+
+ .tweak-icon {
+ position: absolute;
+ top: 50%;
+ transform: translateY(-50%);
+ right: var(--wa-space-s);
+ opacity: var(--tweak-icon-opacity, 0%);
+ }
+
+ .core-column:hover {
+ --tweak-icon-opacity: 40%;
+ }
+
+ &.tweaked .core-column {
+ --tweak-icon-opacity: 80%;
+ }
+}
diff --git a/docs/docs/palettes/tweak.js b/docs/docs/palettes/tweak.js
new file mode 100644
index 000000000..789f905d8
--- /dev/null
+++ b/docs/docs/palettes/tweak.js
@@ -0,0 +1,194 @@
+// TODO move these to local imports
+import Color from 'https://colorjs.io/dist/color.js';
+import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js';
+
+import { cdnUrl, getPaletteCode, hueRanges, hues, Permalink, tints } from '../../assets/scripts/tweak.js';
+import Prism from '/assets/scripts/prism.js';
+
+await Promise.all(['wa-slider'].map(tag => customElements.whenDefined(tag)));
+
+// // Detect https://bugs.webkit.org/show_bug.cgi?id=287637
+// const SAFARI_OKLCH_BUG = (() => {
+// let dummy = document.createElement('div');
+// document.body.appendChild(dummy);
+// dummy.style.color = 'oklch(from #d5e0e6 l c h)';
+// let computedColor = getComputedStyle(dummy).color;
+// dummy.remove();
+// return computedColor.endsWith(' 0)');
+// })();
+
+let paletteAppSpec = {
+ data() {
+ const { paletteId, colors, maxChroma } = wa_data;
+
+ // Replace colors with their oklch coords (since they're all opaque and all in the same color space)
+ for (let hue in colors) {
+ for (let tint of tints) {
+ colors[hue][tint] = colors[hue][tint].coords;
+ }
+ }
+
+ return {
+ permalink: new Permalink(),
+ hueRanges,
+ hueShifts: Object.fromEntries(hues.map(hue => [hue, 0])),
+ paletteId,
+ originalColors: colors,
+ maxChroma,
+ chromaScale: 1,
+ tweaking: {},
+ };
+ },
+
+ created() {
+ // Read URL params and apply them. This facilitates permalinks.
+ this.permalink.mapObject(this.hueShifts, {
+ keyTo: key => key.replace(/-shift$/, ''),
+ keyFrom: key => key + '-shift',
+ valueFrom: value => (!value ? '' : Number(value)),
+ valueTo: value => (!value ? 0 : Number(value)),
+ });
+
+ if (location.search) {
+ // Update from URL
+ this.permalink.writeTo(this.hueShifts);
+
+ if (this.permalink.has('chroma-scale')) {
+ this.chromaScale = Number(this.permalink.get('chromaScale') || 1);
+ }
+ }
+ },
+
+ mounted() {
+ if (this.isTweaked) {
+ // Update contrast colors
+ updateContrastTables(this.colors);
+ }
+ },
+
+ computed: {
+ tweaks() {
+ return { hueShifts: this.hueShifts, chromaScale: this.chromaScale };
+ },
+
+ isTweaked() {
+ return Object.values(this.hueShifts).some(Boolean);
+ },
+
+ paletteHTML() {
+ return getPaletteCode(this.paletteId, this.tweaks, { language: 'html', cdnUrl });
+ },
+
+ paletteCSS() {
+ return getPaletteCode(this.paletteId, this.tweaks, { language: 'css', cdnUrl });
+ },
+
+ colors() {
+ let ret = {};
+
+ for (let hue in this.originalColors) {
+ ret[hue] = {};
+
+ for (let tint of tints) {
+ ret[hue][tint] = this.originalColors[hue][tint].slice();
+
+ if (this.hueShifts[hue]) {
+ ret[hue][tint][2] += this.hueShifts[hue];
+ }
+
+ if (this.chromaScale !== 1) {
+ ret[hue][tint][1] *= this.chromaScale;
+ }
+ }
+ }
+
+ return ret;
+ },
+ },
+
+ watch: {
+ // Note: These could move to `v-html` directives if we widen the app root
+ paletteHTML() {
+ let codeElement = document.querySelector('#usage ~ wa-tab-group.import-stylesheet-code code.language-html');
+ codeElement.textContent = this.paletteHTML;
+ let copyButton = codeElement.previousElementSibling;
+ copyButton.value = this.paletteHTML;
+ Prism.highlightElement(codeElement);
+ },
+
+ paletteCSS() {
+ let codeElement = document.querySelector('#usage ~ wa-tab-group.import-stylesheet-code code.language-css');
+ codeElement.textContent = this.paletteCSS;
+ let copyButton = codeElement.previousElementSibling;
+ copyButton.value = this.paletteCSS;
+ Prism.highlightElement(codeElement);
+ },
+
+ hueShifts: {
+ deep: true,
+ handler() {
+ this.permalink.readFrom(this.hueShifts);
+
+ // Update page URL
+ this.permalink.updateLocation();
+
+ // Update contrast colors
+ updateContrastTables(this.colors);
+ },
+ },
+
+ chromaScale() {
+ this.permalink.set('chroma-scale', this.chromaScale, 1);
+
+ // Update page URL
+ this.permalink.updateLocation();
+
+ // Update contrast colors
+ updateContrastTables(this.colors);
+ },
+ },
+
+ compilerOptions: {
+ isCustomElement: tag => tag.startsWith('wa-'),
+ },
+};
+
+function init() {
+ globalThis.paletteApp = createApp(paletteAppSpec).mount('#palette-app');
+}
+
+function updateContrastTables(colors) {
+ for (let table of document.querySelectorAll('.contrast-table')) {
+ let { minContrast } = table.dataset;
+
+ for (let tr of table.querySelectorAll('tr[data-hue]')) {
+ let { hue } = tr.dataset;
+
+ for (let td of tr.querySelectorAll('td[data-tint-bg][data-tint-fg]')) {
+ let swatch = td.querySelector('.color.swatch');
+
+ let { tintBg, tintFg, originalContrast } = td.dataset;
+
+ let bg = new Color('oklch', colors[hue][tintBg]);
+ let fg = new Color('oklch', colors[hue][tintFg]);
+
+ if (!originalContrast) {
+ td.dataset.originalContrast = originalContrast = swatch.textContent.trim();
+ }
+
+ let contrast = bg.contrast(fg, 'WCAG21').toLocaleString(undefined, { maximumSignificantDigits: 2 });
+ swatch.textContent = contrast;
+
+ swatch.classList.toggle('value-up', contrast > originalContrast);
+ swatch.classList.toggle('value-down', contrast < originalContrast);
+ swatch.classList.toggle('contrast-fail', contrast < minContrast);
+
+ swatch.style.setProperty('--color', bg.display());
+ swatch.style.setProperty('color', fg.display());
+ }
+ }
+ }
+}
+
+init();
+addEventListener('turbo:render', init);
diff --git a/docs/docs/themes/demo.njk b/docs/docs/themes/demo.njk
index d8320e6f4..6744c81de 100644
--- a/docs/docs/themes/demo.njk
+++ b/docs/docs/themes/demo.njk
@@ -34,7 +34,8 @@ eleventyComputed:
|