diff --git a/.stylelintrc b/.stylelintrc index 0354ed7d9a..f54d9febc2 100644 --- a/.stylelintrc +++ b/.stylelintrc @@ -13,6 +13,12 @@ } ], "order/properties-alphabetical-order": true, + "value-keyword-case": [ + "lower", + { + "ignoreProperties": ["/^--/"] + } + ], "selector-class-pattern": [ "^([a-z][a-z0-9]*)(-(-*)[a-z0-9]+)*$", { @@ -27,6 +33,12 @@ } ], "scss/at-extend-no-missing-placeholder": null, + "scss/at-rule-no-unknown": [ + true, + { + "ignoreAtRules": ["source", "theme", "custom-variant", "utility", "variant"] + } + ], "scss/at-if-no-null": [ true, { diff --git a/README.md b/README.md index 1986044b0b..a0947e5214 100644 --- a/README.md +++ b/README.md @@ -61,11 +61,14 @@ retired (GRO-828) and then pruned to what the rendered docs pages actually use | ---------------- | ------------------------------------------------------------------------------------------- | | `styles.css` | Minified Bootstrap 5 subset (scoped under `.bootstrap`) plus the header, footer, `.learn` docs layout, breadcrumb, in-page promo and code-copy styles ported from the marketing bundle. | | `main.css` | Legacy global styles: typography, links, lists, tables, `.Button`, the old (pre-v0.44) docs layout, image zoom and the feedback widget. Linted by stylelint. | -| `docs.css` | Docs-only overrides for the old docs layout (`.MB-Documentation`, `.container-docs`). | -| `docs-local.css` | Styles that only ever lived in this repo (version selector tags, unsupported-version notice). Loaded last so it can override the files above. | +| `docs.css` | Docs-only overrides for the old docs layout (`.MB-Documentation`, `.container-docs`) and the unsupported-version notice. | | `gdpr.css` | Metabase theme for the vendored GDPR cookie notice (`public/docs/gdpr-cookie-notice`). | | `inkeep.css` | Theme for the Inkeep search/chat widget. | +Themed docs pages don't link these files: `src/styles/docs.css` imports +`docs.css`, `styles.css`, `main.css` and `gdpr.css` into its `legacy` cascade +layer. A prune must keep the rules those pages use too. + Sections that remain large on purpose: - The Bootstrap reboot, grid (`.row`, `.col-*` used by the header) and spacing, diff --git a/_includes/beta-blockquote.html b/_includes/beta-blockquote.html index b479aa5ea8..12bb4e7e0d 100644 --- a/_includes/beta-blockquote.html +++ b/_includes/beta-blockquote.html @@ -1,5 +1,9 @@ +{%- comment -%} + The `.md` version of a doc renders markdown/beta-blockquote.html instead. + Keep the wording in sync. +{%- endcomment -%}
-
+
⚠️ This feature is in beta. Feel free to play around with it, but be aware that things might change (and may not work as expected). {% if include.message %} diff --git a/_includes/chrome/stylesheets.html b/_includes/chrome/stylesheets.html index 00189099bf..d360a5c723 100644 --- a/_includes/chrome/stylesheets.html +++ b/_includes/chrome/stylesheets.html @@ -1,6 +1,19 @@ +{% comment %} +Themed docs pages use only Lato 400/700 and Roboto Mono; src/styles/docs.css +drops the Lato 900 uses in styles.css. Other pages keep the marketing set. +{% endcomment %} +{% if include.docs_theme %} + +{% else %} +{% endif %} +{% comment %} +Themed docs pages get these five through src/styles/docs.css (Tailwind), which +puts them in a cascade layer below the utilities. +{% endcomment %} +{% unless include.docs_theme %} {% unless page.layout == "docs-api" %} @@ -8,7 +21,10 @@ {% endunless %} +{% endunless %} - +{% comment %}Themed docs pages get their syntax colors from src/styles/docs.css.{% endcomment %} +{% unless include.docs_theme %} +{% endunless %} diff --git a/_includes/docs-metadata.html b/_includes/docs-metadata.html index ee62404d9a..eadc331727 100644 --- a/_includes/docs-metadata.html +++ b/_includes/docs-metadata.html @@ -1,12 +1,28 @@ -{% assign title = page.title | append: " | Metabase Documentation"%} +{% comment %} +Versioned pages name their version, so tabs and search results for different +versions of a page can be told apart. +{% endcomment %} +{% if page.latest != true and page.version %} +{% assign version_label = page.version | remove_first: "v0." %} +{% assign title = page.title | append: " | Metabase " | append: version_label | append: " Documentation" %} +{% else %} +{% assign title = page.title | append: " | Metabase Documentation" %} +{% endif %} +{% assign title = title | escape_once %} -{% if page.description %} -{% assign desc = page.description %} +{% comment %} +meta_description is plain text from the doc page routes +(src/lib/docs/docPages.ts): the description or summary, else the body's +opening words. +{% endcomment %} +{% if page.meta_description %} +{% assign desc = page.meta_description %} +{% elsif page.description %} +{% assign desc = page.description | strip_html %} {% elsif page.summary %} -{% assign desc = page.summary %} -{% else %} -{% assign desc = page.content | strip_html | truncatewords: 25 | escape_once %} +{% assign desc = page.summary | strip_html %} {% endif %} +{% assign desc = desc | escape_once %} {% assign img = "https://cdn.metabase.com/images/docs/docs-og.png" %} diff --git a/_includes/docs/theme-init.js b/_includes/docs/theme-init.js new file mode 100644 index 0000000000..4ede8dbd01 --- /dev/null +++ b/_includes/docs/theme-init.js @@ -0,0 +1,45 @@ +// Inlined into by head.html before any stylesheet loads, so the theme +// attribute is present at first paint (no flash of the wrong theme). +// data-theme-preference holds the visitor's choice from the theme options in +// DocsMoreMenu.astro: system (the default, nothing stored), light or dark. +// data-theme holds the theme in use; on system it follows the system theme, +// live. Rendered through Liquid, so keep it free of Liquid tag syntax. +(() => { + const KEY = "mb-docs-theme"; + const root = document.documentElement; + const systemDark = matchMedia("(prefers-color-scheme: dark)"); + + const stored = () => { + try { + const theme = localStorage.getItem(KEY); + return theme === "light" || theme === "dark" ? theme : null; + } catch { + return null; /* storage blocked (private mode, disabled cookies) */ + } + }; + const apply = () => { + const preference = root.dataset.themePreference; + root.dataset.theme = + preference === "light" || preference === "dark" + ? preference + : systemDark.matches + ? "dark" + : "light"; + }; + + // For the theme options in DocsMoreMenu.astro. + window.setDocsTheme = (preference) => { + root.dataset.themePreference = preference; + apply(); + try { + if (preference === "system") localStorage.removeItem(KEY); + else localStorage.setItem(KEY, preference); + } catch { + /* storage blocked; the choice lasts for this page only */ + } + }; + + root.dataset.themePreference = stored() ?? "system"; + apply(); + systemDark.addEventListener("change", apply); +})(); diff --git a/_includes/head.html b/_includes/head.html index 7add0e78c4..24719ea4b4 100644 --- a/_includes/head.html +++ b/_includes/head.html @@ -1,7 +1,7 @@ - + @@ -32,6 +32,9 @@ {% include docs-metadata.html %} {% include schema-markup.html %} + {% if include.json_ld %} + + {% endif %} {% if jekyll.environment == "development" %} @@ -43,13 +46,12 @@ - {% include chrome/stylesheets.html %} - - {% comment %} - Loaded after the local chrome so it can override those stylesheets. Must stay - outside the canonical-URL block above, which skips versioned docs pages. - {% endcomment %} - + {% if include.docs_theme %} + + {% endif %} + {% include chrome/stylesheets.html docs_theme=include.docs_theme %} {% if page.url contains "/data-stack-report-2023/" %} diff --git a/_includes/markdown/beta-blockquote.html b/_includes/markdown/beta-blockquote.html new file mode 100644 index 0000000000..96f431491d --- /dev/null +++ b/_includes/markdown/beta-blockquote.html @@ -0,0 +1,8 @@ +{%- comment -%} + Markdown stand-in for ../beta-blockquote.html. +{%- endcomment -%} +> ⚠️ This feature is in beta. Feel free to play around with it, but be aware that things might change (and may not work as expected). +{%- if include.message %} +> +> {{ include.message }} +{%- endif -%} diff --git a/_includes/markdown/plans-blockquote.html b/_includes/markdown/plans-blockquote.html new file mode 100644 index 0000000000..9436d63163 --- /dev/null +++ b/_includes/markdown/plans-blockquote.html @@ -0,0 +1,22 @@ +{%- comment -%} + Markdown stand-in for ../plans-blockquote.html, used by the `.md` version of + a doc (see MARKDOWN_INCLUDES_ROOT in src/lib/liquid/liquidRenderer.ts). +{%- endcomment -%} +{%- if include.convert_pro_link_to_embedding -%} + {%- assign pro_link = "https://store.metabase.com/checkout/embedding" -%} +{%- else -%} + {%- assign pro_link = "/product/pro" -%} +{%- endif -%} +{%- assign feature_text = include.feature | default: "This feature" -%} +{%- assign verb = "is" -%} +{%- if include.is_plural -%} + {%- assign verb = "are" -%} +{%- endif -%} +> {{ feature_text }} {{ verb }} only available on +{%- if include.enterprise-only %} [Enterprise](/product/enterprise) plans +{%- else %} [Pro]({{ pro_link }}) and [Enterprise](/product/enterprise) plans +{%- endif -%} +{%- if include.self-hosted-only %} (only on self-hosted plans) +{%- else %} (both self-hosted and on Metabase Cloud) +{%- endif -%} +. diff --git a/_includes/markdown/shared/in-page-promo-embedding-workshop.html b/_includes/markdown/shared/in-page-promo-embedding-workshop.html new file mode 100644 index 0000000000..2ad0d951de --- /dev/null +++ b/_includes/markdown/shared/in-page-promo-embedding-workshop.html @@ -0,0 +1,3 @@ +{%- comment -%} + Promo banners are left out of the Markdown version of a doc. +{%- endcomment -%} diff --git a/_includes/markdown/shared/in-page-promo.html b/_includes/markdown/shared/in-page-promo.html new file mode 100644 index 0000000000..2ad0d951de --- /dev/null +++ b/_includes/markdown/shared/in-page-promo.html @@ -0,0 +1,3 @@ +{%- comment -%} + Promo banners are left out of the Markdown version of a doc. +{%- endcomment -%} diff --git a/_includes/markdown/svg-icons/cross.svg b/_includes/markdown/svg-icons/cross.svg new file mode 100644 index 0000000000..f97cdf1da9 --- /dev/null +++ b/_includes/markdown/svg-icons/cross.svg @@ -0,0 +1,4 @@ +{%- comment -%} + Markdown stand-in for ../../svg-icons/cross.svg. +{%- endcomment -%} +❌ diff --git a/_includes/markdown/youtube.html b/_includes/markdown/youtube.html new file mode 100644 index 0000000000..1e2bdc4bca --- /dev/null +++ b/_includes/markdown/youtube.html @@ -0,0 +1,5 @@ +{%- comment -%} + Markdown stand-in for ../youtube.html: a link, since an iframe means nothing + outside a browser. +{%- endcomment -%} +[Watch the video on YouTube](https://www.youtube.com/watch?v={{ include.id }}) diff --git a/_includes/plans-blockquote.html b/_includes/plans-blockquote.html index feb0ef127c..ea1b5154cf 100644 --- a/_includes/plans-blockquote.html +++ b/_includes/plans-blockquote.html @@ -1,3 +1,7 @@ +{%- comment -%} + The `.md` version of a doc renders markdown/plans-blockquote.html instead. + Keep the wording in sync. +{%- endcomment -%} {% if include.convert_pro_link_to_embedding %} {% assign pro_link = "https://store.metabase.com/checkout/embedding" %} {% else %} diff --git a/_includes/youtube.html b/_includes/youtube.html index e37f5efc40..96b3a795d3 100644 --- a/_includes/youtube.html +++ b/_includes/youtube.html @@ -1,3 +1,6 @@ +{%- comment -%} + The `.md` version of a doc renders markdown/youtube.html instead. +{%- endcomment -%} {% if include.width != '' %}
{% else %} diff --git a/amplify.yml b/amplify.yml deleted file mode 100644 index 8772d50be6..0000000000 --- a/amplify.yml +++ /dev/null @@ -1,19 +0,0 @@ -version: 1 -frontend: - phases: - preBuild: - commands: - - sudo yum install -y rsync - - curl -fsSL https://bun.sh/install | bash - - export PATH="$HOME/.bun/bin:$PATH" - - bun ci - build: - commands: - - export NODE_ENV=$([ "$AWS_BRANCH" = "master" ] && echo "production" || echo "staging") && bun script/seed-docs-history.ts all && bun run build - artifacts: - baseDirectory: _site - files: - - "**/*" - cache: - paths: - - vendor/bundle/**/* diff --git a/astro.config.mjs b/astro.config.mjs index b4f0d08938..f280902ad8 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -1,5 +1,6 @@ // @ts-check import path from "node:path"; +import tailwindcss from "@tailwindcss/vite"; import { defineConfig } from "astro/config"; import { viteStaticCopy } from "vite-plugin-static-copy"; import { DOCS_DEST, DOCS_SRC_ROOT } from "./src/constants"; @@ -35,6 +36,9 @@ export default defineConfig({ }, vite: { plugins: [ + // Themed docs pages (src/styles/docs.css). Legacy `layout: docs` pages + // keep linking the vendored stylesheets from _includes/chrome/stylesheets.html. + tailwindcss(), viteStaticCopy({ targets: [ { diff --git a/bun.lock b/bun.lock index 7dbaf6ca75..6acb72ca6c 100644 --- a/bun.lock +++ b/bun.lock @@ -15,6 +15,7 @@ "@astrojs/check": "^0.9.9", "@ianvs/prettier-plugin-sort-imports": "^4.7.1", "@inkeep/cxkit-types": "0.5.117", + "@tailwindcss/vite": "^4.3.3", "@types/glob": "^5.0.10", "@types/yamljs": "^0.2.34", "@vercel/config": "^0.7.0", @@ -22,7 +23,6 @@ "astro": "^7.0.9", "bun-types": "^1.3.14", "eslint": "^8.29.0", - "happy-dom": "^20.10.6", "liquidjs": "^10.27.2", "markdownlint-cli": "^0.33.0", "prettier": "^3.9.5", @@ -30,6 +30,7 @@ "stylelint": "^14.9.0", "stylelint-config-standard-scss": "^4.0.0", "stylelint-order": "^5.0.0", + "tailwindcss": "^4.3.3", "typescript": "^6.0.3", "vite-plugin-static-copy": "^4.1.1", "vitest": "^4.1.10", @@ -287,6 +288,8 @@ "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], @@ -429,6 +432,36 @@ "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "@tailwindcss/node": ["@tailwindcss/node@4.3.3", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.24.1", "jiti": "^2.7.0", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.3.3" } }, "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg=="], + + "@tailwindcss/oxide": ["@tailwindcss/oxide@4.3.3", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.3.3", "@tailwindcss/oxide-darwin-arm64": "4.3.3", "@tailwindcss/oxide-darwin-x64": "4.3.3", "@tailwindcss/oxide-freebsd-x64": "4.3.3", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", "@tailwindcss/oxide-linux-x64-musl": "4.3.3", "@tailwindcss/oxide-wasm32-wasi": "4.3.3", "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" } }, "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA=="], + + "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.3.3", "", { "os": "android", "cpu": "arm64" }, "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw=="], + + "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.3.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw=="], + + "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.3.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw=="], + + "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.3.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw=="], + + "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3", "", { "os": "linux", "cpu": "arm" }, "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ=="], + + "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.3.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w=="], + + "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.3.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA=="], + + "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.3.3", "", { "os": "linux", "cpu": "x64" }, "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w=="], + + "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.3.3", "", { "os": "linux", "cpu": "x64" }, "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img=="], + + "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.3.3", "", { "dependencies": { "@emnapi/core": "^1.11.1", "@emnapi/runtime": "^1.11.1", "@emnapi/wasi-threads": "^1.2.2", "@napi-rs/wasm-runtime": "^1.1.4", "@tybys/wasm-util": "^0.10.2", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ=="], + + "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.3.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ=="], + + "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.3.3", "", { "os": "win32", "cpu": "x64" }, "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw=="], + + "@tailwindcss/vite": ["@tailwindcss/vite@4.3.3", "", { "dependencies": { "@tailwindcss/node": "4.3.3", "@tailwindcss/oxide": "4.3.3", "tailwindcss": "4.3.3" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw=="], + "@tanem/svg-injector": ["@tanem/svg-injector@10.1.68", "", { "dependencies": { "@babel/runtime": "^7.23.2", "content-type": "^1.0.5", "tslib": "^2.6.2" } }, "sha512-UkJajeR44u73ujtr5GVSbIlELDWD/mzjqWe54YMK61ljKxFcJoPd9RBSaO7xj02ISCWUqJW99GjrS+sVF0UnrA=="], "@tybys/wasm-util": ["@tybys/wasm-util@0.10.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg=="], @@ -737,6 +770,8 @@ "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + "enhanced-resolve": ["enhanced-resolve@5.26.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-9vhedylFonb2YGogzUKX6+Ja72gOJbN1QHAqdrvqLwhdl/QWbKopzoUC9EbQNVsAns/bx/4uyqalrQAoy1IByw=="], + "entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], "error-ex": ["error-ex@1.3.2", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g=="], @@ -869,6 +904,8 @@ "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="], "gray-matter": ["gray-matter@4.0.3", "", { "dependencies": { "js-yaml": "^3.13.1", "kind-of": "^6.0.2", "section-matter": "^1.0.0", "strip-bom-string": "^1.0.0" } }, "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q=="], @@ -977,6 +1014,8 @@ "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="], + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], "js-yaml": ["js-yaml@4.3.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q=="], @@ -1483,6 +1522,10 @@ "tailwind-merge": ["tailwind-merge@2.6.0", "", {}, "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA=="], + "tailwindcss": ["tailwindcss@4.3.3", "", {}, "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ=="], + + "tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="], + "text-table": ["text-table@0.2.0", "", {}, "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="], "timestring": ["timestring@6.0.0", "", {}, "sha512-wMctrWD2HZZLuIlchlkE2dfXJh7J2KDI9Dwl+2abPYg0mswQHfOAyQW3jJg1pY5VfttSINZuKcXoB3FGypVklA=="], @@ -1763,6 +1806,18 @@ "@radix-ui/react-visually-hidden/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.1", "", { "dependencies": { "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="], + + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.2.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" }, "bundled": true }, "sha512-AJxoUD2/15ESHbvpcyjU274nsAPLuOtPHCk0vKJM5pj//Fg/B1FXNWjPnXTT9PymCYYiHo4zPj0ZomXBKhoy7g=="], + + "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg=="], + + "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "@types/minimatch/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], "@types/nlcst/@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], diff --git a/package.json b/package.json index 08df029fdf..bff09624fc 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "@astrojs/check": "^0.9.9", "@ianvs/prettier-plugin-sort-imports": "^4.7.1", "@inkeep/cxkit-types": "0.5.117", + "@tailwindcss/vite": "^4.3.3", "@types/glob": "^5.0.10", "@types/yamljs": "^0.2.34", "@vercel/config": "^0.7.0", @@ -49,7 +50,6 @@ "astro": "^7.0.9", "bun-types": "^1.3.14", "eslint": "^8.29.0", - "happy-dom": "^20.10.6", "liquidjs": "^10.27.2", "markdownlint-cli": "^0.33.0", "prettier": "^3.9.5", @@ -57,6 +57,7 @@ "stylelint": "^14.9.0", "stylelint-config-standard-scss": "^4.0.0", "stylelint-order": "^5.0.0", + "tailwindcss": "^4.3.3", "typescript": "^6.0.3", "vite-plugin-static-copy": "^4.1.1", "vitest": "^4.1.10" diff --git a/public/docs/css/docs-local.css b/public/docs/css/docs-local.css deleted file mode 100644 index 30e0b5f87f..0000000000 --- a/public/docs/css/docs-local.css +++ /dev/null @@ -1,60 +0,0 @@ -/* because stylesheets mainly live in the marketing repo, this is a subset of the styles - * that are specific to this repo, pending a larger refactor to move all docs-specific styles into this repo. */ - -.version__tag { - border-radius: 4px; - font-size: 10px; - font-weight: 700; - letter-spacing: 0.02em; - line-height: 1; - padding: 3px 5px; - text-transform: uppercase; -} - -.version__tag--unsupported { - background-color: #f1f2f4; - color: #5a6072; -} - -.version__tag--lts { - background-color: #f3faec; - color: #669438; -} - -/* Notice on docs for versions past their EOL date. The `.docs` ancestor is - * present in both the current and legacy layouts, and matches the specificity - * of the blockquote rules in css/styles.css and css/docs.css — this file wins - * the tie because it's the last stylesheet in the head. */ -.docs blockquote.version-unsupported-notice { - background-color: #fef9e6; - border: 1px solid #f7be08; - border-radius: 0.625rem; - color: #4c5773; - font-size: 14px; - line-height: 1.5; - margin: 1.5rem 0; - padding: 0.75rem 1rem; -} - -.docs blockquote.version-unsupported-notice a { - display: inline; -} - -/* Upstream main.css gives nested lists `margin-top: 1.3em` and only tightens it - * to 0.5em at min-width 60em, so on mobile sub-items look detached from their - * parent item. Use a tighter value at every width. */ -.learn.docs li > ul, -.learn.docs li > ol { - margin-top: 0.5rem; -} - -/* Sub-list items sit a bit closer together than top-level items (0.75rem). */ -.learn.docs li li { - margin-bottom: 0.5rem; -} - -/* The list item's own margin already spaces it from the next item, so a - * trailing paragraph shouldn't add more on top. */ -.learn.docs li > p:last-child { - margin-bottom: 0; -} diff --git a/public/docs/css/docs.css b/public/docs/css/docs.css index 70d9191144..9bfd4ee4d9 100644 --- a/public/docs/css/docs.css +++ b/public/docs/css/docs.css @@ -23,6 +23,12 @@ text-decoration: underline; } +/* The version notice (VersionNotice.astro): undoes `.docs a` (inline-block) + * so the links wrap with the text. */ +.docs blockquote.version-unsupported-notice a { + display: inline; +} + .MB-Documentation img { border: 2px solid #e8e8e8; border-radius: 2px; @@ -64,6 +70,19 @@ li { padding-left: 20px; } +/* Notice on docs for versions past their EOL date (VersionNotice.astro). The + * themed layout styles it in src/styles/docs.css. */ +.docs blockquote.version-unsupported-notice { + background-color: #fef9e6; + border: 1px solid #f7be08; + border-radius: 0.625rem; + color: #4c5773; + font-size: 14px; + line-height: 1.5; + margin: 1.5rem 0; + padding: 0.75rem 1rem; +} + .MB-Documentation h1 { margin-top: 0.834em; } diff --git a/public/docs/css/gdpr.css b/public/docs/css/gdpr.css index 3af11a7c7c..1595502705 100644 --- a/public/docs/css/gdpr.css +++ b/public/docs/css/gdpr.css @@ -1,31 +1,106 @@ +/* Cookie banner: a compact, centered dark pill with icon-only settings and + accept buttons and a single "We use cookies." label. These rules come after + the library's stylesheet (gdpr-cookie-notice/dist/style.css) and override + its full-width bar. Icons are inline data-URIs so no external assets are + needed. */ + .gdpr-cookie-notice { + align-items: center; background: #22242b; - border-radius: 0.75rem; - bottom: 0.5rem; - box-shadow: 0 3px 24px rgb(0, 0, 0, 0.08); + border-radius: 24px; + bottom: 24px; + box-shadow: + 0 0 0 1px rgba(255, 255, 255, 0.08), + 0 8px 24px -8px rgba(0, 0, 0, 0.5); + display: flex; + flex-direction: row; font-family: Lato, "Helvetica Neue", Helvetica, sans-serif; - left: 0.5rem; - margin: 0 0.25rem; - padding: 0 0.5rem 0.5rem; + gap: 16px; + justify-content: space-between; + left: 50%; + margin: 0; + max-width: calc(100vw - 32px); + min-width: 340px; + padding: 8px 8px 8px 16px; position: fixed; - right: 0; - width: 75%; + right: auto; + top: auto; + width: auto; + z-index: 1000; +} + +/* The library slides its bar up with a transform once it has loaded; the pill + uses the transform to center itself instead, in both states. */ +.gdpr-cookie-notice, +.gdpr-cookie-notice-loaded .gdpr-cookie-notice { + transform: translateX(-50%); } +/* The label text is the `description` locale string in cookie-consent.js. */ .gdpr-cookie-notice-description { - color: white; - font-size: 13px; - line-height: 14px; + align-items: center; + color: rgba(255, 255, 255, 0.85); + display: flex; + flex: 0 0 auto; + font-size: 14px; + font-weight: 400; + line-height: 1; + margin: 0; + padding: 0; + white-space: nowrap; + width: auto; } .gdpr-cookie-notice-nav { display: flex; - flex-direction: column; - justify-content: flex-start; + flex: 0 0 auto; + flex-direction: row; + gap: 4px; margin: 0; - width: 100%; + width: auto; +} + +.gdpr-cookie-notice-nav-item-settings, +.gdpr-cookie-notice-nav-item-accept { + background-position: center; + background-repeat: no-repeat; + background-size: 16px 16px; + border: 0; + border-radius: 50%; + color: transparent; + cursor: pointer; + flex-shrink: 0; + font-size: 0; + height: 32px; + margin: 0; + overflow: hidden; + padding: 0; + text-indent: 100%; + transition: background-color 0.15s ease; + white-space: nowrap; + width: 32px; +} + +.gdpr-cookie-notice-nav-item-settings { + background-color: rgba(255, 255, 255, 0.1); + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23C6C9D2' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='3'/%3E%3Cpath d='M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 8 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H2a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V2a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H22a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z'/%3E%3C/svg%3E"); +} + +.gdpr-cookie-notice-nav-item-settings:hover { + background-color: rgba(255, 255, 255, 0.18); +} + +.gdpr-cookie-notice-nav-item-accept { + background-color: #509ee3; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E"); } +.gdpr-cookie-notice-nav-item-accept:hover { + background-color: #1c6bb0; +} + +/* Settings modal */ + .gdpr-cookie-notice-modal-header, .gdpr-cookie-notice-modal-cookies, .gdpr-cookie-notice-modal-footer { @@ -80,214 +155,11 @@ right: 0; } -.gdpr-cookie-notice-nav-item { - border-radius: 0.375rem; - display: block; - font-size: 0.938rem; - font-weight: 600; - height: 2.5rem; - line-height: 2.5; - margin: 0.313rem; - padding: 0 1rem; - text-align: center; - white-space: nowrap; - width: 100%; -} - -.gdpr-cookie-notice-nav-item-settings { - background-color: #22242b; - border: 1px solid white; - color: white; -} - -.gdpr-cookie-notice-nav-item-settings:hover { - border: 1px solid #f1f2f4; - color: #f1f2f4; -} - -.gdpr-cookie-notice-nav-item-accept { - background-color: white; - border-color: white; - color: #000; - transition: 300ms linear; -} - -.gdpr-cookie-notice-nav-item-accept:hover { - background-color: #f1f2f4; - border-color: #f1f2f4; - color: #000; -} - .gdpr-cookie-notice-modal-footer-item-save { - transition: background-color 0.3s ease !important; + transition: background-color 0.3s ease; } .gdpr-cookie-notice-modal-footer-item-save:hover { background-color: #1c6bb0; color: white; } - -@media screen and (min-width: 696px) { - .gdpr-cookie-notice { - bottom: 3rem; - left: 2.5rem; - margin: 0; - max-width: 800px; - position: fixed; - right: 0; - width: 70%; - z-index: 20; - } - - .gdpr-cookie-notice-nav-item { - margin: 0 0.625rem; - width: auto; - } - - .gdpr-cookie-notice-nav { - flex-direction: row; - justify-content: flex-start; - } - - .gdpr-cookie-notice { - align-items: center; - display: flex; - } - - .gdpr-cookie-notice-description { - margin-bottom: 0; - padding: 1rem; - width: 101%; - } -} - -@media screen and (min-width: 768px) { - .gdpr-cookie-notice { - padding-top: 0.5rem; - } -} - -@media screen and (min-width: 960px) { - .gdpr-cookie-notice { - left: 0; - margin: auto; - width: 73%; - } - - .gdpr-cookie-notice-nav { - justify-content: flex-end; - } -} - -@media screen and (min-width: 1024px) { - .gdpr-cookie-notice-description { - width: 148%; - } - - .gdpr-cookie-notice { - max-width: 1440px; - } -} - -/* --------------------------------------------------------------------------- - Revamp cookie banner — compact centered dark pill with icon-only settings / - accept buttons and a single "We use cookies." label. Layered on top of the - base rules above (hence !important) so it overrides the legacy full-width - bar sitewide without touching the settings-modal styles. Icons are inline - data-URIs so no external assets are needed. - -------------------------------------------------------------------------- */ -.gdpr-cookie-notice { - align-items: center !important; - background: #22242b !important; - border-radius: 24px !important; - bottom: 24px !important; - box-shadow: - 0 0 0 1px rgba(255, 255, 255, 0.08), - 0 8px 24px -8px rgba(0, 0, 0, 0.5) !important; - display: flex !important; - flex-direction: row !important; - gap: 16px !important; - justify-content: space-between !important; - left: 50% !important; - margin: 0 !important; - max-width: calc(100vw - 32px); - min-width: 340px; - padding: 8px 8px 8px 16px !important; - position: fixed !important; - right: auto !important; - top: auto !important; - transform: translateX(-50%) !important; - width: auto !important; - z-index: 1000; -} - -.gdpr-cookie-notice-description { - align-items: center !important; - color: rgba(255, 255, 255, 0.85) !important; - display: flex !important; - flex: 0 0 auto !important; - font-size: 0 !important; - margin: 0 !important; - padding: 0 !important; - white-space: nowrap; - width: auto !important; -} - -.gdpr-cookie-notice-description::after { - content: "We use cookies."; - font-size: 14px; - font-weight: 400; - line-height: 1; -} - -.gdpr-cookie-notice-description a { - display: none !important; -} - -.gdpr-cookie-notice-nav { - display: flex !important; - flex: 0 0 auto !important; - flex-direction: row !important; - gap: 4px !important; - margin: 0 !important; - width: auto !important; -} - -.gdpr-cookie-notice-nav-item-settings, -.gdpr-cookie-notice-nav-item-accept { - background-position: center !important; - background-repeat: no-repeat !important; - background-size: 16px 16px !important; - border: 0 !important; - border-radius: 50% !important; - color: transparent !important; - cursor: pointer; - flex-shrink: 0; - font-size: 0 !important; - height: 32px !important; - margin: 0 !important; - overflow: hidden !important; - padding: 0 !important; - text-indent: 100% !important; - transition: background-color 0.15s ease !important; - white-space: nowrap !important; - width: 32px !important; -} - -.gdpr-cookie-notice-nav-item-settings { - background-color: rgba(255, 255, 255, 0.1) !important; - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23C6C9D2' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='3'/%3E%3Cpath d='M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 8 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H2a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V2a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H22a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z'/%3E%3C/svg%3E") !important; -} - -.gdpr-cookie-notice-nav-item-settings:hover { - background-color: rgba(255, 255, 255, 0.18) !important; -} - -.gdpr-cookie-notice-nav-item-accept { - background-color: #509ee3 !important; - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E") !important; -} - -.gdpr-cookie-notice-nav-item-accept:hover { - background-color: #1c6bb0 !important; -} diff --git a/public/docs/css/inkeep.css b/public/docs/css/inkeep.css index 93cf8990cb..00dfa3bc4c 100644 --- a/public/docs/css/inkeep.css +++ b/public/docs/css/inkeep.css @@ -3,87 +3,45 @@ inkeep-portal { z-index: 100000; } -.ikp-search-bar-trigger__container { - font-family: Lato, "Helvetica Neue", Helvetica, sans-serif; +/* Push the modal below the docs header (--spacing-header-offset in + src/styles/docs.css: 0 while the header is hidden on scroll). The content + is position: fixed, so this margin is what moves it; padding on its + .ikp-modal__overlay parent would not. */ +.ikp-modal__content { + margin-top: var(--tw-spacing-header-offset); } .ikp-modal-widget__content { - font-family: Lato, sans-serif; + font-family: var(--tw-font-sans); } .ikp-ai-chat__toolbar-header { visibility: hidden; } -.ikp-search-bar-trigger__wrapper .ikp-btn { - background-color: #eef6fc; - border: 1px solid #e4ecfb; +/* Match the 8px radius of the docs header's buttons. */ +.ikp-search-bar__button { + border-radius: 8px; } -.ikp-search-bar-trigger__wrapper { - background-color: #eef6fc; - border-radius: 6px; - transition: background-color 0.3s ease; -} - -.ikp-search-bar-trigger__wrapper:hover { - background-color: white; -} - -.ikp-search-bar-trigger__container--variant_subtle:is(:hover,[data-hover]) { - background-color: #eef6fc !important; +/* The font and size of the Get started button beside it (theme variables + from src/styles/docs.css). Inkeep's Inter looks larger at the same size. */ +.ikp-search-bar__text { + font-family: var(--tw-font-sans); + font-size: var(--tw-text-control); + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .ikp-ai-chat-page-wrapper { padding-top: 10px; } -.ikp-search-bar-trigger__text--size_compact { - font-size: 14px; -} - -.ikp-switch-view-toggle { - background-color: transparent; - border: 1px solid #e2e4e9; - margin-right: 8px; -} - -.ikp-switch-view-toggle button { - font-size: 14px; - font-weight: 700; - margin-left: 6px; - padding: 18px; - transition: background-color 0.2s ease, color 0.2s ease; -} - -.ikp-switch-view-toggle button:hover { - background-color: #fafbfe; -} - -.ikp-switch-view-toggle button:first-child { - margin-left: 0; -} - -.ikp-switch-view-toggle__button--active { - background-color: #eef6fc; - border: none; - box-shadow: none; - color: #509ee3; -} - -.ikp-switch-view-toggle__button--active:hover { - background-color: #eef6fc !important; - border: none; - color: #509ee3; -} - -.ikp-switch-view-toggle__button--active svg path { - fill: #509ee3; -} - .ikp-code { - background-color: #f9fbfe; - color: #5a6072; + background-color: var(--tw-color-code-bg); + color: var(--tw-color-secondary); font-weight: 400; } @@ -99,30 +57,11 @@ div.ikp-d_flex.ikp-flex_row.ikp-items_center.ikp-pt_3.ikp-pb_1:has(.ikp-search-b } .ikp-ai-chat__message-box { - background-color: #eef6fc; - border: 1px solid #e4ecfb; + background-color: var(--tw-color-surface-hover); + border: 1px solid var(--tw-color-border); box-shadow: none; } .ikp-ai-chat-footer__content-wrapper { padding-bottom: 30px; } - -@media (max-width: 991.98px) { - .ikp-search-bar__text, - .ikp-search-bar__kbd-wrapper { - display: none; - } - - .ikp-search-bar__button { - background: none; - border: 0; - justify-content: center; - padding: 0; - } - - .ikp-search-bar__icon { - color: #5a6072; - font-size: 18px; - } -} diff --git a/public/docs/js/cookie-consent.js b/public/docs/js/cookie-consent.js index 3e5c886c95..c6f0dfbeee 100644 --- a/public/docs/js/cookie-consent.js +++ b/public/docs/js/cookie-consent.js @@ -272,8 +272,9 @@ // locales gdprCookieNoticeLocales = { en: { - description: - "Like so many others, we use cookies to improve your experience on this website. We assume you're OK with it, but you can opt out if you want.", + // Shown as the one-line label of the cookie pill (gdpr.css), which + // has no room for more. + description: "We use cookies.", settings: "Settings", accept: "Accept cookies", statement: "Our cookie statement", diff --git a/public/docs/js/image-zoom.js b/public/docs/js/image-zoom.js index cb9d714241..db8d80a4e1 100644 --- a/public/docs/js/image-zoom.js +++ b/public/docs/js/image-zoom.js @@ -1,46 +1,115 @@ +// Click (or Enter/Space on) an article image to see it full screen; click +// anywhere, the close button or Escape to go back. The overlay is a modal +// dialog whose only control is the close button, so focus stays there and +// returns to the image afterwards. src/styles/docs.css styles the close +// button, the focus states and the zoomed copy. +const reducedMotion = matchMedia("(prefers-reduced-motion: reduce)"); + const imageZoomWrapper = document.createElement("div"); const imageZoomInnerWrapper = document.createElement("div"); -const closeImageButton = document.createElement("img"); +const closeImageButton = document.createElement("button"); +const closeImageIcon = document.createElement("img"); imageZoomWrapper.classList.add("image-zoom-wrapper", "bootstrap"); +imageZoomWrapper.setAttribute("role", "dialog"); +imageZoomWrapper.setAttribute("aria-modal", "true"); +imageZoomWrapper.setAttribute("aria-label", "Enlarged image"); imageZoomInnerWrapper.classList.add("container"); -imageZoomWrapper.style.transition = "opacity 0.2s"; imageZoomWrapper.style.position = "fixed"; imageZoomWrapper.style.top = "0"; imageZoomWrapper.style.left = "0"; imageZoomWrapper.style.height = "100vh"; imageZoomWrapper.style.width = "100vw"; imageZoomWrapper.style.zIndex = "2000"; -imageZoomWrapper.style.backgroundColor = "#FAFBFE"; +imageZoomWrapper.style.backgroundColor = "var(--tw-color-page, #FAFBFE)"; imageZoomWrapper.style.visibility = "hidden"; -imageZoomInnerWrapper.style.transition = "transform 0.2s"; imageZoomInnerWrapper.style.transform = "scale(0.95)"; imageZoomInnerWrapper.style.height = "100vh"; imageZoomInnerWrapper.style.display = "flex"; imageZoomInnerWrapper.style.alignItems = "center"; imageZoomInnerWrapper.style.justifyContent = "center"; -closeImageButton.src = "/images/close-grey.svg"; -closeImageButton.alt = "Close Image Button"; -closeImageButton.width = 16; -closeImageButton.height = 16; -closeImageButton.style.position = "absolute"; -closeImageButton.style.top = "16px"; -closeImageButton.style.right = "32px"; -closeImageButton.style.cursor = "pointer"; +// Positioned and styled by src/styles/docs.css. +closeImageButton.type = "button"; +closeImageButton.classList.add("image-zoom-close-button"); +closeImageButton.setAttribute("aria-label", "Close"); + +closeImageIcon.classList.add("image-zoom-close"); +closeImageIcon.src = "/images/close-grey.svg"; +closeImageIcon.alt = ""; +closeImageIcon.width = 16; +closeImageIcon.height = 16; +closeImageButton.appendChild(closeImageIcon); imageZoomWrapper.appendChild(imageZoomInnerWrapper); imageZoomWrapper.appendChild(closeImageButton); document.body.appendChild(imageZoomWrapper); -imageZoomWrapper.addEventListener("click", function() { +let zoomTrigger = null; +let hideTimeout = null; + +// Fade and scale, unless the reader asked for less motion. Returns how long +// the transition takes. +function setMotion() { + const duration = reducedMotion.matches ? 0 : 200; + imageZoomWrapper.style.transition = duration ? "opacity 0.2s" : "none"; + imageZoomInnerWrapper.style.transition = duration ? "transform 0.2s" : "none"; + return duration; +} + +function openZoom(trigger, cloneImage) { + clearTimeout(hideTimeout); + setMotion(); + zoomTrigger = trigger; + + imageZoomWrapper.style.visibility = "visible"; + imageZoomWrapper.style.opacity = "1"; + + if (imageZoomInnerWrapper.firstChild) { + imageZoomInnerWrapper.firstChild.replaceWith(cloneImage); + } else { + imageZoomInnerWrapper.appendChild(cloneImage); + } + + imageZoomInnerWrapper.style.transform = "scale(1)"; + closeImageButton.focus(); +} + +function closeZoom() { + if (!zoomTrigger) { + return; + } + + const duration = setMotion(); imageZoomWrapper.style.opacity = "0"; imageZoomInnerWrapper.style.transform = "scale(0.95)"; - setTimeout(function() { + hideTimeout = setTimeout(function() { imageZoomWrapper.style.visibility = "hidden"; - }, 200); + }, duration); + + // A click on a detached wrapper (see below) has nothing to return to. + if (zoomTrigger.isConnected) { + zoomTrigger.focus({ preventScroll: true }); + } + zoomTrigger = null; +} + +// The close button's click bubbles here too. +imageZoomWrapper.addEventListener("click", closeZoom); + +imageZoomWrapper.addEventListener("keydown", function(event) { + if (event.key === "Tab") { + event.preventDefault(); + closeImageButton.focus(); + } +}); + +document.addEventListener("keydown", function(event) { + if (event.key === "Escape") { + closeZoom(); + } }); // Avoid wrapping certain images, like the chevrons used in Learn breadcrumbst @@ -66,15 +135,23 @@ contentImages.forEach(function(image) { }); wrapper.addEventListener("click", function() { - imageZoomWrapper.style.visibility = "visible"; - imageZoomWrapper.style.opacity = "1"; - - if (imageZoomInnerWrapper.firstChild) { - imageZoomInnerWrapper.firstChild.replaceWith(cloneImage); - } else { - imageZoomInnerWrapper.appendChild(cloneImage); - } - - imageZoomInnerWrapper.style.transform = "scale(1)"; + openZoom(wrapper, cloneImage); }); + + // Keyboard access, except for images inside a link: the link is the + // control there. + if (wrapper.isConnected && !image.closest("a")) { + wrapper.tabIndex = 0; + wrapper.setAttribute("role", "button"); + wrapper.setAttribute( + "aria-label", + image.alt ? `Enlarge image: ${image.alt}` : "Enlarge image", + ); + wrapper.addEventListener("keydown", function(event) { + if (event.key === "Enter" || event.key === " ") { + event.preventDefault(); + openZoom(wrapper, cloneImage); + } + }); + } }); diff --git a/public/docs/js/new-docs-anchor-links.js b/public/docs/js/new-docs-anchor-links.js index 6c214199d2..fceeb547f8 100644 --- a/public/docs/js/new-docs-anchor-links.js +++ b/public/docs/js/new-docs-anchor-links.js @@ -5,7 +5,7 @@ function htmlToElement(html) { return $template.content.firstChild; } -function addCopyToClipboardLogic($element, link) { +function addCopyToClipboardLogic($element, link, linkHash) { const copyToClipboardWrapper = $element.querySelector( ".copy-to-clipboard-wrapper", ); @@ -15,18 +15,26 @@ function addCopyToClipboardLogic($element, link) { let copyToClipboardTimeout = null; let copyToClipboardFadeTimeout = null; - $element.addEventListener("mouseenter", function() { + // The tooltip shows for keyboard focus as well as for the pointer. + function showTooltip() { copyToClipboardWrapper.classList.remove("invisible"); copyToClipboardWrapper.style.opacity = "1"; - }); - $element.addEventListener("mouseleave", function() { + } + function hideTooltip() { copyToClipboardWrapper.classList.add("invisible"); copyToClipboardWrapper.style.opacity = "0"; - }); + } + $element.addEventListener("mouseenter", showTooltip); + $element.addEventListener("mouseleave", hideTooltip); + $element.addEventListener("focus", showTooltip); + $element.addEventListener("blur", hideTooltip); + // Copies the link and puts it in the address bar, without the jump (or + // the :target highlight) that following the href would cause. $element.onclick = function(event) { event.preventDefault(); navigator.clipboard.writeText(link); + history.replaceState(history.state, "", linkHash); if (copyToClipboardTimeout) { clearTimeout(copyToClipboardTimeout); @@ -49,8 +57,8 @@ function addCopyToClipboardLogic($element, link) { } const copyToClipboardHTML = - '\n' + - '
) : (
These are the docs for Metabase {version}. Check out the{" "} - + docs for the current stable version, Metabase {site.docs_version}.
diff --git a/src/components/chrome/VersionSelector.astro b/src/components/chrome/VersionSelector.astro deleted file mode 100644 index 5054295132..0000000000 --- a/src/components/chrome/VersionSelector.astro +++ /dev/null @@ -1,228 +0,0 @@ ---- -import Chevron from "@/icons/chevron.svg"; -import { docsVersionHref } from "@/lib/docs/versionHref"; -import { baseCtx } from "@/lib/liquid/liquidRenderer"; - -type Props = { - version?: string; - variant: "top-bar" | "inline"; -}; - -const { version, variant } = Astro.props; -const { site } = baseCtx; - -const currentVersion = version === "latest" ? site.docs_version : version; -const versions = [...site.available_versions].reverse().slice(0, 10); ---- - -
-
- {currentVersion} - -
- -
- - - - diff --git a/src/components/chrome/VersionTag.astro b/src/components/chrome/VersionTag.astro new file mode 100644 index 0000000000..9796548323 --- /dev/null +++ b/src/components/chrome/VersionTag.astro @@ -0,0 +1,39 @@ +--- +import { getVersionSupport } from "@/lib/docs/versionSupport"; +import { baseCtx } from "@/lib/liquid/liquidRenderer"; + +// Status tag next to a docs version: "latest" for the current docs version +// (site.docs_version), "unsupported" past the EOL date, "lts" for +// long-term-support releases, nothing otherwise. Shows one tag at most, in +// that order. Pass `tag` instead to show one in prose. Used by the sidebar's +// version selector and /docs/all. +type Tag = "latest" | "unsupported" | "lts"; +type Props = { version: string } | { tag: Tag }; + +const tagFor = (version: string): Tag | undefined => { + if (version === baseCtx.site.docs_version) return "latest"; + const support = getVersionSupport(version); + if (support?.status === "unsupported") return "unsupported"; + return support?.lts ? "lts" : undefined; +}; + +const tag = + "tag" in Astro.props ? Astro.props.tag : tagFor(Astro.props.version); + +const colors: Record = { + latest: "tw:bg-surface-hover tw:text-brand", + unsupported: "tw:bg-tag tw:text-secondary", + lts: "tw:bg-tag-lts tw:text-tag-lts-text", +}; +--- + +{tag && ( + + {tag} + +)} diff --git a/src/components/chrome/breakpoints.ts b/src/components/chrome/breakpoints.ts new file mode 100644 index 0000000000..18ee771070 --- /dev/null +++ b/src/components/chrome/breakpoints.ts @@ -0,0 +1,8 @@ +// Below the theme's lg breakpoint the docs chrome switches to its mobile +// layout. docs.css emits the breakpoint on :root (`@theme static`), so +// scripts read it instead of repeating the value. +const lg = getComputedStyle(document.documentElement) + .getPropertyValue("--tw-breakpoint-lg") + .trim(); + +export const belowLg = matchMedia(`(width < ${lg})`); diff --git a/src/components/chrome/docs-ui.ts b/src/components/chrome/docs-ui.ts new file mode 100644 index 0000000000..1463717f4e --- /dev/null +++ b/src/components/chrome/docs-ui.ts @@ -0,0 +1,46 @@ +// Utility class lists shared by more than one docs chrome component or +// layout. Anything used in a single component stays inline in that component. + +/** + * Quiet controls: the section bar's buttons (SectionBar.astro, + * TocToggle.astro) and the version selector trigger. Each pairs this with its + * own layout classes. + */ +export const quietControl = + "tw:cursor-pointer tw:rounded-lg tw:border-0 tw:bg-transparent tw:font-sans tw:text-sm/5 tw:text-secondary tw:transition-colors tw:hover:bg-surface-active tw:hover:text-brand tw:focus-ring tw:aria-expanded:bg-surface-active tw:aria-expanded:text-brand"; + +/** A 20px line icon (an inline on a 20×20 viewBox). */ +export const strokeIcon = + "tw:size-5 tw:shrink-0 tw:fill-none tw:stroke-current tw:stroke-[1.5] tw:[stroke-linecap:round]"; + +/** Outlined 40px icon buttons in the header: search (below xl) and ⋯. */ +export const iconButton = + "tw:flex tw:size-10 tw:shrink-0 tw:cursor-pointer tw:items-center tw:justify-center tw:rounded-lg tw:border tw:border-border tw:bg-surface tw:p-0 tw:text-secondary tw:transition-colors tw:hover:bg-surface-hover tw:hover:text-brand tw:focus-ring tw:aria-expanded:bg-surface-active tw:aria-expanded:text-brand"; + +/** + * A dropdown's menu. It follows its trigger, which carries `peer` and the + * aria-expanded state (dropdown.ts). Callers add its horizontal position. + * + * visibility keeps the closed menu's links out of the tab order. It + * transitions with the fade: a transitioning visibility counts as visible + * until the end, so the menu disappears only once the fade-out has played. + * With reduced motion it opens and closes at once. + */ +export const menuPanel = + "tw:pointer-events-none tw:invisible tw:absolute tw:top-[calc(100%+6px)] tw:z-5 tw:m-0 tw:min-w-40 tw:scale-[0.97] tw:list-none tw:overflow-hidden tw:rounded-lg tw:border tw:border-border tw:bg-surface-elevated tw:px-0 tw:py-1 tw:opacity-0 tw:shadow-card tw:peer-aria-expanded:pointer-events-auto tw:peer-aria-expanded:visible tw:peer-aria-expanded:scale-100 tw:peer-aria-expanded:opacity-100 tw:motion-safe:transition-[scale,opacity,visibility] tw:motion-safe:duration-200 tw:motion-safe:ease-in-out"; + +/** + * An item in a `menuPanel`: a link or button. It is a flex row, so an icon or + * a tag can go beside the label. Callers add the text color. The focus ring + * sits inside: the panel clips its overflow. + */ +export const menuItem = + "tw:flex tw:items-center tw:gap-2 tw:px-4 tw:py-2 tw:text-sm tw:no-underline tw:hover:bg-page tw:focus-ring tw:focus-visible:-outline-offset-2"; + +/** A page link in the left sidebar (LeftSidebar.astro, NavItem.astro). */ +export const navLink = + "tw:relative tw:block tw:rounded-lg tw:pt-1 tw:pb-1.25 tw:pl-2 tw:text-sm/5 tw:no-underline tw:transition-colors tw:focus-ring tw:focus-visible:-outline-offset-2 tw:aria-[current=page]:bg-surface-hover tw:aria-[current=page]:font-bold tw:aria-[current=page]:text-brand"; + +/** An item in the AI tools row under the doc title (DocActions.astro, CopyMarkdownButton.astro). */ +export const docAction = + "tw:flex tw:cursor-pointer tw:items-center tw:gap-1.5 tw:rounded-sm tw:border-0 tw:bg-transparent tw:p-0 tw:font-sans tw:text-sm/5 tw:font-bold tw:whitespace-nowrap tw:text-muted tw:no-underline tw:transition-colors tw:hover:text-brand tw:focus-ring"; diff --git a/src/components/chrome/dropdown.ts b/src/components/chrome/dropdown.ts new file mode 100644 index 0000000000..288f7916a8 --- /dev/null +++ b/src/components/chrome/dropdown.ts @@ -0,0 +1,38 @@ +// Open and close for the docs chrome dropdowns. Each marks its root +// `data-dropdown` and its trigger `data-dropdown-trigger`, and imports this +// module, which Astro bundles once. +// The trigger's aria-expanded is the open state; the components style off it +// (`aria-expanded:`, `group-aria-expanded:`, `peer-aria-expanded:`). +const initDropdown = (root: HTMLElement, trigger: HTMLElement) => { + const isOpen = () => trigger.getAttribute("aria-expanded") === "true"; + + const setOpen = (open: boolean) => { + trigger.setAttribute("aria-expanded", open ? "true" : "false"); + }; + + trigger.addEventListener("click", () => setOpen(!isOpen())); + + // Clicks inside the menu leave it open (the theme options), and a click on + // another dropdown's trigger closes this one. + document.addEventListener("click", (event) => { + if (!root.contains(event.target as Node)) setOpen(false); + }); + + // A null relatedTarget means focus went to the body (Safari does not + // focus links on click); the document click handler covers that case. + root.addEventListener("focusout", (event) => { + const next = event.relatedTarget as Node | null; + if (next && !root.contains(next)) setOpen(false); + }); + + document.addEventListener("keydown", (event) => { + if (event.key !== "Escape" || !isOpen()) return; + setOpen(false); + if (root.contains(document.activeElement)) trigger.focus(); + }); +}; + +document.querySelectorAll("[data-dropdown]").forEach((root) => { + const trigger = root.querySelector("[data-dropdown-trigger]"); + if (trigger) initDropdown(root, trigger); +}); diff --git a/src/constants.ts b/src/constants.ts index a083c0911d..afe08f7e04 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -23,5 +23,3 @@ export const UNIFY_ENABLED_PAGES = [ "/docs/latest/embedding/sdk/quickstart", "/docs/latest/embedding/full-app-embedding-quick-start-guide", ]; - -export const TOP_BAR_ID = "top-bar"; diff --git a/src/env.d.ts b/src/env.d.ts index 9dfd81c658..20c1e7ad2c 100644 --- a/src/env.d.ts +++ b/src/env.d.ts @@ -1,2 +1,11 @@ // cxkit-js points "types" at dist/embed.d.ts but doesn't publish it. declare module "@inkeep/cxkit-js"; + +interface Window { + // Snowplow's consented tracker, which cookie-consent.js loads (via + // marketing-snowplow.js) only after marketing cookies are accepted. + snowplow?: (...args: unknown[]) => void; + // Sets and stores the docs theme preference: "system", "light" or "dark". + // Defined by _includes/docs/theme-init.js, inlined on themed docs pages. + setDocsTheme: (preference: string) => void; +} diff --git a/src/icons/check.svg b/src/icons/check.svg new file mode 100644 index 0000000000..206567efca --- /dev/null +++ b/src/icons/check.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/icons/chevron.svg b/src/icons/chevron.svg index a0ec40d6ac..62f4eaecb5 100644 --- a/src/icons/chevron.svg +++ b/src/icons/chevron.svg @@ -9,7 +9,7 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/icons/chrome/metabase-wordmark.svg b/src/icons/chrome/metabase-wordmark.svg index c34b2a0635..b6673c7dd2 100644 --- a/src/icons/chrome/metabase-wordmark.svg +++ b/src/icons/chrome/metabase-wordmark.svg @@ -1,12 +1,12 @@ - - - - - - - - + + + + + + + + diff --git a/src/icons/copy.svg b/src/icons/copy.svg new file mode 100644 index 0000000000..f0647bc05a --- /dev/null +++ b/src/icons/copy.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/icons/markdown.svg b/src/icons/markdown.svg new file mode 100644 index 0000000000..c8a9b02529 --- /dev/null +++ b/src/icons/markdown.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/icons/plug.svg b/src/icons/plug.svg new file mode 100644 index 0000000000..aad5aeb73d --- /dev/null +++ b/src/icons/plug.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/layouts/DefaultNewLayout.astro b/src/layouts/DefaultNewLayout.astro index 63396576b4..15650d370b 100644 --- a/src/layouts/DefaultNewLayout.astro +++ b/src/layouts/DefaultNewLayout.astro @@ -1,27 +1,32 @@ --- import Footer from "@/components/chrome/Footer.astro"; -import Header from "@/components/chrome/Header.astro"; import LiquidInclude from "@/components/LiquidInclude.astro"; import { getLiquidRenderer } from "@/lib/liquid/liquidRenderer"; +import "@/styles/docs.css"; +// Docs-only: the theme is always on, and there is no default header, so +// callers must fill the "header" slot (DocsHeader). type Props = { page: Record; dirname: string; unify?: boolean; - hideOnScroll?: boolean; + /** schema.org JSON for the page's (src/lib/docs/jsonLd.ts). */ + jsonLd?: string; }; -const { page, dirname, unify, hideOnScroll } = Astro.props; +const { page, dirname, unify, jsonLd } = Astro.props; const lq = getLiquidRenderer({ page, dirname }); --- - + {/* docs_theme: head.html inlines the theme init script and skips the legacy + stylesheet links, which src/styles/docs.css imports instead. */} + - -
+ +
diff --git a/src/layouts/ErrorLayout.astro b/src/layouts/ErrorLayout.astro deleted file mode 100644 index 5220f0ac81..0000000000 --- a/src/layouts/ErrorLayout.astro +++ /dev/null @@ -1,32 +0,0 @@ ---- -import Footer from "@/components/chrome/Footer.astro"; -import Header from "@/components/chrome/Header.astro"; -import LiquidInclude from "@/components/LiquidInclude.astro"; -import { getLiquidRenderer } from "@/lib/liquid/liquidRenderer"; - -type Props = { - page: Record; - dirname: string; -}; - -const { page, dirname } = Astro.props; - -const lq = getLiquidRenderer({ page, dirname }); ---- - - - - - - -
- -
-
- -
-
- -