;
// expands markdown for a file
// input file doesn't have to be markdown; it can be, for example, a knitr spin file
diff --git a/src/project/types/book/book-config.ts b/src/project/types/book/book-config.ts
index 05847a80ab2..8e4a4d14c85 100644
--- a/src/project/types/book/book-config.ts
+++ b/src/project/types/book/book-config.ts
@@ -144,8 +144,8 @@ export async function bookProjectConfig(
site[kSiteFavicon] = book[kSiteFavicon];
if (!site[kSiteFavicon]) {
const brand = await project.resolveBrand();
- if (brand) {
- site[kSiteFavicon] = getFavicon(brand);
+ if (brand?.light) {
+ site[kSiteFavicon] = getFavicon(brand.light); //
}
}
site[kSiteUrl] = book[kSiteUrl];
diff --git a/src/project/types/website/website-shared.ts b/src/project/types/website/website-shared.ts
index 80147c0d8af..195b196bbc9 100644
--- a/src/project/types/website/website-shared.ts
+++ b/src/project/types/website/website-shared.ts
@@ -166,12 +166,12 @@ export async function websiteNavigationConfig(project: ProjectContext) {
const projectBrand = await project.resolveBrand();
if (
- projectBrand?.processedData.logo && sidebars?.[0]
+ projectBrand?.light?.processedData.logo && sidebars?.[0]
) {
if (sidebars[0].logo === undefined) {
- const logo = projectBrand.processedData.logo.medium ??
- projectBrand.processedData.logo.small ??
- projectBrand.processedData.logo.large;
+ const logo = projectBrand.light.processedData.logo.medium ??
+ projectBrand.light.processedData.logo.small ??
+ projectBrand.light.processedData.logo.large;
if (logo) {
sidebars[0].logo = logo.light.path; // TODO: This needs smarts to work on light+dark themes
sidebars[0]["logo-alt"] = logo.light.alt;
@@ -180,11 +180,11 @@ export async function websiteNavigationConfig(project: ProjectContext) {
}
if (
- projectBrand?.processedData && navbar
+ projectBrand?.light?.processedData && navbar
) {
- const logo = projectBrand.processedData.logo.small ??
- projectBrand.processedData.logo.medium ??
- projectBrand.processedData.logo.large;
+ const logo = projectBrand.light.processedData.logo.small ??
+ projectBrand.light.processedData.logo.medium ??
+ projectBrand.light.processedData.logo.large;
if (logo) {
navbar.logo = logo.light.path; // TODO: This needs smarts to work on light+dark themes
navbar["logo-alt"] = logo.light.alt;
diff --git a/src/project/types/website/website.ts b/src/project/types/website/website.ts
index 07ecb45aa8b..3a924975abf 100644
--- a/src/project/types/website/website.ts
+++ b/src/project/types/website/website.ts
@@ -182,8 +182,8 @@ export const websiteProjectType: ProjectType = {
let favicon = websiteConfigString(kSiteFavicon, project.config);
if (!favicon) {
const brand = await project.resolveBrand();
- if (brand) {
- favicon = getFavicon(brand);
+ if (brand?.light) {
+ favicon = getFavicon(brand.light);
}
}
if (favicon) {
diff --git a/src/resources/editor/tools/vs-code.mjs b/src/resources/editor/tools/vs-code.mjs
index 597c7397424..e1b259a61c0 100644
--- a/src/resources/editor/tools/vs-code.mjs
+++ b/src/resources/editor/tools/vs-code.mjs
@@ -12544,6 +12544,42 @@ var require_yaml_intelligence_resources = __commonJS({
}
}
},
+ {
+ id: "brand-path-bool-light-dark",
+ anyOf: [
+ "string",
+ "boolean",
+ {
+ object: {
+ closed: true,
+ properties: {
+ light: {
+ anyOf: [
+ "string",
+ {
+ ref: "brand"
+ }
+ ],
+ description: "The path to a light brand file or an inline light brand definition.\n"
+ },
+ dark: {
+ anyOf: [
+ "string",
+ {
+ ref: "brand"
+ }
+ ],
+ description: "The path to a dark brand file or an inline dark brand definition.\n"
+ }
+ }
+ }
+ },
+ {
+ ref: "brand"
+ }
+ ],
+ description: "Branding information to use for this document. If a string, the path to a brand file.\nIf false, don't use branding on this document. If an object, an inline brand\ndefinition, or an object with light and dark brand paths or definitions.\n"
+ },
{
id: "brand-defaults",
object: {
@@ -16689,15 +16725,9 @@ var require_yaml_intelligence_resources = __commonJS({
{
name: "brand",
schema: {
- anyOf: [
- "string",
- "boolean",
- {
- ref: "brand"
- }
- ]
+ ref: "brand-path-bool-light-dark"
},
- description: "Branding information to use for this document. If a string, the path to a brand file.\nIf false, don't use branding on this document. If an object, an inline brand\ndefinition.\n"
+ description: "Branding information to use for this document. If a string, the path to a brand file.\nIf false, don't use branding on this document. If an object, an inline brand\ndefinition, or an object with light and dark brand paths or definitions.\n"
},
{
name: "theme",
@@ -21910,11 +21940,15 @@ var require_yaml_intelligence_resources = __commonJS({
"The font files to include. These can be local or online. Local file\npaths should be relative to the brand.yml file. Online\npaths should be complete URLs.",
"The path to the font file. This can be a local path or a URL.",
"A locally-installed font family name. When used, the end-user is\nresponsible for ensuring that the font is installed on their system.",
+ "Branding information to use for this document. If a string, the path\nto a brand file. If false, don\u2019t use branding on this document. If an\nobject, an inline brand definition, or an object with light and dark\nbrand paths or definitions.",
+ "The path to a light brand file or an inline light brand\ndefinition.",
+ "The path to a dark brand file or an inline dark brand definition.",
{
short: "Unique label for code cell",
long: "Unique label for code cell. Used when other code needs to refer to\nthe cell (e.g. for cross references fig-samples or\ntbl-summary)"
},
"Classes to apply to cell container",
+ "Array of rendering names",
"Array of tags for notebook cell",
{
short: "Notebook cell identifier",
@@ -22879,7 +22913,9 @@ var require_yaml_intelligence_resources = __commonJS({
},
"If true, force the presence of the OJS runtime. If\nfalse, force the absence instead. If unset, the OJS runtime\nis included only if OJS cells are present in the document.",
"Use the specified file as a style reference in producing a docx,\npptx, or odt file.",
- "Branding information to use for this document. If a string, the path\nto a brand file. If false, don\u2019t use branding on this document. If an\nobject, an inline brand definition.",
+ "Branding information to use for this document. If a string, the path\nto a brand file. If false, don\u2019t use branding on this document. If an\nobject, an inline brand definition, or an object with light and dark\nbrand paths or definitions.",
+ "The path to a light brand file or an inline light brand\ndefinition.",
+ "The path to a dark brand file or an inline dark brand definition.",
"Theme name, theme scss file, or a mix of both.",
"The light theme name, theme scss file, or a mix of both.",
"The light theme name, theme scss file, or a mix of both.",
@@ -23630,6 +23666,7 @@ var require_yaml_intelligence_resources = __commonJS({
"Disambiguating year suffix in author-date styles (e.g. \u201Ca\u201D in \u201CDoe,\n1999a\u201D).",
"Manuscript configuration",
"internal-schema-hack",
+ "List execution engines you want to give priority when determining\nwhich engine should render a notebook. If two engines have support for a\nnotebook, the one listed earlier will be chosen. Quarto\u2019s default order\nis \u2018knitr\u2019, \u2018jupyter\u2019, \u2018markdown\u2019, \u2018julia\u2019.",
{
short: "Include an automatically generated table of contents",
long: ""
@@ -23982,7 +24019,6 @@ var require_yaml_intelligence_resources = __commonJS({
"Disambiguating year suffix in author-date styles (e.g. \u201Ca\u201D in \u201CDoe,\n1999a\u201D).",
"Manuscript configuration",
"internal-schema-hack",
- "Array of rendering names",
"List execution engines you want to give priority when determining\nwhich engine should render a notebook. If two engines have support for a\nnotebook, the one listed earlier will be chosen. Quarto\u2019s default order\nis \u2018knitr\u2019, \u2018jupyter\u2019, \u2018markdown\u2019, \u2018julia\u2019."
],
"schema/external-schemas.yml": [
@@ -24212,12 +24248,12 @@ var require_yaml_intelligence_resources = __commonJS({
mermaid: "%%"
},
"handlers/mermaid/schema.yml": {
- _internalId: 194327,
+ _internalId: 196120,
type: "object",
description: "be an object",
properties: {
"mermaid-format": {
- _internalId: 194319,
+ _internalId: 196112,
type: "enum",
enum: [
"png",
@@ -24233,7 +24269,7 @@ var require_yaml_intelligence_resources = __commonJS({
exhaustiveCompletions: true
},
theme: {
- _internalId: 194326,
+ _internalId: 196119,
type: "anyOf",
anyOf: [
{
diff --git a/src/resources/editor/tools/yaml/web-worker.js b/src/resources/editor/tools/yaml/web-worker.js
index 62b7d2ba481..d666a0a15db 100644
--- a/src/resources/editor/tools/yaml/web-worker.js
+++ b/src/resources/editor/tools/yaml/web-worker.js
@@ -12545,6 +12545,42 @@ try {
}
}
},
+ {
+ id: "brand-path-bool-light-dark",
+ anyOf: [
+ "string",
+ "boolean",
+ {
+ object: {
+ closed: true,
+ properties: {
+ light: {
+ anyOf: [
+ "string",
+ {
+ ref: "brand"
+ }
+ ],
+ description: "The path to a light brand file or an inline light brand definition.\n"
+ },
+ dark: {
+ anyOf: [
+ "string",
+ {
+ ref: "brand"
+ }
+ ],
+ description: "The path to a dark brand file or an inline dark brand definition.\n"
+ }
+ }
+ }
+ },
+ {
+ ref: "brand"
+ }
+ ],
+ description: "Branding information to use for this document. If a string, the path to a brand file.\nIf false, don't use branding on this document. If an object, an inline brand\ndefinition, or an object with light and dark brand paths or definitions.\n"
+ },
{
id: "brand-defaults",
object: {
@@ -16690,15 +16726,9 @@ try {
{
name: "brand",
schema: {
- anyOf: [
- "string",
- "boolean",
- {
- ref: "brand"
- }
- ]
+ ref: "brand-path-bool-light-dark"
},
- description: "Branding information to use for this document. If a string, the path to a brand file.\nIf false, don't use branding on this document. If an object, an inline brand\ndefinition.\n"
+ description: "Branding information to use for this document. If a string, the path to a brand file.\nIf false, don't use branding on this document. If an object, an inline brand\ndefinition, or an object with light and dark brand paths or definitions.\n"
},
{
name: "theme",
@@ -21911,11 +21941,15 @@ try {
"The font files to include. These can be local or online. Local file\npaths should be relative to the brand.yml file. Online\npaths should be complete URLs.",
"The path to the font file. This can be a local path or a URL.",
"A locally-installed font family name. When used, the end-user is\nresponsible for ensuring that the font is installed on their system.",
+ "Branding information to use for this document. If a string, the path\nto a brand file. If false, don\u2019t use branding on this document. If an\nobject, an inline brand definition, or an object with light and dark\nbrand paths or definitions.",
+ "The path to a light brand file or an inline light brand\ndefinition.",
+ "The path to a dark brand file or an inline dark brand definition.",
{
short: "Unique label for code cell",
long: "Unique label for code cell. Used when other code needs to refer to\nthe cell (e.g. for cross references fig-samples or\ntbl-summary)"
},
"Classes to apply to cell container",
+ "Array of rendering names",
"Array of tags for notebook cell",
{
short: "Notebook cell identifier",
@@ -22880,7 +22914,9 @@ try {
},
"If true, force the presence of the OJS runtime. If\nfalse, force the absence instead. If unset, the OJS runtime\nis included only if OJS cells are present in the document.",
"Use the specified file as a style reference in producing a docx,\npptx, or odt file.",
- "Branding information to use for this document. If a string, the path\nto a brand file. If false, don\u2019t use branding on this document. If an\nobject, an inline brand definition.",
+ "Branding information to use for this document. If a string, the path\nto a brand file. If false, don\u2019t use branding on this document. If an\nobject, an inline brand definition, or an object with light and dark\nbrand paths or definitions.",
+ "The path to a light brand file or an inline light brand\ndefinition.",
+ "The path to a dark brand file or an inline dark brand definition.",
"Theme name, theme scss file, or a mix of both.",
"The light theme name, theme scss file, or a mix of both.",
"The light theme name, theme scss file, or a mix of both.",
@@ -23631,6 +23667,7 @@ try {
"Disambiguating year suffix in author-date styles (e.g. \u201Ca\u201D in \u201CDoe,\n1999a\u201D).",
"Manuscript configuration",
"internal-schema-hack",
+ "List execution engines you want to give priority when determining\nwhich engine should render a notebook. If two engines have support for a\nnotebook, the one listed earlier will be chosen. Quarto\u2019s default order\nis \u2018knitr\u2019, \u2018jupyter\u2019, \u2018markdown\u2019, \u2018julia\u2019.",
{
short: "Include an automatically generated table of contents",
long: ""
@@ -23983,7 +24020,6 @@ try {
"Disambiguating year suffix in author-date styles (e.g. \u201Ca\u201D in \u201CDoe,\n1999a\u201D).",
"Manuscript configuration",
"internal-schema-hack",
- "Array of rendering names",
"List execution engines you want to give priority when determining\nwhich engine should render a notebook. If two engines have support for a\nnotebook, the one listed earlier will be chosen. Quarto\u2019s default order\nis \u2018knitr\u2019, \u2018jupyter\u2019, \u2018markdown\u2019, \u2018julia\u2019."
],
"schema/external-schemas.yml": [
@@ -24213,12 +24249,12 @@ try {
mermaid: "%%"
},
"handlers/mermaid/schema.yml": {
- _internalId: 194327,
+ _internalId: 196120,
type: "object",
description: "be an object",
properties: {
"mermaid-format": {
- _internalId: 194319,
+ _internalId: 196112,
type: "enum",
enum: [
"png",
@@ -24234,7 +24270,7 @@ try {
exhaustiveCompletions: true
},
theme: {
- _internalId: 194326,
+ _internalId: 196119,
type: "anyOf",
anyOf: [
{
diff --git a/src/resources/editor/tools/yaml/yaml-intelligence-resources.json b/src/resources/editor/tools/yaml/yaml-intelligence-resources.json
index 08d87ba0637..663646e453a 100644
--- a/src/resources/editor/tools/yaml/yaml-intelligence-resources.json
+++ b/src/resources/editor/tools/yaml/yaml-intelligence-resources.json
@@ -5516,6 +5516,42 @@
}
}
},
+ {
+ "id": "brand-path-bool-light-dark",
+ "anyOf": [
+ "string",
+ "boolean",
+ {
+ "object": {
+ "closed": true,
+ "properties": {
+ "light": {
+ "anyOf": [
+ "string",
+ {
+ "ref": "brand"
+ }
+ ],
+ "description": "The path to a light brand file or an inline light brand definition.\n"
+ },
+ "dark": {
+ "anyOf": [
+ "string",
+ {
+ "ref": "brand"
+ }
+ ],
+ "description": "The path to a dark brand file or an inline dark brand definition.\n"
+ }
+ }
+ }
+ },
+ {
+ "ref": "brand"
+ }
+ ],
+ "description": "Branding information to use for this document. If a string, the path to a brand file.\nIf false, don't use branding on this document. If an object, an inline brand\ndefinition, or an object with light and dark brand paths or definitions.\n"
+ },
{
"id": "brand-defaults",
"object": {
@@ -9661,15 +9697,9 @@
{
"name": "brand",
"schema": {
- "anyOf": [
- "string",
- "boolean",
- {
- "ref": "brand"
- }
- ]
+ "ref": "brand-path-bool-light-dark"
},
- "description": "Branding information to use for this document. If a string, the path to a brand file.\nIf false, don't use branding on this document. If an object, an inline brand\ndefinition.\n"
+ "description": "Branding information to use for this document. If a string, the path to a brand file.\nIf false, don't use branding on this document. If an object, an inline brand\ndefinition, or an object with light and dark brand paths or definitions.\n"
},
{
"name": "theme",
@@ -14882,11 +14912,15 @@
"The font files to include. These can be local or online. Local file\npaths should be relative to the brand.yml file. Online\npaths should be complete URLs.",
"The path to the font file. This can be a local path or a URL.",
"A locally-installed font family name. When used, the end-user is\nresponsible for ensuring that the font is installed on their system.",
+ "Branding information to use for this document. If a string, the path\nto a brand file. If false, don’t use branding on this document. If an\nobject, an inline brand definition, or an object with light and dark\nbrand paths or definitions.",
+ "The path to a light brand file or an inline light brand\ndefinition.",
+ "The path to a dark brand file or an inline dark brand definition.",
{
"short": "Unique label for code cell",
"long": "Unique label for code cell. Used when other code needs to refer to\nthe cell (e.g. for cross references fig-samples or\ntbl-summary)"
},
"Classes to apply to cell container",
+ "Array of rendering names",
"Array of tags for notebook cell",
{
"short": "Notebook cell identifier",
@@ -15851,7 +15885,9 @@
},
"If true, force the presence of the OJS runtime. If\nfalse, force the absence instead. If unset, the OJS runtime\nis included only if OJS cells are present in the document.",
"Use the specified file as a style reference in producing a docx,\npptx, or odt file.",
- "Branding information to use for this document. If a string, the path\nto a brand file. If false, don’t use branding on this document. If an\nobject, an inline brand definition.",
+ "Branding information to use for this document. If a string, the path\nto a brand file. If false, don’t use branding on this document. If an\nobject, an inline brand definition, or an object with light and dark\nbrand paths or definitions.",
+ "The path to a light brand file or an inline light brand\ndefinition.",
+ "The path to a dark brand file or an inline dark brand definition.",
"Theme name, theme scss file, or a mix of both.",
"The light theme name, theme scss file, or a mix of both.",
"The light theme name, theme scss file, or a mix of both.",
@@ -16602,6 +16638,7 @@
"Disambiguating year suffix in author-date styles (e.g. “a” in “Doe,\n1999a”).",
"Manuscript configuration",
"internal-schema-hack",
+ "List execution engines you want to give priority when determining\nwhich engine should render a notebook. If two engines have support for a\nnotebook, the one listed earlier will be chosen. Quarto’s default order\nis ‘knitr’, ‘jupyter’, ‘markdown’, ‘julia’.",
{
"short": "Include an automatically generated table of contents",
"long": ""
@@ -16954,7 +16991,6 @@
"Disambiguating year suffix in author-date styles (e.g. “a” in “Doe,\n1999a”).",
"Manuscript configuration",
"internal-schema-hack",
- "Array of rendering names",
"List execution engines you want to give priority when determining\nwhich engine should render a notebook. If two engines have support for a\nnotebook, the one listed earlier will be chosen. Quarto’s default order\nis ‘knitr’, ‘jupyter’, ‘markdown’, ‘julia’."
],
"schema/external-schemas.yml": [
@@ -17184,12 +17220,12 @@
"mermaid": "%%"
},
"handlers/mermaid/schema.yml": {
- "_internalId": 194327,
+ "_internalId": 196120,
"type": "object",
"description": "be an object",
"properties": {
"mermaid-format": {
- "_internalId": 194319,
+ "_internalId": 196112,
"type": "enum",
"enum": [
"png",
@@ -17205,7 +17241,7 @@
"exhaustiveCompletions": true
},
"theme": {
- "_internalId": 194326,
+ "_internalId": 196119,
"type": "anyOf",
"anyOf": [
{
diff --git a/src/resources/formats/html/esbuild-analysis-cache.json b/src/resources/formats/html/esbuild-analysis-cache.json
index 110a06d005f..16f8062bbc3 100644
--- a/src/resources/formats/html/esbuild-analysis-cache.json
+++ b/src/resources/formats/html/esbuild-analysis-cache.json
@@ -2,7 +2,7 @@
"quarto.js": {
"inputs": {
"quarto.js": {
- "bytes": 26396,
+ "bytes": 26830,
"imports": [],
"format": "esm"
}
@@ -20,10 +20,10 @@
"entryPoint": "quarto.js",
"inputs": {
"quarto.js": {
- "bytesInOutput": 21946
+ "bytesInOutput": 22313
}
},
- "bytes": 21946
+ "bytes": 22313
}
}
}
diff --git a/src/resources/schema/definitions.yml b/src/resources/schema/definitions.yml
index 69615106c34..6fe40176b5c 100644
--- a/src/resources/schema/definitions.yml
+++ b/src/resources/schema/definitions.yml
@@ -2980,6 +2980,31 @@
defaults:
ref: brand-defaults
+- id: brand-path-bool-light-dark
+ anyOf:
+ - string # a file path
+ - boolean # if false, don't use branding on this document
+ - object:
+ closed: true
+ properties:
+ light:
+ anyOf:
+ - string
+ - ref: brand
+ description: >
+ The path to a light brand file or an inline light brand definition.
+ dark:
+ anyOf:
+ - string
+ - ref: brand
+ description: >
+ The path to a dark brand file or an inline dark brand definition.
+ - ref: brand
+ description: |
+ Branding information to use for this document. If a string, the path to a brand file.
+ If false, don't use branding on this document. If an object, an inline brand
+ definition, or an object with light and dark brand paths or definitions.
+
- id: brand-defaults
object:
properties:
diff --git a/src/resources/schema/document-options.yml b/src/resources/schema/document-options.yml
index 1407ceb29e9..0b7be354fb5 100644
--- a/src/resources/schema/document-options.yml
+++ b/src/resources/schema/document-options.yml
@@ -8,14 +8,11 @@
- name: brand
schema:
- anyOf:
- - string # a file path
- - boolean # if false, don't use branding on this document
- - ref: brand # an inline brand object definition
+ ref: brand-path-bool-light-dark
description: |
Branding information to use for this document. If a string, the path to a brand file.
If false, don't use branding on this document. If an object, an inline brand
- definition.
+ definition, or an object with light and dark brand paths or definitions.
- name: theme
tags:
diff --git a/src/resources/schema/json-schemas.json b/src/resources/schema/json-schemas.json
index 1898e0c768b..06040e4dcaf 100644
--- a/src/resources/schema/json-schemas.json
+++ b/src/resources/schema/json-schemas.json
@@ -3776,6 +3776,45 @@
}
}
},
+ "BrandPathBoolLightDark": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "boolean"
+ },
+ {
+ "object": {
+ "properties": {
+ "light": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "$ref": "#/$defs/Brand"
+ }
+ ]
+ },
+ "dark": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "$ref": "#/$defs/Brand"
+ }
+ ]
+ }
+ }
+ }
+ },
+ {
+ "$ref": "#/$defs/Brand"
+ }
+ ]
+ },
"BrandDefaults": {
"object": {
"properties": {
diff --git a/src/resources/types/schema-types.ts b/src/resources/types/schema-types.ts
index c755cbb615d..1d95382ed38 100644
--- a/src/resources/types/schema-types.ts
+++ b/src/resources/types/schema-types.ts
@@ -1483,6 +1483,21 @@ export type Brand = {
typography?: BrandTypography;
};
+export type BrandPathBoolLightDark =
+ | string
+ | boolean
+ | {
+ dark?:
+ | string
+ | Brand /* The path to a dark brand file or an inline dark brand definition. */;
+ light?:
+ | string
+ | Brand; /* The path to a light brand file or an inline light brand definition. */
+ }
+ | Brand; /* Branding information to use for this document. If a string, the path to a brand file.
+If false, don't use branding on this document. If an object, an inline brand
+definition, or an object with light and dark brand paths or definitions. */
+
export type BrandDefaults = {
bootstrap?: BrandDefaultsBootstrap;
quarto?: JsonObject;
diff --git a/tests/docs/smoke-all/dark-mode/ggplot-brandless.qmd b/tests/docs/smoke-all/dark-mode/ggplot-brandless.qmd
index 9ba0fcab178..0437a6dc3fa 100644
--- a/tests/docs/smoke-all/dark-mode/ggplot-brandless.qmd
+++ b/tests/docs/smoke-all/dark-mode/ggplot-brandless.qmd
@@ -1,5 +1,5 @@
---
-title: "knitr dark mode - thematic"
+title: "knitr dark mode - ggplot"
format:
html:
theme:
diff --git a/tests/docs/smoke-all/dark-mode/ggplot-duobrand.qmd b/tests/docs/smoke-all/dark-mode/ggplot-duobrand.qmd
new file mode 100644
index 00000000000..2ba8106d939
--- /dev/null
+++ b/tests/docs/smoke-all/dark-mode/ggplot-duobrand.qmd
@@ -0,0 +1,113 @@
+---
+title: "knitr dark mode - ggplot"
+brand:
+ light: united-brand.yml
+ dark: slate-brand.yml
+execute:
+ echo: false
+ warning: false
+_quarto:
+ tests:
+ html:
+ ensureHtmlElements:
+ -
+ - 'body.quarto-light'
+ - 'div.cell div.light-content'
+ - 'div.cell div.dark-content'
+ - 'div.cell div.cell-code pre.code-with-copy'
+ - []
+---
+
+```{r}
+#| echo: false
+#| warning: false
+library(ggplot2)
+
+ggplot_theme <- function(bgcolor, fgcolor) {
+ theme_minimal(base_size = 11) %+%
+ theme(
+ panel.border = element_blank(),
+ panel.grid.major.y = element_blank(),
+ panel.grid.minor.y = element_blank(),
+ panel.grid.major.x = element_blank(),
+ panel.grid.minor.x = element_blank(),
+ text = element_text(colour = fgcolor),
+ axis.text = element_text(colour = fgcolor),
+ rect = element_rect(colour = bgcolor, fill = bgcolor),
+ plot.background = element_rect(fill = bgcolor, colour = NA),
+ axis.line = element_line(colour = fgcolor),
+ axis.ticks = element_line(colour = fgcolor)
+ )
+}
+
+brand_ggplot <- function(brand_yml) {
+ brand <- yaml::yaml.load_file(brand_yml)
+ ggplot_theme(brand$color$background, brand$color$foreground)
+}
+
+united_theme <- brand_ggplot("united-brand.yml")
+slate_theme <- brand_ggplot("slate-brand.yml")
+
+colour_scale <- scale_colour_manual(values = c("darkorange", "purple", "cyan4"))
+```
+
+### no crossref, no caption
+
+```{r}
+#| renderings: [light, dark]
+ggplot(mtcars, aes(mpg, wt)) +
+ geom_point(aes(colour = factor(cyl))) + united_theme + colour_scale
+ggplot(mtcars, aes(mpg, wt)) +
+ geom_point(aes(colour = factor(cyl))) + slate_theme + colour_scale
+```
+
+### with crossref but no caption
+
+::: {#fig-thematic-ggplot}
+```{r}
+#| echo: true
+#| renderings:
+#| - dark
+#| - light
+ggplot(mtcars, aes(mpg, disp)) +
+ geom_point(aes(colour = factor(cyl))) + slate_theme + colour_scale
+ggplot(mtcars, aes(mpg, disp)) +
+ geom_point(aes(colour = factor(cyl))) + united_theme + colour_scale
+```
+:::
+
+### with caption but no crossref
+
+dark rendering only
+
+
+
+```{r}
+#| renderings: [dark]
+ggplot(mtcars, aes(mpg, disp)) +
+ geom_point(aes(colour = factor(cyl))) + slate_theme + colour_scale
+```
+
+thematic - base r graphics
+
+
+
+## patchwork
+
+### with crossref and caption
+
+::: {#fig-thematic-patchwork}
+```{r}
+#| renderings: [light, dark]
+ggplot(mtcars, aes(mpg, hp)) +
+ geom_point(aes(colour = factor(cyl))) + united_theme + colour_scale
+ggplot(mtcars, aes(mpg, hp)) +
+ geom_point(aes(colour = factor(cyl))) + slate_theme + colour_scale
+```
+
+mtcars - mpg vs hp
+:::
+
+Here's a [link](https://example.com).
+
+{{< lipsum 3 >}}
\ No newline at end of file
diff --git a/tests/docs/smoke-all/dark-mode/matplotlib-duobrand-defaultdark.qmd b/tests/docs/smoke-all/dark-mode/matplotlib-duobrand-defaultdark.qmd
new file mode 100644
index 00000000000..a5f1e43daad
--- /dev/null
+++ b/tests/docs/smoke-all/dark-mode/matplotlib-duobrand-defaultdark.qmd
@@ -0,0 +1,201 @@
+---
+title: "jupyter dark mode - matplotlib"
+engine: jupyter
+brand:
+ dark: slate-brand.yml
+ light: united-brand.yml
+keep-md: true
+_quarto:
+ tests:
+ html:
+ ensureHtmlElements:
+ -
+ - 'body.quarto-dark'
+ - 'div.cell div.light-content'
+ - 'div.cell div.dark-content'
+ - 'div.cell div.cell-code pre.code-with-copy'
+ - []
+---
+
+```{python}
+#| echo: false
+import yaml
+import tempfile
+import os
+
+def apply_mpl_colors(bgcolor, fgcolor, primarycolor):
+ fd, name = tempfile.mkstemp("mplstyle")
+ os.close(fd)
+ with open(name, "w") as out:
+ out.write("axes.facecolor: \"%s\"\n" % bgcolor)
+ out.write("axes.edgecolor: \"%s\"\n" % fgcolor)
+ out.write("axes.labelcolor: \"%s\"\n" % fgcolor)
+ out.write("axes.titlecolor: \"%s\"\n" % fgcolor)
+ out.write("figure.facecolor: \"%s\"\n" % bgcolor)
+ out.write("figure.edgecolor: \"%s\"\n" % fgcolor)
+ out.write("text.color: \"%s\"\n" % fgcolor)
+ out.write("xtick.color: \"%s\"\n" % fgcolor)
+ out.write("ytick.color: \"%s\"\n" % fgcolor)
+ # seems to require named color, is there a better way?
+ out.write("axes.prop_cycle: cycler('color', ['%s'])" % primarycolor)
+ plt.style.use(name)
+ os.unlink(name)
+
+def apply_brand_colors(filename):
+ with open(filename) as brand_file:
+ brand = yaml.safe_load(brand_file.read())
+ def apply():
+ apply_mpl_colors(brand["color"]["background"], brand["color"]["foreground"], brand["color"]["primary"])
+ return apply
+
+united_colors = apply_brand_colors("united-brand.yml")
+slate_colors = apply_brand_colors("slate-brand.yml")
+```
+
+### No crossref or caption
+```{python}
+#| echo: false
+#| renderings: [light, dark]
+import numpy as np
+import matplotlib.pyplot as plt
+
+# Parameters for the normal distribution
+mean = 0
+std_dev = 1
+
+# Generate data
+x = np.linspace(mean - 4*std_dev, mean + 4*std_dev, 1000)
+y = (1/(std_dev * np.sqrt(2 * np.pi))) * np.exp(-0.5 * ((x - mean) / std_dev)**2)
+
+# Plotting
+united_colors()
+plt.figure(figsize=(8, 5))
+plt.plot(x, y, label='Normal Distribution')
+plt.title('Normal Distribution Curve')
+plt.xlabel('X-axis')
+plt.ylabel('Probability Density')
+plt.legend()
+plt.grid(True)
+plt.show()
+
+slate_colors()
+plt.figure(figsize=(8, 5))
+plt.plot(x, y, label='Normal Distribution')
+plt.title('Normal Distribution Curve')
+plt.xlabel('X-axis')
+plt.ylabel('Probability Density')
+plt.legend()
+plt.grid(True)
+plt.show()
+```
+
+### With crossref but no caption
+
+And `echo: true`
+
+::: {#fig-matplotlib-line}
+```{python}
+#| echo: true
+#| renderings: [light, dark]
+import matplotlib.pyplot as plt
+
+united_colors()
+plt.title("Hello")
+plt.plot([1,2,3])
+plt.grid(True)
+plt.show(block=False)
+
+slate_colors()
+plt.figure()
+plt.title("Hello")
+plt.plot([1,2,3])
+plt.grid(True)
+plt.show(block=False)
+```
+:::
+
+### With caption but no crossref
+
+::: {}
+```{python}
+#| echo: false
+#| renderings: [light, dark]
+
+# author: "anthropic claude-3-5-sonnet-20240620"
+import numpy as np
+import matplotlib.pyplot as plt
+
+# Generate data points
+x = np.linspace(0, 2 * np.pi, 100)
+y = np.sin(x)
+
+united_colors()
+plt.figure(figsize=(10, 6))
+plt.plot(x, y)
+plt.title('Sine Wave')
+plt.xlabel('x')
+plt.ylabel('sin(x)')
+plt.grid(True)
+plt.axhline(y=0, color='k', linestyle='--')
+plt.axvline(x=0, color='k', linestyle='--')
+plt.show()
+
+slate_colors()
+plt.figure(figsize=(10, 6))
+plt.plot(x, y)
+plt.title('Sine Wave')
+plt.xlabel('x')
+plt.ylabel('sin(x)')
+plt.grid(True)
+plt.axhline(y=0, color='k', linestyle='--')
+plt.axvline(x=0, color='k', linestyle='--')
+plt.show()
+```
+matplotlib sine wave
+
+:::
+
+### With crossref and caption
+
+::: {#fig-matplotlib-cosine}
+```{python}
+#| echo: false
+#| renderings: [dark, light]
+import numpy as np
+import matplotlib.pyplot as plt
+
+# Generate data points
+x = np.linspace(0, 2 * np.pi, 100)
+y = np.cos(x)
+
+# Create the plot
+slate_colors()
+plt.figure(figsize=(10, 6))
+plt.plot(x, y)
+plt.title('Cosine Wave')
+plt.xlabel('x')
+plt.ylabel('cos(x)')
+plt.grid(True)
+plt.axhline(y=0, color='k', linestyle='--')
+plt.axvline(x=0, color='k', linestyle='--')
+plt.show()
+
+united_colors()
+plt.figure(figsize=(10, 6))
+plt.plot(x, y)
+plt.title('Cosine Wave')
+plt.xlabel('x')
+plt.ylabel('cos(x)')
+plt.grid(True)
+plt.axhline(y=0, color='k', linestyle='--')
+plt.axvline(x=0, color='k', linestyle='--')
+plt.show()
+```
+
+matplotlib cosine wave
+:::
+
+Here's a [link](https://example.com).
+
+
+{{< lipsum 3 >}}
\ No newline at end of file
diff --git a/tests/docs/smoke-all/dark-mode/matplotlib-duobrand.qmd b/tests/docs/smoke-all/dark-mode/matplotlib-duobrand.qmd
new file mode 100644
index 00000000000..b65998d28c1
--- /dev/null
+++ b/tests/docs/smoke-all/dark-mode/matplotlib-duobrand.qmd
@@ -0,0 +1,201 @@
+---
+title: "jupyter dark mode - matplotlib"
+engine: jupyter
+brand:
+ light: united-brand.yml
+ dark: slate-brand.yml
+keep-md: true
+_quarto:
+ tests:
+ html:
+ ensureHtmlElements:
+ -
+ - 'body.quarto-light'
+ - 'div.cell div.light-content'
+ - 'div.cell div.dark-content'
+ - 'div.cell div.cell-code pre.code-with-copy'
+ - []
+---
+
+```{python}
+#| echo: false
+import yaml
+import tempfile
+import os
+
+def apply_mpl_colors(bgcolor, fgcolor, primarycolor):
+ fd, name = tempfile.mkstemp("mplstyle")
+ os.close(fd)
+ with open(name, "w") as out:
+ out.write("axes.facecolor: \"%s\"\n" % bgcolor)
+ out.write("axes.edgecolor: \"%s\"\n" % fgcolor)
+ out.write("axes.labelcolor: \"%s\"\n" % fgcolor)
+ out.write("axes.titlecolor: \"%s\"\n" % fgcolor)
+ out.write("figure.facecolor: \"%s\"\n" % bgcolor)
+ out.write("figure.edgecolor: \"%s\"\n" % fgcolor)
+ out.write("text.color: \"%s\"\n" % fgcolor)
+ out.write("xtick.color: \"%s\"\n" % fgcolor)
+ out.write("ytick.color: \"%s\"\n" % fgcolor)
+ # seems to require named color, is there a better way?
+ out.write("axes.prop_cycle: cycler('color', ['%s'])" % primarycolor)
+ plt.style.use(name)
+ os.unlink(name)
+
+def apply_brand_colors(filename):
+ with open(filename) as brand_file:
+ brand = yaml.safe_load(brand_file.read())
+ def apply():
+ apply_mpl_colors(brand["color"]["background"], brand["color"]["foreground"], brand["color"]["primary"])
+ return apply
+
+united_colors = apply_brand_colors("united-brand.yml")
+slate_colors = apply_brand_colors("slate-brand.yml")
+```
+
+### No crossref or caption
+```{python}
+#| echo: false
+#| renderings: [light, dark]
+import numpy as np
+import matplotlib.pyplot as plt
+
+# Parameters for the normal distribution
+mean = 0
+std_dev = 1
+
+# Generate data
+x = np.linspace(mean - 4*std_dev, mean + 4*std_dev, 1000)
+y = (1/(std_dev * np.sqrt(2 * np.pi))) * np.exp(-0.5 * ((x - mean) / std_dev)**2)
+
+# Plotting
+united_colors()
+plt.figure(figsize=(8, 5))
+plt.plot(x, y, label='Normal Distribution')
+plt.title('Normal Distribution Curve')
+plt.xlabel('X-axis')
+plt.ylabel('Probability Density')
+plt.legend()
+plt.grid(True)
+plt.show()
+
+slate_colors()
+plt.figure(figsize=(8, 5))
+plt.plot(x, y, label='Normal Distribution')
+plt.title('Normal Distribution Curve')
+plt.xlabel('X-axis')
+plt.ylabel('Probability Density')
+plt.legend()
+plt.grid(True)
+plt.show()
+```
+
+### With crossref but no caption
+
+And `echo: true`
+
+::: {#fig-matplotlib-line}
+```{python}
+#| echo: true
+#| renderings: [light, dark]
+import matplotlib.pyplot as plt
+
+united_colors()
+plt.title("Hello")
+plt.plot([1,2,3])
+plt.grid(True)
+plt.show(block=False)
+
+slate_colors()
+plt.figure()
+plt.title("Hello")
+plt.plot([1,2,3])
+plt.grid(True)
+plt.show(block=False)
+```
+:::
+
+### With caption but no crossref
+
+::: {}
+```{python}
+#| echo: false
+#| renderings: [light, dark]
+
+# author: "anthropic claude-3-5-sonnet-20240620"
+import numpy as np
+import matplotlib.pyplot as plt
+
+# Generate data points
+x = np.linspace(0, 2 * np.pi, 100)
+y = np.sin(x)
+
+united_colors()
+plt.figure(figsize=(10, 6))
+plt.plot(x, y)
+plt.title('Sine Wave')
+plt.xlabel('x')
+plt.ylabel('sin(x)')
+plt.grid(True)
+plt.axhline(y=0, color='k', linestyle='--')
+plt.axvline(x=0, color='k', linestyle='--')
+plt.show()
+
+slate_colors()
+plt.figure(figsize=(10, 6))
+plt.plot(x, y)
+plt.title('Sine Wave')
+plt.xlabel('x')
+plt.ylabel('sin(x)')
+plt.grid(True)
+plt.axhline(y=0, color='k', linestyle='--')
+plt.axvline(x=0, color='k', linestyle='--')
+plt.show()
+```
+matplotlib sine wave
+
+:::
+
+### With crossref and caption
+
+::: {#fig-matplotlib-cosine}
+```{python}
+#| echo: false
+#| renderings: [dark, light]
+import numpy as np
+import matplotlib.pyplot as plt
+
+# Generate data points
+x = np.linspace(0, 2 * np.pi, 100)
+y = np.cos(x)
+
+# Create the plot
+slate_colors()
+plt.figure(figsize=(10, 6))
+plt.plot(x, y)
+plt.title('Cosine Wave')
+plt.xlabel('x')
+plt.ylabel('cos(x)')
+plt.grid(True)
+plt.axhline(y=0, color='k', linestyle='--')
+plt.axvline(x=0, color='k', linestyle='--')
+plt.show()
+
+united_colors()
+plt.figure(figsize=(10, 6))
+plt.plot(x, y)
+plt.title('Cosine Wave')
+plt.xlabel('x')
+plt.ylabel('cos(x)')
+plt.grid(True)
+plt.axhline(y=0, color='k', linestyle='--')
+plt.axvline(x=0, color='k', linestyle='--')
+plt.show()
+```
+
+matplotlib cosine wave
+:::
+
+Here's a [link](https://example.com).
+
+
+{{< lipsum 3 >}}
\ No newline at end of file
diff --git a/tests/docs/smoke-all/dark-mode/slate-brand.yml b/tests/docs/smoke-all/dark-mode/slate-brand.yml
new file mode 100644
index 00000000000..8b640748b2e
--- /dev/null
+++ b/tests/docs/smoke-all/dark-mode/slate-brand.yml
@@ -0,0 +1,12 @@
+color:
+ background: "#282B30"
+ foreground: "#aaaaaa"
+ primary: white
+typography:
+ fonts:
+ - family: "Montserrat"
+ source: google
+ base:
+ family: "Montserrat"
+ monospace-block:
+ background-color: "#435"
diff --git a/tests/docs/smoke-all/dark-mode/united-brand.yml b/tests/docs/smoke-all/dark-mode/united-brand.yml
new file mode 100644
index 00000000000..1f1a4c50144
--- /dev/null
+++ b/tests/docs/smoke-all/dark-mode/united-brand.yml
@@ -0,0 +1,10 @@
+color:
+ background: "#ffffff"
+ foreground: "#333333"
+ primary: red
+typography:
+ fonts:
+ - family: "Montserrat"
+ source: google
+ base:
+ family: "Montserrat"