├── .editorconfig ├── .env-dist ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug.yml │ └── config.yml ├── PULL_REQUEST_TEMPLATE ├── dependabot.yml ├── release-please-config.json ├── release-please-manifest.json └── workflows │ ├── _deploy.yml │ ├── auto-merge.yml │ ├── main-review-companion.yml │ ├── npm-publish-simulation.yml │ ├── npm-publish.yml │ ├── pr-rebase-needed.yml │ ├── pr-review-companion.yml │ └── test.yml ├── .gitignore ├── .lefthook.yml ├── .npmignore ├── .nvmrc ├── .prettierignore ├── .stylelintignore ├── .vscode ├── .gitignore └── extensions.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── build.sh ├── build ├── env.js ├── eslint-fred.js ├── hmr.js ├── loaders │ ├── escape.js │ ├── fix-light-dark.js │ ├── fluent.js │ ├── lit-css.js │ └── lit-svg.js ├── localhost-cert.pem ├── localhost-privkey.pem ├── plugins │ ├── csp-hash.js │ └── generate-element-map.js ├── render.js ├── server-worker.js ├── ssr.js ├── types.d.ts └── utils.js ├── components ├── a11y-menu │ ├── server.css │ └── server.js ├── about-tabs │ ├── element.css │ └── element.js ├── about-team-member │ └── element.js ├── advertising │ ├── assets │ │ └── m-logo.svg │ ├── server.css │ └── server.js ├── article-footer │ ├── article-footer.svg │ ├── server.css │ └── server.js ├── badge │ └── global.css ├── banner │ ├── server.css │ └── server.js ├── baseline-indicator │ ├── hook.js │ ├── icons │ │ ├── browser-check.svg │ │ ├── browser-cross.svg │ │ ├── browser │ │ │ ├── chrome.svg │ │ │ ├── edge.svg │ │ │ ├── firefox.svg │ │ │ └── safari.svg │ │ ├── chevron.svg │ │ └── status │ │ │ ├── high-dark.svg │ │ │ ├── high.svg │ │ │ ├── limited-dark.svg │ │ │ ├── limited.svg │ │ │ ├── low-dark.svg │ │ │ └── low.svg │ ├── inline.js │ ├── server.css │ ├── server.js │ └── types.d.ts ├── blog-index │ ├── assets │ │ └── m-logo.svg │ ├── server.css │ └── server.js ├── blog-post │ ├── server.css │ └── server.js ├── blog │ └── utils.js ├── breadcrumbs-bar │ ├── server.css │ └── server.js ├── breadcrumbs │ ├── server.css │ └── server.js ├── button │ ├── element.css │ ├── element.js │ ├── global.css │ ├── pure.js │ ├── sandbox.js │ ├── server.css │ ├── server.js │ └── types.d.ts ├── code-example │ ├── common.css │ ├── element.css │ ├── element.js │ ├── global.css │ ├── prism.css │ ├── syntax-highlight.js │ └── types.d.ts ├── collection-save-button │ ├── element.css │ ├── element.js │ ├── rust-types.d.ts │ └── types.d.ts ├── color-theme │ ├── controller.js │ ├── element.css │ ├── element.js │ ├── global.css │ └── types.d.ts ├── color │ ├── area.css │ ├── background.css │ ├── border.css │ ├── global.css │ ├── link.css │ ├── scrollbar.css │ └── text.css ├── compat-table-lazy │ ├── element.css │ └── element.js ├── compat-table │ ├── constants.js │ ├── element.css │ ├── element.js │ ├── feature-row.js │ ├── icons.css │ ├── index-common.css │ ├── index-desktop-md.css │ ├── index-desktop-xl.css │ ├── index-desktop.css │ ├── index-global.css │ ├── index-mobile.css │ ├── index-trailer.css │ └── utils.js ├── content-feedback │ ├── element.css │ ├── element.js │ └── sandbox.js ├── content-section │ ├── server.css │ └── server.js ├── contributor-list │ ├── element.css │ ├── element.js │ └── types.d.ts ├── contributor-spotlight │ ├── server.css │ └── server.js ├── cookie │ └── utils.js ├── copy-button │ └── element.js ├── curriculum-about │ ├── server.css │ └── server.js ├── curriculum-default │ ├── server.css │ └── server.js ├── curriculum-landing │ ├── server.css │ └── server.js ├── curriculum-module │ ├── server.css │ └── server.js ├── curriculum-overview │ ├── server.css │ └── server.js ├── curriculum-tabs │ └── element.js ├── curriculum │ ├── assets │ │ ├── curriculum-about-covered.svg │ │ ├── curriculum-about-detail.svg │ │ ├── curriculum-about-educators.svg │ │ ├── curriculum-about-not.svg │ │ ├── curriculum-about-students.svg │ │ ├── curriculum-bullet.svg │ │ ├── curriculum-ext-resource.svg │ │ ├── curriculum-landing-about-beginner.svg │ │ ├── curriculum-landing-about-bullet.svg │ │ ├── curriculum-landing-about-free.svg │ │ ├── curriculum-landing-about-pace.svg │ │ ├── curriculum-landing-arrow.svg │ │ ├── curriculum-landing-stairway-1.svg │ │ ├── curriculum-landing-stairway-2-small.svg │ │ ├── curriculum-landing-stairway-2.svg │ │ ├── curriculum-landing-started-advanced.svg │ │ ├── curriculum-landing-started-beginner.svg │ │ ├── curriculum-landing-started-educator.svg │ │ ├── curriculum-landing-started-employment.svg │ │ ├── curriculum-landing-top.svg │ │ ├── curriculum-mdn-resource.svg │ │ ├── curriculum-modules-underline.svg │ │ ├── curriculum-next.svg │ │ ├── curriculum-partner-banner-illustration-large-dark.svg │ │ ├── curriculum-partner-banner-illustration-large-light.svg │ │ ├── curriculum-partner-bg.svg │ │ ├── curriculum-partner-underline-large.svg │ │ ├── curriculum-partner-underline-small.svg │ │ ├── curriculum-prev.svg │ │ ├── curriculum-resources.svg │ │ ├── curriculum-scrim-bg.svg │ │ ├── curriculum-started-underline.svg │ │ ├── curriculum-topic-practices.svg │ │ ├── curriculum-topic-scripting.svg │ │ ├── curriculum-topic-standards.svg │ │ ├── curriculum-topic-styling.svg │ │ ├── curriculum-topic-tooling.svg │ │ ├── fullscreen-enter.svg │ │ ├── landing-scrim.png │ │ ├── scrim-bg.png │ │ ├── scrim-hexagons.svg │ │ ├── scrim-play.svg │ │ └── scrimba-logo.svg │ ├── layout.css │ ├── module-list.css │ ├── module.css │ ├── partner-banner.css │ ├── shared.css │ ├── sidebar.css │ ├── toc.css │ ├── utils.js │ └── vars.css ├── doc │ └── server.js ├── dropdown │ ├── element.css │ ├── element.js │ └── global.css ├── env │ ├── README.md │ ├── index.js │ ├── runtime.js │ ├── types.d.ts │ └── utils.js ├── external-link │ ├── global.css │ └── sandbox.js ├── favicon │ └── pure.js ├── featured-articles │ ├── server.css │ └── server.js ├── font │ ├── README.md │ ├── fonts │ │ ├── inter-cyrillic.woff2 │ │ ├── inter-italic-cyrillic.woff2 │ │ ├── inter-italic-latin-extended.woff2 │ │ ├── inter-italic-latin.woff2 │ │ ├── inter-latin-extended.woff2 │ │ ├── inter-latin.woff2 │ │ ├── jetbrains-mono-cyrillic.woff2 │ │ ├── jetbrains-mono-italic-cyrillic.woff2 │ │ ├── jetbrains-mono-italic-latin-extended.woff2 │ │ ├── jetbrains-mono-italic-latin.woff2 │ │ ├── jetbrains-mono-latin-extended.woff2 │ │ ├── jetbrains-mono-latin.woff2 │ │ ├── jetbrains-mono-nl.patch │ │ ├── source │ │ │ ├── inter-italic.ttf │ │ │ ├── inter.ttf │ │ │ ├── jetbrains-mono-italic.ttf │ │ │ └── jetbrains-mono.ttf │ │ ├── subset.sh │ │ └── unicode │ │ │ ├── cyrillic.txt │ │ │ ├── latin-extended.txt │ │ │ └── latin.txt │ ├── global.css │ ├── inter.css │ └── jetbrains-mono.css ├── footer │ ├── mdn.svg │ ├── mozilla.svg │ ├── server.css │ └── server.js ├── generic-about │ ├── assets │ │ ├── accurate-sm.svg │ │ ├── accurate.svg │ │ ├── building-dark.svg │ │ ├── building.svg │ │ ├── collaborative-sm.svg │ │ ├── collaborative.svg │ │ ├── dot-dark.svg │ │ ├── dot.svg │ │ ├── education.svg │ │ ├── github-mark-small.svg │ │ ├── global-impact-dark.svg │ │ ├── global-impact.svg │ │ ├── handshake.svg │ │ ├── inclusive-sm.svg │ │ ├── inclusive.svg │ │ ├── line-dot.svg │ │ ├── lines.svg │ │ ├── sparkle.svg │ │ ├── text-box-check-outline.svg │ │ └── web-check.svg │ ├── server.css │ └── server.js ├── generic-community │ ├── assets │ │ ├── community-calls-dark.svg │ │ ├── community-calls.svg │ │ ├── discord-dark.svg │ │ ├── discord.svg │ │ ├── people-dark.svg │ │ ├── people.svg │ │ ├── quote-end-dark.svg │ │ ├── quote-end.svg │ │ ├── quote-start-dark.svg │ │ ├── quote-start.svg │ │ ├── video-bg-dark.svg │ │ ├── video-bg.svg │ │ ├── video-thumbnail.png │ │ └── youtube-play.svg │ ├── server.css │ └── server.js ├── generic-content │ ├── server.css │ └── server.js ├── generic-doc │ └── server.js ├── generic-layout │ ├── server.css │ └── server.js ├── generic-sidebar │ ├── server.css │ └── server.js ├── generic-toc │ ├── server.css │ └── server.js ├── generic │ └── server.js ├── global │ └── global.css ├── heading-anchor │ ├── server.css │ └── server.js ├── homepage-body │ ├── server.css │ └── server.js ├── homepage-contributor-spotlight │ ├── mdn_contributor.png │ ├── quote.svg │ ├── server.css │ └── server.js ├── homepage-footer │ ├── server.css │ └── server.js ├── homepage-header │ ├── server.css │ └── server.js ├── homepage-hero │ ├── server.css │ └── server.js ├── homepage-search │ ├── element.css │ └── element.js ├── homepage │ ├── server.css │ └── server.js ├── html │ └── global.css ├── icon │ ├── README.md │ ├── arrow-left.svg │ ├── arrow-right.svg │ ├── asterisk.svg │ ├── bookmark-check.svg │ ├── bookmark.svg │ ├── browser │ │ ├── bun.svg │ │ ├── chrome.svg │ │ ├── deno.svg │ │ ├── edge.svg │ │ ├── firefox.svg │ │ ├── nodejs.svg │ │ ├── opera.svg │ │ ├── safari.svg │ │ ├── samsung-internet.svg │ │ └── webview.svg │ ├── cancel.svg │ ├── chart-no-axes-combined.svg │ ├── check.svg │ ├── chevron-down.svg │ ├── chevron-left.svg │ ├── chevron-right.svg │ ├── circle-alert.svg │ ├── circle-check.svg │ ├── circle-help.svg │ ├── circle-play.svg │ ├── circle-slash.svg │ ├── circle-x.svg │ ├── circle.svg │ ├── cog.svg │ ├── contrast.svg │ ├── ellipsis.svg │ ├── external-link.svg │ ├── filter.svg │ ├── flag.svg │ ├── flask-conical.svg │ ├── fullscreen-enter.svg │ ├── git-fork.svg │ ├── global.css │ ├── graduation-cap.svg │ ├── info.svg │ ├── languages.svg │ ├── lightbulb.svg │ ├── lock.svg │ ├── log-in.svg │ ├── mdn-m.svg │ ├── mdn-prefix.svg │ ├── menu.svg │ ├── monitor.svg │ ├── moon.svg │ ├── panel-left-close.svg │ ├── panel-left.svg │ ├── quote.svg │ ├── rss.svg │ ├── search.svg │ ├── server.svg │ ├── shield-check.svg │ ├── smartphone.svg │ ├── social │ │ ├── bluesky.svg │ │ ├── github.svg │ │ ├── mastodon.svg │ │ └── x.svg │ ├── sun.svg │ ├── thumbs-down.svg │ ├── thumbs-up.svg │ ├── trash-2.svg │ ├── triangle-alert.svg │ ├── users.svg │ └── wrench.svg ├── image-history │ └── element.js ├── interactive-example │ ├── element.css │ ├── element.js │ ├── utils.js │ ├── with-choices.js │ ├── with-console.js │ └── with-tabs.js ├── issues-table │ ├── element.css │ └── element.js ├── ix-tab-panel │ ├── element.css │ └── element.js ├── ix-tab-wrapper │ ├── element.css │ └── element.js ├── ix-tab │ ├── element.css │ └── element.js ├── language-always-redirect-button │ └── element.js ├── language-switcher │ ├── element.css │ └── element.js ├── latest-news │ ├── server.css │ └── server.js ├── layout │ ├── README.md │ └── global.css ├── left-sidebar │ ├── server.css │ └── server.js ├── live-sample-result │ ├── element.css │ ├── element.js │ └── global.css ├── login-button │ └── element.js ├── logo │ ├── server.css │ └── server.js ├── lorem-ipsum │ └── pure.js ├── mandala │ ├── server.css │ └── server.js ├── media │ ├── README.md │ └── index.css ├── menu │ ├── base.css │ ├── constants.js │ ├── desktop.css │ ├── missing-docs.json │ ├── mobile.css │ ├── server.css │ ├── server.js │ ├── types.d.ts │ └── update-missing-docs.js ├── modal │ ├── element.css │ └── element.js ├── navigation │ ├── base.css │ ├── desktop.css │ ├── hook.js │ ├── mobile.css │ ├── server.css │ └── server.js ├── not-found │ ├── element.css │ ├── element.js │ ├── server.css │ ├── server.js │ └── utils.js ├── notecard │ └── index.css ├── observatory-comparison-table │ ├── element.css │ └── element.js ├── observatory-form │ ├── element.css │ └── element.js ├── observatory-header-link │ ├── element.css │ └── element.js ├── observatory-human-duration │ ├── element.css │ └── element.js ├── observatory-landing │ ├── server.css │ └── server.js ├── observatory-rescan-button │ ├── element.css │ └── element.js ├── observatory-results │ ├── comparison.js │ ├── cookies.js │ ├── csp.js │ ├── element.css │ ├── element.js │ ├── history.js │ ├── print.css │ ├── rating.js │ ├── raw-headers.js │ ├── scoring.js │ ├── server.css │ ├── server.js │ ├── tabs.js │ ├── tooltip.js │ └── trend.js ├── observatory-tests-and-scores │ ├── element.css │ └── element.js ├── observatory │ ├── assets │ │ ├── alert-circle.svg │ │ ├── assessment.svg │ │ ├── fail-icon.svg │ │ ├── landing-illustration.svg │ │ ├── mdn.svg │ │ ├── pass-icon.svg │ │ ├── results-icon.svg │ │ ├── scanning.svg │ │ ├── security.svg │ │ ├── stars.svg │ │ ├── summary-icon.svg │ │ └── tooltip-arrow.svg │ ├── colors.css │ ├── common.css │ ├── constants.js │ └── utils.js ├── only-in-en-us │ └── global.css ├── outer-layout │ ├── server.js │ └── utils.js ├── page-layout │ ├── server.css │ └── server.js ├── page-not-created │ └── global.css ├── pagination │ ├── server.css │ └── server.js ├── placement-bottom │ ├── element.css │ └── element.js ├── placement-hp-main │ ├── element.css │ └── element.js ├── placement-no │ ├── element.css │ └── element.js ├── placement-note │ ├── element.css │ └── element.js ├── placement-sidebar │ ├── element.css │ └── element.js ├── placement-top │ ├── element.css │ └── element.js ├── placement │ ├── context.js │ ├── mixin.js │ └── types.d.ts ├── play-console │ ├── element.css │ ├── element.js │ ├── types.d.ts │ └── utils.js ├── play-controller │ └── element.js ├── play-editor │ ├── element.css │ └── element.js ├── play-runner │ ├── element.css │ ├── element.js │ └── types.d.ts ├── playground │ ├── element.css │ ├── element.js │ ├── server.css │ ├── server.js │ ├── types.d.ts │ └── utils.js ├── plus │ ├── assets │ │ ├── ai-help │ │ │ ├── code-examples-playground.png │ │ │ ├── code-examples-queue.png │ │ │ ├── example-question-answering.png │ │ │ ├── example-question-editing.png │ │ │ ├── history-banner.png │ │ │ ├── history-settings.png │ │ │ ├── issue-template.png │ │ │ ├── login-signup.png │ │ │ ├── rate-answers.png │ │ │ └── report-feedback.png │ │ ├── collections │ │ │ ├── collections-dashboard.png │ │ │ ├── desktop-collections-dashboard-delete.png │ │ │ ├── desktop-collections-delete-fva.png │ │ │ ├── desktop-collections-edit-dialog-add-note.png │ │ │ ├── desktop-collections-edit-dialog.png │ │ │ ├── desktop-collections-edit-menu.png │ │ │ ├── desktop-collections-filter.png │ │ │ ├── desktop-collections-fva.png │ │ │ ├── desktop-collections-sort.png │ │ │ ├── desktop-collections-three-dot-menu.png │ │ │ ├── desktop-collections-undo.png │ │ │ ├── desktop-collections-user-menu.png │ │ │ ├── desktop-page-add-note.png │ │ │ ├── desktop-page-dialog-save.png │ │ │ ├── desktop-page-open-dialog.png │ │ │ ├── desktop-remove-saved-delete.png │ │ │ ├── desktop-remove-saved.png │ │ │ ├── desktop-saving-page.png │ │ │ ├── mobile-add-note.png │ │ │ ├── mobile-burger-menu.png │ │ │ ├── mobile-collections-dashboard-delete-entry.png │ │ │ ├── mobile-collections-dashboard-edit-entry.png │ │ │ ├── mobile-collections-dashboard-edit.png │ │ │ ├── mobile-collections-dashboard-three-dots.png │ │ │ ├── mobile-collections-dashboard.png │ │ │ ├── mobile-collections-delete-entry.png │ │ │ ├── mobile-collections-fva-undo.png │ │ │ ├── mobile-collections-menu-item.png │ │ │ ├── mobile-collections-undo.png │ │ │ ├── mobile-menu.png │ │ │ ├── mobile-open-article-actions.png │ │ │ ├── mobile-plus-menu.png │ │ │ ├── mobile-save-page-step-one.png │ │ │ ├── mobile-save-page.png │ │ │ └── mobile-saved-page.png │ │ ├── notifications │ │ │ ├── access-notifications-from-main-menu.png │ │ │ ├── bulk-unwatch-dashboard.png │ │ │ ├── mobile-notifications-user-menu.png │ │ │ ├── notifications-user-menu.png │ │ │ ├── star-notification.png │ │ │ ├── unwatch-dashboard.png │ │ │ ├── unwatch-page.png │ │ │ ├── watch-list.png │ │ │ └── watch-page.png │ │ ├── offline │ │ │ ├── desktop-offline-clear-data.png │ │ │ ├── desktop-offline-enable-auto-update.png │ │ │ ├── desktop-offline-enable-offline.png │ │ │ ├── desktop-offline-manual-update.png │ │ │ └── desktop-offline-user-menu.png │ │ ├── playground │ │ │ ├── playground-example.png │ │ │ ├── playground-menu.png │ │ │ └── playground-sample.png │ │ └── updates │ │ │ ├── collections.png │ │ │ ├── updates.png │ │ │ ├── updates_bcd.png │ │ │ ├── updates_collection.png │ │ │ ├── updates_filter.png │ │ │ └── updates_search.png │ └── server.js ├── preferred-locale │ └── utils.js ├── prev-next │ └── index.css ├── print │ └── global.css ├── progress-bar │ ├── element.js │ └── index.css ├── radius │ └── global.css ├── recent-contributions │ ├── server.css │ └── server.js ├── recently-visited │ ├── element.css │ ├── element.js │ └── index.js ├── record-visit │ └── element.js ├── reference-layout │ ├── server.css │ └── server.js ├── reference-toc │ ├── server.css │ └── server.js ├── sandbox │ ├── class.js │ ├── server.css │ └── server.js ├── scrim-inline │ ├── assets │ │ ├── BarlowCondensed-SemiBold.woff2 │ │ ├── landing-scrim.png │ │ ├── scrim-bg.png │ │ ├── scrim-hexagons.svg │ │ ├── scrim-play.svg │ │ └── scrimba-logo.svg │ ├── element.css │ ├── element.js │ └── global.css ├── search-button │ ├── element.css │ └── element.js ├── search-modal │ ├── element.css │ ├── element.js │ └── types.d.ts ├── server │ ├── async-local-storage.js │ ├── index.js │ └── types.d.ts ├── sidebar-filter │ ├── element.css │ ├── element.js │ ├── sidebar-filterer.js │ └── utils.js ├── site-search │ ├── element.css │ ├── element.js │ ├── server.css │ ├── server.js │ └── types.d.ts ├── specifications-list │ └── index.js ├── survey │ ├── README.md │ ├── assets │ │ └── survey.svg │ ├── element.css │ ├── element.js │ ├── surveys.js │ ├── types.d.ts │ └── utils.js ├── switch │ ├── element.css │ └── element.js ├── table-container │ ├── README.md │ └── global.css ├── themed-image │ ├── element.css │ └── element.js ├── toggle-sidebar │ ├── element.css │ └── element.js ├── translation-banner │ ├── server.css │ └── server.js ├── user-menu │ ├── base.css │ ├── desktop.css │ ├── element.css │ ├── element.js │ ├── links.js │ └── mobile.css ├── user │ ├── context.js │ └── types.d.ts ├── utils │ └── index.js ├── vars │ └── global.css ├── viewed-controller │ └── viewed-controller.js ├── visually-hidden │ └── global.css ├── writer-open-editor │ └── element.js ├── writer-reload │ ├── element.css │ └── element.js └── writer-toolbar │ ├── server.css │ └── server.js ├── entry.client.js ├── entry.inline.js ├── entry.ssr.js ├── eslint.config.js ├── hooks ├── code-examples.js ├── dialog-closedby.js ├── ga-init.d.ts ├── ga-init.js ├── glean-init.js ├── legacy-theme-controller.js ├── live-samples.js ├── load-elements.js ├── sidebar-scroll-to-current.js ├── skip-to-search.js ├── toc-highlight.js └── user-ping.js ├── l10n ├── context.js ├── de.ftl ├── en-US.ftl ├── es.ftl ├── fluent.js ├── fr.ftl ├── ja.ftl ├── ko.ftl ├── mixin.js ├── pt-BR.ftl ├── ru.ftl ├── zh-CN.ftl └── zh-TW.ftl ├── legacy ├── index.tsx ├── legacy.css └── tsconfig.json ├── package-lock.json ├── package.json ├── public ├── apple-touch-icon.528534bba673c38049c2.png ├── apple-touch-icon.png ├── contribute.json ├── favicon-16x16.png ├── favicon-192x192.png ├── favicon-32x32.png ├── favicon-48x48.png ├── favicon-512x512.png ├── favicon.ico ├── favicon.svg ├── manifest.f42880861b394dd4dc9b.json ├── manifest.json ├── mdn-social-share.d893525a4fb5fb1f67a2.png ├── mdn-social-share.png └── opensearch.xml ├── rspack.config.js ├── scripts ├── npm-test.js ├── server.js └── tests.js ├── server.js ├── stylelint.config.js ├── svgo.config.js ├── symmetric-context ├── both.js ├── client.js ├── server.js └── types.d.ts ├── template.html ├── test ├── pageobjects │ ├── doc.page.js │ └── page.js ├── specs │ └── kitchensink.e2e.js └── utils.js ├── tsconfig.json ├── types ├── bcd.ts ├── compat.ts ├── element-map.d.ts ├── fluent-2.ts ├── fluent.ts ├── fred.ts ├── github.ts ├── lit.ts ├── modules.d.ts ├── observatory.ts └── rari.ts ├── utils ├── dnt-helper.js ├── glean.js ├── mdn-url2breadcrumb.js ├── name-transformation.js └── telemetry-opt-out.js ├── vendor └── yari │ ├── client │ ├── pwa │ │ ├── README.md │ │ ├── package.json │ │ ├── src │ │ │ ├── caches.ts │ │ │ ├── db.ts │ │ │ ├── fetch-interceptors.ts │ │ │ ├── fetcher.ts │ │ │ ├── service-worker.ts │ │ │ └── unpack-cache.ts │ │ ├── tsconfig.json │ │ ├── webpack.config.js │ │ ├── webpack.production.config.js │ │ └── yarn.lock │ └── tsconfig.json │ ├── libs │ └── play │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── package-lock.json │ │ ├── package.json │ │ └── tsconfig.json │ └── tsconfig.json └── wdio.conf.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # MDN Fred CODEOWNERS 3 | # ---------------------------------------------------------------------------- 4 | # Order is important. The last matching pattern takes precedence. 5 | # For more detailed information, see: 6 | # https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners 7 | # ---------------------------------------------------------------------------- 8 | 9 | * @mdn/engineering 10 | 11 | /package.json @mdn/engineering @mdn-bot 12 | /package-lock.json @mdn/engineering @mdn-bot 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: Content or feature request 4 | url: https://github.com/mdn/mdn/issues/new/choose 5 | about: Propose new content for MDN Web Docs or submit a feature request using this link. 6 | - name: MDN GitHub Discussions 7 | url: https://github.com/orgs/mdn/discussions 8 | about: Does the issue involve a lot of changes, or is it hard to split it into actionable tasks? Start a discussion before opening an issue. 9 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### Description 4 | 5 | 6 | 7 | ### Motivation 8 | 9 | 10 | 11 | ### Additional details 12 | 13 | 14 | 15 | ### Related issues and pull requests 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | groups: 8 | prod: 9 | dependency-type: production 10 | update-types: 11 | - minor 12 | - patch 13 | exclude-patterns: 14 | - "@mdn/*" 15 | dev: 16 | dependency-type: development 17 | update-types: 18 | - minor 19 | - patch 20 | exclude-patterns: 21 | - "@mdn/*" 22 | commit-message: 23 | prefix: "chore(deps): " 24 | prefix-development: "chore(deps-dev): " 25 | 26 | - package-ecosystem: "github-actions" 27 | directory: "/" 28 | schedule: 29 | interval: "weekly" 30 | commit-message: 31 | prefix: "ci(deps): " 32 | -------------------------------------------------------------------------------- /.github/release-please-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", 3 | "last-release-sha": "880ac28328394f103694281e0698640d90f52fbb", 4 | "release-type": "node", 5 | "changelog-sections": [ 6 | { "type": "feat", "section": "Features", "hidden": false }, 7 | { "type": "fix", "section": "Bug Fixes", "hidden": false }, 8 | { "type": "enhance", "section": "Enhancements", "hidden": false }, 9 | { "type": "chore", "section": "Miscellaneous", "hidden": false } 10 | ], 11 | "include-component-in-tag": false, 12 | "packages": { 13 | ".": {} 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.github/release-please-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | ".": "1.8.0" 3 | } 4 | -------------------------------------------------------------------------------- /.github/workflows/main-review-companion.yml: -------------------------------------------------------------------------------- 1 | name: Main Review Companion 2 | 3 | on: 4 | schedule: 5 | - cron: "0 0 * * *" 6 | push: 7 | branches: 8 | - main 9 | 10 | workflow_dispatch: 11 | 12 | permissions: 13 | # Authenticate with GCP. 14 | id-token: write 15 | 16 | jobs: 17 | deploy: 18 | if: github.repository_owner == 'mdn' && (github.event_name == 'schedule' || github.actor != 'dependabot[bot]') 19 | uses: ./.github/workflows/_deploy.yml 20 | secrets: inherit 21 | with: 22 | prefix: fred 23 | -------------------------------------------------------------------------------- /.github/workflows/pr-rebase-needed.yml: -------------------------------------------------------------------------------- 1 | name: "PR conflicts" 2 | 3 | on: 4 | push: 5 | pull_request_target: 6 | branches: 7 | - main 8 | types: [synchronize] 9 | 10 | permissions: 11 | # Label pull requests. 12 | pull-requests: write 13 | 14 | concurrency: 15 | group: pr-rebase-needed-${{ github.event.pull_request.number }} 16 | cancel-in-progress: true 17 | 18 | jobs: 19 | labeler: 20 | uses: mdn/workflows/.github/workflows/pr-rebase-needed.yml@main 21 | with: 22 | target-repo: "mdn/content" 23 | secrets: 24 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/* 3 | !dist/ssr/ 4 | dist/ssr/* 5 | !dist/ssr/index.d.ts 6 | out/ 7 | .eslintcache 8 | .stylelintcache 9 | .DS_Store 10 | .env 11 | *.tgz -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .github/ 2 | node_modules/ 3 | 4 | eslint.config.js 5 | rsbuild.config.js 6 | .eslintcache 7 | .lefthook.yml 8 | .prettierignore 9 | .stylelintcache 10 | .stylelintignore 11 | 12 | *.tgz 13 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v22 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | CHANGELOG.md 2 | -------------------------------------------------------------------------------- /.stylelintignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | -------------------------------------------------------------------------------- /.vscode/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | !extensions.json 4 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["jackolope.lit-analyzer-plugin"] 3 | } 4 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of conduct 2 | 3 | This repository is governed by Mozilla's code of conduct and etiquette guidelines. 4 | For more details, read [Mozilla's Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/). 5 | 6 | ## Reporting violations 7 | 8 | For more information on how to report violations of the Community Participation Guidelines, read the [How to report](https://www.mozilla.org/about/governance/policies/participation/reporting/) page. 9 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | If you've discovered a security issue, please report it through the form linked 6 | below, which will create a secure, private ticket. 7 | https://bugzilla.mozilla.org/form.web.bounty 8 | 9 | MDN may be eligible for 10 | [Mozilla's Security Bug Bounty Program](https://www.mozilla.org/en-US/security/bug-bounty/). 11 | You can find more information about the bounty program in the 12 | [Mozilla Web Bug Bounty FAQ](https://www.mozilla.org/en-US/security/bug-bounty/faq-webapp/). 13 | You can use the above form even if you are not interested in a bounty reward. 14 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | if [ -z "$CONTENT_ROOT" ]; then 5 | echo "Error: CONTENT_ROOT must be defined." 6 | exit 1 7 | fi 8 | 9 | npm install 10 | export BUILD_OUT_ROOT=out 11 | npm run rari build 12 | npm run build 13 | npm run ssr 14 | -------------------------------------------------------------------------------- /build/env.js: -------------------------------------------------------------------------------- 1 | import path from "node:path"; 2 | 3 | const defaultOut = path.join(import.meta.dirname, "..", "out"); 4 | const nodeModule = defaultOut.endsWith( 5 | path.join("node_modules", "@mdn", "fred", "out"), 6 | ); 7 | 8 | export const RARI_BUILD_ROOT = process.env.BUILD_OUT_ROOT || defaultOut; 9 | 10 | // When running from an npm package, this needs to be the default output directory, 11 | // so we can load the pre-built assets 12 | export const FRED_BUILD_ROOT = nodeModule ? defaultOut : RARI_BUILD_ROOT; 13 | -------------------------------------------------------------------------------- /build/hmr.js: -------------------------------------------------------------------------------- 1 | const hmr = new EventSource("/__webpack_hmr"); 2 | 3 | hmr.addEventListener("message", (event) => { 4 | try { 5 | const message = JSON.parse(event.data); 6 | if (message.action === "built") { 7 | console.log(`Reloading page: ${message.name} bundle updated`); 8 | location.reload(); 9 | } 10 | } catch { 11 | // 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /build/loaders/escape.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} str 3 | */ 4 | export default function escape(str) { 5 | return str 6 | .replaceAll(`\\`, `\\\\`) 7 | .replaceAll(`$`, `\\$`) 8 | .replaceAll(`\``, `\\\``); 9 | } 10 | -------------------------------------------------------------------------------- /build/loaders/fix-light-dark.js: -------------------------------------------------------------------------------- 1 | import crypto from "node:crypto"; 2 | 3 | /** 4 | * Adds a per-file identifier to the CSS variables used by the light-dark polyfill, 5 | * to avoid conflicts between CSS files. 6 | * @this {import("@rspack/core").LoaderContext} 7 | * @param {string} source 8 | */ 9 | export default function postCssLightDarkFix(source) { 10 | const id = hash(this.resourcePath); 11 | return source.replaceAll( 12 | "--csstools-light-dark-toggle--", 13 | `--csstools-light-dark-toggle-${id}-`, 14 | ); 15 | } 16 | 17 | /** 18 | * @param {string} str 19 | * @returns {string} 20 | */ 21 | function hash(str) { 22 | return crypto.createHash("sha256").update(str).digest("hex").slice(0, 8); 23 | } 24 | -------------------------------------------------------------------------------- /build/loaders/fluent.js: -------------------------------------------------------------------------------- 1 | import escape from "./escape.js"; 2 | 3 | /** 4 | * @this {import("@rspack/core").LoaderContext} 5 | * @param {string} contents 6 | */ 7 | export default function fluentLoader(contents) { 8 | return `export default \`${escape(contents)}\`;`; 9 | } 10 | -------------------------------------------------------------------------------- /build/loaders/lit-css.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @this {import("@rspack/core").LoaderContext} 3 | * @param {string} source 4 | */ 5 | export default function litCssLoader(source) { 6 | return `import { css } from "lit"; 7 | ${source.replace(/export default (.*);/, "export default css([$1]);")}`; 8 | } 9 | -------------------------------------------------------------------------------- /build/loaders/lit-svg.js: -------------------------------------------------------------------------------- 1 | import escape from "./escape.js"; 2 | 3 | /** 4 | * @this {import("@rspack/core").LoaderContext} 5 | * @param {string} contents 6 | */ 7 | export default function litSvgLoader(contents) { 8 | return `import { svg } from "lit"; export default svg\`${escape(contents)}\`;`; 9 | } 10 | -------------------------------------------------------------------------------- /build/types.d.ts: -------------------------------------------------------------------------------- 1 | import { PartialContext } from "@fred"; 2 | import { StatsCompilation } from "@rspack/core"; 3 | 4 | export interface WorkerData { 5 | reqPath: string; 6 | context: PartialContext; 7 | compilationStats: StatsCompilation[]; 8 | } 9 | -------------------------------------------------------------------------------- /build/utils.js: -------------------------------------------------------------------------------- 1 | import { fdir } from "fdir"; 2 | 3 | /** 4 | * @param {string} root 5 | * @param {(path: string, isDirectory: boolean) => boolean} filter 6 | */ 7 | export function crawl(root, filter) { 8 | return new fdir() 9 | .withPathSeparator("/") 10 | .withFullPaths() 11 | .withErrors() 12 | .filter(filter) 13 | .crawl(root) 14 | .withPromise(); 15 | } 16 | -------------------------------------------------------------------------------- /components/a11y-menu/server.css: -------------------------------------------------------------------------------- 1 | .a11y-menu { 2 | --offset: 20em; 3 | --outline: 2px; 4 | 5 | position: absolute; 6 | top: calc(-1 * var(--offset)); 7 | right: var(--outline); 8 | left: var(--outline); 9 | z-index: 1000; 10 | 11 | width: calc(100% - 2 * var(--outline)); 12 | 13 | margin: 0; /* Reset. */ 14 | 15 | list-style: none; /* Reset. */ 16 | 17 | a { 18 | position: absolute; 19 | right: 0; 20 | left: 0; 21 | 22 | padding: 0.8em; 23 | 24 | font-weight: var(--font-weight-bold); 25 | 26 | text-align: center; 27 | 28 | text-decoration: none; 29 | 30 | background-color: var(--color-background-primary); 31 | 32 | &:focus-within { 33 | top: calc(var(--offset) + var(--outline)); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /components/a11y-menu/server.js: -------------------------------------------------------------------------------- 1 | import { html } from "@lit-labs/ssr"; 2 | 3 | import { ServerComponent } from "../server/index.js"; 4 | 5 | export class A11yMenu extends ServerComponent { 6 | /** 7 | * @param {import("@fred").Context} context 8 | */ 9 | render(context) { 10 | return html``; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /components/advertising/assets/m-logo.svg: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /components/banner/server.css: -------------------------------------------------------------------------------- 1 | .banner { 2 | padding: 16px; 3 | 4 | background-color: var(--color-background-secondary); 5 | background-image: repeating-linear-gradient( 6 | 115deg, 7 | var(--color-background-primary) 0 24px, 8 | var(--color-background-secondary) 24px 48px 9 | ); 10 | border-block-end: 1px solid var(--color-border-primary); 11 | } 12 | -------------------------------------------------------------------------------- /components/banner/server.js: -------------------------------------------------------------------------------- 1 | import { html } from "@lit-labs/ssr"; 2 | 3 | import { ServerComponent } from "../server/index.js"; 4 | 5 | export class Banner extends ServerComponent { 6 | render() { 7 | return html``; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /components/baseline-indicator/hook.js: -------------------------------------------------------------------------------- 1 | try { 2 | const indicator = document.querySelector(".baseline-indicator"); 3 | if (indicator instanceof HTMLDetailsElement) { 4 | indicator.addEventListener("toggle", () => { 5 | saveState(indicator.open); 6 | }); 7 | saveState(indicator.open); 8 | } 9 | 10 | /** 11 | * @param {boolean} open 12 | */ 13 | function saveState(open) { 14 | if (open) { 15 | localStorage.setItem("baseline-indicator", "open"); 16 | } else { 17 | localStorage.removeItem("baseline-indicator"); 18 | } 19 | } 20 | } catch (error) { 21 | console.warn("Unable to attach to baseline indicator", error); 22 | } 23 | -------------------------------------------------------------------------------- /components/baseline-indicator/icons/browser-check.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/baseline-indicator/icons/browser-cross.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/baseline-indicator/icons/chevron.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /components/baseline-indicator/icons/status/high-dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/baseline-indicator/icons/status/high.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/baseline-indicator/icons/status/limited-dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/baseline-indicator/icons/status/limited.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/baseline-indicator/icons/status/low-dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/baseline-indicator/icons/status/low.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/baseline-indicator/inline.js: -------------------------------------------------------------------------------- 1 | if (localStorage.getItem("baseline-indicator") === "open") { 2 | const indicator = document.querySelector(".baseline-indicator"); 3 | if (indicator instanceof HTMLDetailsElement) { 4 | indicator.open = true; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /components/baseline-indicator/types.d.ts: -------------------------------------------------------------------------------- 1 | import { Support } from "@mdn/rari"; 2 | 3 | export type BrowserIdentifier = keyof Support; 4 | export interface BrowserGroup { 5 | name: string; 6 | ids: BrowserIdentifier[]; 7 | } 8 | -------------------------------------------------------------------------------- /components/blog-index/assets/m-logo.svg: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /components/button/element.css: -------------------------------------------------------------------------------- 1 | @import url("./server.css"); 2 | 3 | :host { 4 | display: inline-flex; 5 | vertical-align: middle; 6 | } 7 | 8 | .button { 9 | box-sizing: border-box; 10 | width: 100%; 11 | height: 100%; 12 | } 13 | -------------------------------------------------------------------------------- /components/button/global.css: -------------------------------------------------------------------------------- 1 | mdn-button { 2 | display: inline-flex; 3 | vertical-align: middle; 4 | } 5 | -------------------------------------------------------------------------------- /components/button/server.js: -------------------------------------------------------------------------------- 1 | import { ServerComponent } from "../server/index.js"; 2 | 3 | import PureButton from "./pure.js"; 4 | 5 | export class Button extends ServerComponent { 6 | /** 7 | * @param {import("@fred").Context} _context 8 | * @param {Parameters[0]} args 9 | */ 10 | render(_context, args) { 11 | return PureButton(args); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /components/button/types.d.ts: -------------------------------------------------------------------------------- 1 | export type ButtonVariants = "primary" | "secondary" | "plain"; 2 | export type ButtonActions = "positive" | "negative" | undefined; 3 | export type ButtonIconPositions = "before" | "after"; 4 | -------------------------------------------------------------------------------- /components/code-example/element.css: -------------------------------------------------------------------------------- 1 | @import url("../global/global.css"); 2 | @import url("./prism.css"); 3 | @import url("./common.css"); 4 | 5 | .code-example { 6 | .example-header { 7 | gap: 0.5rem; 8 | padding-right: 0.5rem; 9 | 10 | .language-name { 11 | margin-right: auto; 12 | } 13 | } 14 | } 15 | 16 | @media print { 17 | mdn-copy-button, 18 | mdn-button { 19 | display: none !important; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /components/code-example/global.css: -------------------------------------------------------------------------------- 1 | mdn-code-example { 2 | display: block; 3 | } 4 | -------------------------------------------------------------------------------- /components/code-example/types.d.ts: -------------------------------------------------------------------------------- 1 | export interface PrismLanguage { 2 | alias?: string | string[]; 3 | require?: string | string[]; 4 | optional?: string | string[]; 5 | [key: string]: any; 6 | } 7 | -------------------------------------------------------------------------------- /components/collection-save-button/types.d.ts: -------------------------------------------------------------------------------- 1 | export interface NewItem { 2 | collection_id: string; 3 | url: string; 4 | title: string; 5 | notes?: string; 6 | } 7 | -------------------------------------------------------------------------------- /components/color-theme/global.css: -------------------------------------------------------------------------------- 1 | html[data-theme="light"] { 2 | color-scheme: light; 3 | } 4 | 5 | html[data-theme="dark"] { 6 | color-scheme: dark; 7 | } 8 | -------------------------------------------------------------------------------- /components/color-theme/types.d.ts: -------------------------------------------------------------------------------- 1 | export type ColorScheme = "light dark" | "light" | "dark"; 2 | -------------------------------------------------------------------------------- /components/color/area.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --color-area-background: var(--color-background-blue); 3 | --color-area-highlight-border: var(--color-blue-50); 4 | --color-area-link: var(--color-link-normal); 5 | } 6 | 7 | :root[data-current-area="learn"] { 8 | --color-area-background: var(--color-background-orange); 9 | --color-area-highlight-border: var(--color-orange-50); 10 | --color-area-link: light-dark(var(--color-orange-20), var(--color-orange-80)); 11 | } 12 | -------------------------------------------------------------------------------- /components/color/border.css: -------------------------------------------------------------------------------- 1 | /* Border */ 2 | 3 | :root { 4 | --color-border-primary: light-dark( 5 | var(--color-gray-60), 6 | var(--color-gray-40) 7 | ); 8 | --color-border-secondary: light-dark( 9 | var(--color-gray-40), 10 | var(--color-gray-60) 11 | ); 12 | --color-border-active: light-dark(var(--color-blue-50), var(--color-blue-80)); 13 | } 14 | -------------------------------------------------------------------------------- /components/color/link.css: -------------------------------------------------------------------------------- 1 | /* Link */ 2 | 3 | :root { 4 | --color-link-normal: light-dark(var(--color-blue-20), var(--color-blue-80)); 5 | --color-link-visited: light-dark( 6 | var(--color-purple-20), 7 | var(--color-purple-80) 8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /components/color/scrollbar.css: -------------------------------------------------------------------------------- 1 | /* Scrollbar */ 2 | 3 | :root { 4 | --color-scrollbar-track: transparent; 5 | --color-scrollbar-thumb: light-dark( 6 | var(--color-black-alpha-25), 7 | var(--color-white-alpha-25) 8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /components/color/text.css: -------------------------------------------------------------------------------- 1 | /* Text */ 2 | 3 | /* Basic */ 4 | 5 | :root { 6 | --color-text-primary: light-dark(var(--color-black), var(--color-white)); 7 | --color-text-secondary: light-dark( 8 | var(--color-gray-40), 9 | var(--color-gray-60) 10 | ); 11 | } 12 | 13 | /* Tinted */ 14 | 15 | :root { 16 | --color-text-red: light-dark(var(--color-red-20), var(--color-red-80)); 17 | --color-text-orange: light-dark( 18 | var(--color-orange-20), 19 | var(--color-orange-80) 20 | ); 21 | --color-text-yellow: light-dark( 22 | var(--color-yellow-20), 23 | var(--color-yellow-80) 24 | ); 25 | --color-text-green: light-dark(var(--color-green-20), var(--color-green-80)); 26 | --color-text-blue: light-dark(var(--color-blue-20), var(--color-blue-80)); 27 | --color-text-purple: light-dark( 28 | var(--color-purple-20), 29 | var(--color-purple-80) 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /components/compat-table-lazy/element.css: -------------------------------------------------------------------------------- 1 | @import url("../global/global.css"); 2 | -------------------------------------------------------------------------------- /components/compat-table/constants.js: -------------------------------------------------------------------------------- 1 | // [libs/constants] 2 | export const DEFAULT_LOCALE = "en-US"; 3 | 4 | export const ISSUE_METADATA_TEMPLATE = ` 5 | 6 |
7 | MDN page report details 8 | 9 | * Query: \`$QUERY_ID\` 10 | * Report started: $DATE 11 | 12 |
13 | `; 14 | -------------------------------------------------------------------------------- /components/compat-table/element.css: -------------------------------------------------------------------------------- 1 | /* 2 | @use "sass:meta"; 3 | @use "~@mdn/minimalist/sass/mixins/utils" as *; 4 | @use "../../ui/vars" as *; 5 | @use "../../ui/atoms/button"; 6 | @use "../../ui/atoms/icon"; 7 | */ 8 | 9 | @import url("index-global.css"); 10 | @import url("icons.css"); 11 | @import url("index-common.css"); 12 | @import url("index-mobile.css"); 13 | @import url("index-desktop.css"); 14 | @import url("index-desktop-md.css"); 15 | @import url("index-desktop-xl.css"); 16 | @import url("index-trailer.css"); 17 | -------------------------------------------------------------------------------- /components/compat-table/index-desktop-md.css: -------------------------------------------------------------------------------- 1 | /* Compat (desktop-md) */ 2 | 3 | @media (--screen-medium-and-wider) { 4 | /* TODO: removed to stop overflowing 5 | .table-container { 6 | width: calc(100% + 6rem); 7 | } 8 | 9 | .table-container-inner { 10 | min-width: initial; 11 | } */ 12 | 13 | .bc-on-github { 14 | text-align: right; 15 | } 16 | 17 | .bc-table { 18 | /* 25% for feature, 75% for browser columns. */ 19 | grid-template-columns: minmax(25%, max-content) repeat( 20 | var(--compat-browser-count), 21 | calc(75% / var(--compat-browser-count)) 22 | ); 23 | } 24 | 25 | .icon { 26 | /* Workaround for Icons being cut by 1px at the top. */ 27 | --size: calc(1rem + 1px); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /components/compat-table/index-desktop-xl.css: -------------------------------------------------------------------------------- 1 | /* Compat (desktop-xl) */ 2 | 3 | @media (--screen-xlarge-and-wider) { 4 | /* TODO: removed to stop overflowing 5 | .table-container { 6 | width: 100%; 7 | margin: 0; 8 | } 9 | 10 | .table-container-inner { 11 | padding: 0; 12 | } */ 13 | 14 | .bc-table { 15 | /* 33% for feature, 67% for browser columns. */ 16 | grid-template-columns: minmax(33%, max-content) repeat( 17 | var(--compat-browser-count), 18 | calc(67% / var(--compat-browser-count)) 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /components/compat-table/index-global.css: -------------------------------------------------------------------------------- 1 | /* Compat (global) */ 2 | 3 | @import url("../external-link/global.css"); 4 | @import url("../visually-hidden/global.css"); 5 | 6 | * { 7 | box-sizing: border-box; 8 | overflow-wrap: break-word; 9 | } 10 | 11 | /* Remove default margin */ 12 | body, 13 | h1, 14 | h2, 15 | h3, 16 | h4, 17 | p, 18 | figure, 19 | blockquote, 20 | dl, 21 | dd { 22 | margin: 0; 23 | } 24 | 25 | a { 26 | &:link { 27 | color: var(--color-link-normal); 28 | } 29 | 30 | &:visited { 31 | color: var(--color-link-visited); 32 | } 33 | 34 | &:hover { 35 | text-decoration: none; 36 | } 37 | } 38 | 39 | code { 40 | width: fit-content; 41 | padding: 0.125rem 0.25rem; 42 | background-color: var(--color-background-secondary); 43 | } 44 | 45 | button { 46 | appearance: none; 47 | background: none; 48 | border: medium; 49 | } 50 | -------------------------------------------------------------------------------- /components/content-feedback/element.css: -------------------------------------------------------------------------------- 1 | /** Content feedback. */ 2 | 3 | .content-feedback { 4 | padding: 0; 5 | margin: 0; 6 | margin-bottom: 0.25rem; 7 | 8 | border: none; 9 | 10 | > label { 11 | display: block; 12 | margin-bottom: 0.25rem; 13 | } 14 | 15 | .thank-you { 16 | display: block; 17 | margin-bottom: calc(2.75rem + 2px); 18 | } 19 | 20 | mdn-button { 21 | /* Ensure both buttons have same size. */ 22 | flex: 1; 23 | min-width: 0; 24 | } 25 | } 26 | 27 | .content-feedback--buttons { 28 | /* Ensure both buttons take minimal width. */ 29 | display: inline-flex; 30 | gap: 0.75rem; 31 | margin: 0.25rem 0; 32 | } 33 | 34 | .content-feedback--radios { 35 | display: flex; 36 | 37 | gap: 0.25rem; 38 | align-items: center; 39 | 40 | margin: 0.25rem 0; 41 | } 42 | -------------------------------------------------------------------------------- /components/content-feedback/sandbox.js: -------------------------------------------------------------------------------- 1 | import { html } from "lit"; 2 | 3 | import { SandboxComponent } from "../sandbox/class.js"; 4 | 5 | export class ContentFeedbackSandbox extends SandboxComponent { 6 | render() { 7 | return html``; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /components/contributor-list/types.d.ts: -------------------------------------------------------------------------------- 1 | export type ContributorData = { 2 | name: string; 3 | github: string; 4 | org?: string; 5 | }; 6 | -------------------------------------------------------------------------------- /components/curriculum-default/server.css: -------------------------------------------------------------------------------- 1 | @import url("../curriculum/shared.css"); 2 | @import url("../curriculum/sidebar.css"); 3 | @import url("../curriculum/layout.css"); 4 | 5 | .curriculum-content-container.curriculum-default { 6 | .curriculum-layout__header { 7 | padding-top: 0; 8 | padding-bottom: 0; 9 | } 10 | 11 | .curriculum-layout__body { 12 | padding-top: 0; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /components/curriculum-overview/server.css: -------------------------------------------------------------------------------- 1 | @import url("../curriculum/shared.css"); 2 | @import url("../curriculum/sidebar.css"); 3 | @import url("../curriculum/module-list.css"); 4 | @import url("../curriculum/layout.css"); 5 | 6 | .curriculum-content-container.curriculum-overview { 7 | .curriculum-layout__header { 8 | padding-top: 0; 9 | padding-bottom: 0; 10 | } 11 | 12 | .curriculum-layout__body { 13 | padding-top: 0; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /components/curriculum/assets/curriculum-about-covered.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /components/curriculum/assets/curriculum-about-not.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /components/curriculum/assets/curriculum-next.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /components/curriculum/assets/curriculum-partner-underline-large.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /components/curriculum/assets/curriculum-partner-underline-small.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /components/curriculum/assets/curriculum-prev.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /components/curriculum/assets/curriculum-started-underline.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /components/curriculum/assets/curriculum-topic-practices.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /components/curriculum/assets/curriculum-topic-standards.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /components/curriculum/assets/curriculum-topic-styling.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /components/curriculum/assets/fullscreen-enter.svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 12 | 13 | 14 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /components/curriculum/assets/landing-scrim.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/curriculum/assets/landing-scrim.png -------------------------------------------------------------------------------- /components/curriculum/assets/scrim-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/curriculum/assets/scrim-bg.png -------------------------------------------------------------------------------- /components/curriculum/assets/scrim-hexagons.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/curriculum/assets/scrim-play.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/doc/server.js: -------------------------------------------------------------------------------- 1 | import { PageLayout } from "../page-layout/server.js"; 2 | import { ReferenceLayout } from "../reference-layout/server.js"; 3 | import { ServerComponent } from "../server/index.js"; 4 | 5 | export class Doc extends ServerComponent { 6 | /** 7 | * @param {import("@fred").Context} context 8 | */ render(context) { 9 | context.pageTitle = context.doc.pageTitle; 10 | return PageLayout.render(context, ReferenceLayout.render(context)); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /components/dropdown/element.css: -------------------------------------------------------------------------------- 1 | :host { 2 | display: contents; 3 | } 4 | 5 | :host(:not([loaded], :focus-within)) { 6 | slot[name="dropdown"] { 7 | display: none; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /components/dropdown/global.css: -------------------------------------------------------------------------------- 1 | mdn-dropdown { 2 | display: contents; 3 | 4 | &:not([loaded], :focus-within) { 5 | [slot="dropdown"] { 6 | display: none; 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /components/env/runtime.js: -------------------------------------------------------------------------------- 1 | import { parseBool } from "./utils.js"; 2 | 3 | /** @type {string[]} */ 4 | export const runtimeVariables = []; 5 | /** Overriden to prod default (false) in rspack config, set to true so it works in dev server by default. */ 6 | export const RUNTIME_ENV = parseBool("RUNTIME_ENV", true); 7 | -------------------------------------------------------------------------------- /components/env/types.d.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | var __MDNEnv: Record | undefined; 3 | } 4 | 5 | export {}; 6 | -------------------------------------------------------------------------------- /components/external-link/global.css: -------------------------------------------------------------------------------- 1 | .external::after { 2 | display: inline-block; 3 | 4 | width: 1em; 5 | height: 1em; 6 | 7 | margin-left: 0.25em; 8 | 9 | vertical-align: text-top; 10 | 11 | /* 12 | Unicode zero width space as `` to expose the alt text in the a11y tree. 13 | See: https://bugzilla.mozilla.org/show_bug.cgi?id=1976574 14 | */ 15 | content: "\200b" / " (external)"; 16 | 17 | background-color: currentcolor; 18 | 19 | mask-image: url("../icon/external-link.svg"); 20 | mask-size: cover; 21 | } 22 | -------------------------------------------------------------------------------- /components/external-link/sandbox.js: -------------------------------------------------------------------------------- 1 | import { html } from "lit"; 2 | 3 | import { SandboxComponent } from "../sandbox/class.js"; 4 | 5 | export class ExternalLinkSandbox extends SandboxComponent { 6 | render() { 7 | return html`example link`; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /components/favicon/pure.js: -------------------------------------------------------------------------------- 1 | import { html } from "@lit-labs/ssr"; 2 | 3 | export default function Favicon() { 4 | return html` 5 | 10 | 15 | `; 16 | } 17 | -------------------------------------------------------------------------------- /components/font/fonts/inter-cyrillic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/font/fonts/inter-cyrillic.woff2 -------------------------------------------------------------------------------- /components/font/fonts/inter-italic-cyrillic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/font/fonts/inter-italic-cyrillic.woff2 -------------------------------------------------------------------------------- /components/font/fonts/inter-italic-latin-extended.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/font/fonts/inter-italic-latin-extended.woff2 -------------------------------------------------------------------------------- /components/font/fonts/inter-italic-latin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/font/fonts/inter-italic-latin.woff2 -------------------------------------------------------------------------------- /components/font/fonts/inter-latin-extended.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/font/fonts/inter-latin-extended.woff2 -------------------------------------------------------------------------------- /components/font/fonts/inter-latin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/font/fonts/inter-latin.woff2 -------------------------------------------------------------------------------- /components/font/fonts/jetbrains-mono-cyrillic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/font/fonts/jetbrains-mono-cyrillic.woff2 -------------------------------------------------------------------------------- /components/font/fonts/jetbrains-mono-italic-cyrillic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/font/fonts/jetbrains-mono-italic-cyrillic.woff2 -------------------------------------------------------------------------------- /components/font/fonts/jetbrains-mono-italic-latin-extended.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/font/fonts/jetbrains-mono-italic-latin-extended.woff2 -------------------------------------------------------------------------------- /components/font/fonts/jetbrains-mono-italic-latin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/font/fonts/jetbrains-mono-italic-latin.woff2 -------------------------------------------------------------------------------- /components/font/fonts/jetbrains-mono-latin-extended.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/font/fonts/jetbrains-mono-latin-extended.woff2 -------------------------------------------------------------------------------- /components/font/fonts/jetbrains-mono-latin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/font/fonts/jetbrains-mono-latin.woff2 -------------------------------------------------------------------------------- /components/font/fonts/source/inter-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/font/fonts/source/inter-italic.ttf -------------------------------------------------------------------------------- /components/font/fonts/source/inter.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/font/fonts/source/inter.ttf -------------------------------------------------------------------------------- /components/font/fonts/source/jetbrains-mono-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/font/fonts/source/jetbrains-mono-italic.ttf -------------------------------------------------------------------------------- /components/font/fonts/source/jetbrains-mono.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/font/fonts/source/jetbrains-mono.ttf -------------------------------------------------------------------------------- /components/font/fonts/subset.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | for font_path in ./source/*.{ttf,otf}; do 6 | [ -e "$font_path" ] || continue 7 | font_filename=$(basename "$font_path") 8 | font_basename="${font_filename%.*}" 9 | 10 | for unicode_path in ./unicode/*.txt; do 11 | [ -e "$unicode_path" ] || continue 12 | unicode_filename=$(basename "$unicode_path") 13 | unicode_basename="${unicode_filename%.*}" 14 | 15 | output_file="./${font_basename}-${unicode_basename}.woff2" 16 | 17 | pyftsubset "$font_path" \ 18 | --output-file="$output_file" \ 19 | --unicodes-file="$unicode_path" \ 20 | --layout-features='*' \ 21 | --flavor=woff2 \ 22 | --with-zopfli \ 23 | --ignore-missing-glyphs 24 | 25 | echo "Created: $output_file" 26 | done 27 | done 28 | -------------------------------------------------------------------------------- /components/font/fonts/unicode/cyrillic.txt: -------------------------------------------------------------------------------- 1 | U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116 2 | -------------------------------------------------------------------------------- /components/font/fonts/unicode/latin-extended.txt: -------------------------------------------------------------------------------- 1 | U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF 2 | -------------------------------------------------------------------------------- /components/font/fonts/unicode/latin.txt: -------------------------------------------------------------------------------- 1 | U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD 2 | -------------------------------------------------------------------------------- /components/font/global.css: -------------------------------------------------------------------------------- 1 | @import url("inter.css"); 2 | @import url("jetbrains-mono.css"); 3 | 4 | /* Font size */ 5 | 6 | :root { 7 | --font-family-text: "Inter", sans-serif; 8 | --font-family-code: "JetBrains Mono", monospace; 9 | 10 | --font-line-content: 1.75; 11 | --font-line-normal: 1.5; 12 | --font-line-ui: 1; 13 | 14 | --font-size-largest: 2.5rem; 15 | --font-size-larger: 2rem; 16 | --font-size-large: 1.5rem; 17 | --font-size-normal: 1rem; 18 | --font-size-small: 0.8rem; 19 | 20 | --font-weight-normal: 400; 21 | --font-weight-bold: 600; 22 | } 23 | -------------------------------------------------------------------------------- /components/generic-about/assets/accurate-sm.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/generic-about/assets/accurate.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/generic-about/assets/dot-dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/generic-about/assets/dot.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/generic-about/assets/education.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/generic-about/assets/github-mark-small.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/generic-about/assets/handshake.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/generic-about/assets/inclusive-sm.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/generic-about/assets/inclusive.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/generic-about/assets/line-dot.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/generic-about/assets/lines.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/generic-about/assets/sparkle.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/generic-about/assets/text-box-check-outline.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/generic-community/assets/community-calls-dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/generic-community/assets/community-calls.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/generic-community/assets/quote-end-dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/generic-community/assets/quote-end.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/generic-community/assets/quote-start-dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/generic-community/assets/quote-start.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/generic-community/assets/video-thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/generic-community/assets/video-thumbnail.png -------------------------------------------------------------------------------- /components/generic-community/assets/youtube-play.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/generic-doc/server.js: -------------------------------------------------------------------------------- 1 | import { GenericLayout } from "../generic-layout/server.js"; 2 | import { PageLayout } from "../page-layout/server.js"; 3 | import { ServerComponent } from "../server/index.js"; 4 | 5 | export class GenericDoc extends ServerComponent { 6 | /** 7 | * @param {import("@fred").Context} context 8 | */ 9 | render(context) { 10 | return PageLayout.render(context, GenericLayout.render(context)); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /components/generic-layout/server.js: -------------------------------------------------------------------------------- 1 | import { html } from "@lit-labs/ssr"; 2 | 3 | import { GenericContent } from "../generic-content/server.js"; 4 | import { GenericSidebar } from "../generic-sidebar/server.js"; 5 | import { GenericToc } from "../generic-toc/server.js"; 6 | import { ServerComponent } from "../server/index.js"; 7 | 8 | export class GenericLayout extends ServerComponent { 9 | /** 10 | * @param {import("@fred").Context} context 11 | */ 12 | render(context) { 13 | return html` 14 |
15 | 16 |
17 | ${GenericContent.render(context)} 18 |
19 | 22 |
23 | `; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /components/generic/server.js: -------------------------------------------------------------------------------- 1 | import { html } from "@lit-labs/ssr"; 2 | 3 | import { ContentSection } from "../content-section/server.js"; 4 | import { PageLayout } from "../page-layout/server.js"; 5 | import { ServerComponent } from "../server/index.js"; 6 | 7 | export class Generic extends ServerComponent { 8 | /** 9 | * @param {import("@fred").Context} context 10 | */ 11 | render(context) { 12 | return PageLayout.render( 13 | context, 14 | html` 15 | ${context.hyData.sections.map((section) => 16 | ContentSection.render(context, section), 17 | )} 18 | `, 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /components/global/global.css: -------------------------------------------------------------------------------- 1 | /* Global */ 2 | 3 | *, 4 | ::before, 5 | ::after { 6 | box-sizing: border-box; 7 | } 8 | 9 | input, 10 | button, 11 | textarea, 12 | select { 13 | font: inherit; 14 | } 15 | 16 | button { 17 | color: inherit; 18 | cursor: pointer; 19 | } 20 | 21 | img { 22 | max-width: 100%; 23 | height: auto; 24 | } 25 | 26 | a { 27 | color: var(--color-link-normal); 28 | } 29 | 30 | [hidden] { 31 | display: none !important; 32 | } 33 | -------------------------------------------------------------------------------- /components/heading-anchor/server.css: -------------------------------------------------------------------------------- 1 | .heading:target { 2 | scroll-margin-top: var(--sticky-header-height); 3 | } 4 | 5 | .heading-anchor { 6 | color: inherit !important; 7 | 8 | &:not(:hover) { 9 | text-decoration: none; 10 | } 11 | 12 | /* Hash */ 13 | 14 | &::before, 15 | &::after { 16 | position: absolute; 17 | 18 | margin-top: 0.3em; 19 | 20 | font-size: 0.75em; 21 | 22 | color: var(--color-text-secondary); 23 | } 24 | 25 | @media not (--screen-small-and-narrower) { 26 | &::before { 27 | padding-right: 0.8em; 28 | margin-left: -0.8em; 29 | content: "#"; 30 | } 31 | } 32 | 33 | @media (--screen-small-and-narrower) { 34 | padding-right: 1.1ch; 35 | 36 | &::after { 37 | margin-left: 0.25em; 38 | content: "#"; 39 | } 40 | } 41 | 42 | &:not(:hover, :focus)::before, 43 | &:not(:hover, :focus)::after { 44 | visibility: hidden; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /components/heading-anchor/server.js: -------------------------------------------------------------------------------- 1 | import { nothing } from "lit"; 2 | import { ifDefined } from "lit/directives/if-defined.js"; 3 | import { html as hh, unsafeStatic } from "lit/static-html.js"; 4 | 5 | import { ServerComponent } from "../server/index.js"; 6 | 7 | export class HeadingAnchor extends ServerComponent { 8 | /** 9 | * @param {number} level 10 | * @param {string?} id 11 | * @param {string} title 12 | */ 13 | render(level, id, title) { 14 | return id 15 | ? hh`<${unsafeStatic("h" + level)} id=${ifDefined(id)} class="heading">${unsafeStatic(title)}` 16 | : nothing; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /components/homepage-body/server.css: -------------------------------------------------------------------------------- 1 | .homepage-body { 2 | h2 { 3 | font-size: var(--font-size-large); 4 | font-weight: 500; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /components/homepage-contributor-spotlight/mdn_contributor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/homepage-contributor-spotlight/mdn_contributor.png -------------------------------------------------------------------------------- /components/homepage-contributor-spotlight/quote.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /components/homepage-footer/server.css: -------------------------------------------------------------------------------- 1 | .homepage-footer { 2 | h2 { 3 | font-size: var(--font-size-large); 4 | font-weight: 500; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /components/homepage-footer/server.js: -------------------------------------------------------------------------------- 1 | import { html } from "@lit-labs/ssr"; 2 | 3 | import { HomepageContributorSpotlight } from "../homepage-contributor-spotlight/server.js"; 4 | import { ServerComponent } from "../server/index.js"; 5 | 6 | export class HomepageFooter extends ServerComponent { 7 | /** 8 | * @param {import("@fred").Context} context 9 | */ 10 | render(context) { 11 | return html``; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /components/homepage-header/server.css: -------------------------------------------------------------------------------- 1 | .homepage-header { 2 | display: grid; 3 | 4 | grid-template-areas: "copy mandala" "search mandala"; 5 | grid-column-end: extended-full-end; /* Make space for Mandala. */ 6 | 7 | column-gap: 1rem; 8 | } 9 | 10 | .homepage-header__copy { 11 | grid-area: copy; 12 | padding-top: 1rem; 13 | } 14 | 15 | .homepage-header__search { 16 | grid-area: search; 17 | padding-bottom: 1rem; 18 | } 19 | 20 | .homepage-header__mandala { 21 | grid-area: mandala; 22 | max-height: 20rem; 23 | overflow: hidden; 24 | 25 | .mandala svg { 26 | --height: 35rem; 27 | display: block; 28 | height: var(--height); 29 | margin-top: calc(-1 * var(--height) / 4); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /components/homepage-header/server.js: -------------------------------------------------------------------------------- 1 | import { html } from "@lit-labs/ssr"; 2 | 3 | import { HomepageHero } from "../homepage-hero/server.js"; 4 | import { Mandala } from "../mandala/server.js"; 5 | import { ServerComponent } from "../server/index.js"; 6 | 7 | export class HomepageHeader extends ServerComponent { 8 | /** 9 | * @param {import("@fred").Context} context 10 | */ 11 | render(context) { 12 | return html` 13 |
14 |
${HomepageHero.render(context)}
15 | 18 |
${Mandala.render()}
19 |
20 | `; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /components/homepage-hero/server.css: -------------------------------------------------------------------------------- 1 | .homepage-hero { 2 | font-size: var(--font-size-large); 3 | 4 | h1 { 5 | margin-top: 0; 6 | font-size: var(--font-size-largest); 7 | font-weight: normal; 8 | 9 | &::after { 10 | color: light-dark(var(--color-blue-50), var(--color-blue-80)); 11 | content: "_"; 12 | } 13 | } 14 | 15 | a { 16 | color: var(--color-link-normal); 17 | text-decoration: underline; 18 | 19 | &:focus, 20 | &:hover { 21 | text-decoration: none; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /components/homepage-search/element.css: -------------------------------------------------------------------------------- 1 | @import url("../global/global.css"); 2 | 3 | .mdn-homepage-search { 4 | display: flex; 5 | 6 | gap: 0.25em; 7 | align-items: center; 8 | 9 | padding: 0.8em 1em; 10 | margin: 0 auto; 11 | 12 | font-size: var(--font-size-large); 13 | 14 | background-color: transparent; 15 | border: 2px solid var(--color-border-primary); 16 | border-radius: var(--radius-full); 17 | 18 | &:hover { 19 | background-color: var(--color-background-secondary); 20 | } 21 | 22 | &::before { 23 | width: 1em; 24 | height: 1em; 25 | 26 | content: ""; 27 | 28 | background-color: currentcolor; 29 | 30 | mask-image: url("../icon/search.svg"); 31 | mask-size: contain; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /components/homepage/server.css: -------------------------------------------------------------------------------- 1 | .homepage { 2 | display: grid; 3 | grid-template-columns: var(--layout-no-sidebar-extended); 4 | 5 | > * { 6 | grid-column: content; 7 | } 8 | 9 | mdn-placement-hp-main { 10 | grid-column: extended-full; 11 | } 12 | } 13 | 14 | .homepage--dark { 15 | color: var(--color-text-primary); 16 | color-scheme: dark; 17 | background-color: var(--color-background-primary); 18 | } 19 | -------------------------------------------------------------------------------- /components/html/global.css: -------------------------------------------------------------------------------- 1 | /* HTML */ 2 | 3 | html { 4 | font-family: var(--font-family-text); 5 | font-size: var(--font-size-normal); 6 | font-feature-settings: "calt" 0; 7 | font-variant-alternates: styleset(disambiguation); 8 | 9 | line-height: var(--font-line-normal); 10 | 11 | color: var(--color-text-primary); 12 | 13 | overflow-wrap: break-word; 14 | 15 | color-scheme: light dark; 16 | 17 | background-color: var(--color-background-page); 18 | scroll-behavior: smooth; 19 | 20 | @media (prefers-reduced-motion: reduce) { 21 | scroll-behavior: auto; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /components/icon/README.md: -------------------------------------------------------------------------------- 1 | # Icon 2 | 3 | ## Adding a new icon 4 | 5 | 1. Go to [Lucide](https://lucide.dev/icons/) icon catalog and search for the icon you want. 6 | 2. Download the SVG file and save it in the `src/components/icon` directory. 7 | 3. Remove the `class="lucide etc."` attribute from the SVG code. 8 | 9 | ## Custom icon 10 | 11 | If you need a custom icon that is not available in Lucide, add the `mdn-` prefix to the file name: `mdn-custom-icon.svg`. This will help differentiate custom icons. 12 | 13 | ## Setting the color 14 | 15 | TODO 16 | 17 | ``` 18 | stroke="currentColor" 19 | fill="currentColor" 20 | ``` 21 | -------------------------------------------------------------------------------- /components/icon/arrow-left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /components/icon/arrow-right.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/asterisk.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/bookmark-check.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/bookmark.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/browser/chrome.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/browser/edge.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/browser/nodejs.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/browser/opera.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/browser/webview.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/cancel.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/chart-no-axes-combined.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/check.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/chevron-down.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/chevron-left.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/chevron-right.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/circle-alert.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/circle-check.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/circle-help.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/circle-play.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/circle-slash.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/circle-x.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/circle.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/cog.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/contrast.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/ellipsis.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/external-link.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/filter.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/flag.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/flask-conical.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/fullscreen-enter.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/git-fork.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/graduation-cap.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/info.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/languages.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/lightbulb.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/lock.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/log-in.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/icon/mdn-m.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/mdn-prefix.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/menu.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/monitor.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/moon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/panel-left-close.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/panel-left.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/quote.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/rss.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/search.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/server.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/shield-check.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/smartphone.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/social/bluesky.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/social/github.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/social/mastodon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/social/x.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/sun.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/thumbs-down.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/thumbs-up.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/trash-2.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/triangle-alert.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/users.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/icon/wrench.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /components/image-history/element.js: -------------------------------------------------------------------------------- 1 | import { LitElement } from "lit"; 2 | 3 | export class MDNImageHistory extends LitElement { 4 | // Force client-side rendering for disabled shadow DOM 5 | static ssr = false; 6 | 7 | // Disable shadow DOM 8 | createRenderRoot() { 9 | return this; 10 | } 11 | 12 | firstUpdated() { 13 | for (const img of this.renderRoot.querySelectorAll("img")) { 14 | const regex = /@([0-9]+(?:\.[0-9]+)?)(?=x\.[a-z]+$)/; 15 | const match = img.src.match(regex); 16 | if (match?.[1]) { 17 | const baseRes = Number.parseFloat(match[1]); 18 | const dpis = [1, 2]; 19 | img.srcset = dpis 20 | .map( 21 | (dpi) => `${img.src.replace(regex, `@${baseRes * dpi}`)} ${dpi}x`, 22 | ) 23 | .join(", "); 24 | } 25 | } 26 | } 27 | } 28 | 29 | customElements.define("mdn-image-history", MDNImageHistory); 30 | -------------------------------------------------------------------------------- /components/ix-tab-panel/element.css: -------------------------------------------------------------------------------- 1 | :host { 2 | flex: 1; 3 | min-height: 0; 4 | } 5 | -------------------------------------------------------------------------------- /components/ix-tab-panel/element.js: -------------------------------------------------------------------------------- 1 | import { LitElement, html } from "lit"; 2 | 3 | import styles from "./element.css?lit"; 4 | 5 | export class MDNIXTabPanel extends LitElement { 6 | static styles = styles; 7 | 8 | connectedCallback() { 9 | super.connectedCallback(); 10 | this.setAttribute("tabindex", "0"); 11 | this.setAttribute("role", "tabpanel"); 12 | } 13 | 14 | setActive() { 15 | this.setAttribute("slot", "active-panel"); 16 | } 17 | 18 | unsetActive() { 19 | this.removeAttribute("slot"); 20 | } 21 | 22 | render() { 23 | return html``; 24 | } 25 | } 26 | 27 | customElements.define("mdn-ix-tab-panel", MDNIXTabPanel); 28 | -------------------------------------------------------------------------------- /components/ix-tab-wrapper/element.css: -------------------------------------------------------------------------------- 1 | :host { 2 | display: flex; 3 | flex-direction: column; 4 | overflow: hidden; 5 | } 6 | 7 | #tablist { 8 | display: flex; 9 | 10 | flex-shrink: 0; 11 | 12 | gap: 0.5rem; 13 | 14 | overflow-x: auto; 15 | 16 | background: var(--color-background-secondary); 17 | border-bottom: 1px solid var(--color-border-primary); 18 | } 19 | -------------------------------------------------------------------------------- /components/ix-tab/element.css: -------------------------------------------------------------------------------- 1 | :host { 2 | --ix-tab-background-active: light-dark( 3 | var(--color-gray-90), 4 | var(--color-gray-10) 5 | ); 6 | padding: 0.5em 30px; 7 | 8 | font-size: var(--font-size-small); 9 | 10 | color: var(--color-text-secondary); 11 | 12 | cursor: pointer; 13 | 14 | background-color: transparent; 15 | border: 0 none; 16 | border-top: 3px solid transparent; 17 | border-bottom: 3px solid transparent; 18 | 19 | transition: 20 | color 0.2s, 21 | background-color 0.2s; 22 | } 23 | 24 | :host(:hover), 25 | :host(:focus) { 26 | color: var(--color-text-primary); 27 | background-color: var(--ix-tab-background-active); 28 | } 29 | 30 | :host([aria-selected="true"]) { 31 | color: var(--color-border-active); 32 | background-color: var(--ix-tab-background-active); 33 | border-bottom-color: var(--color-border-active); 34 | } 35 | -------------------------------------------------------------------------------- /components/left-sidebar/server.js: -------------------------------------------------------------------------------- 1 | import { html } from "@lit-labs/ssr"; 2 | import { nothing } from "lit"; 3 | import { unsafeHTML } from "lit/directives/unsafe-html.js"; 4 | 5 | import { ServerComponent } from "../server/index.js"; 6 | 7 | export class LeftSidebar extends ServerComponent { 8 | /** 9 | * @param {import("@fred").Context} context 10 | */ 11 | render(context) { 12 | const content = context?.doc?.sidebarHTML; 13 | 14 | if (!content) { 15 | return nothing; 16 | } 17 | 18 | return html``; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /components/live-sample-result/element.css: -------------------------------------------------------------------------------- 1 | @import url("../global/global.css"); 2 | @import url("../code-example/common.css"); 3 | 4 | .code-example { 5 | .example-header { 6 | justify-content: end; 7 | padding-right: 0.5rem; 8 | } 9 | } 10 | 11 | @media print { 12 | .example-header { 13 | display: none !important; 14 | } 15 | 16 | mdn-button { 17 | display: none !important; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /components/live-sample-result/global.css: -------------------------------------------------------------------------------- 1 | mdn-live-sample-result { 2 | display: block; 3 | } 4 | -------------------------------------------------------------------------------- /components/login-button/element.js: -------------------------------------------------------------------------------- 1 | import { LitElement, html } from "lit"; 2 | 3 | import "../button/element.js"; 4 | import { L10nMixin } from "../../l10n/mixin.js"; 5 | import { FXA_SIGNIN_URL } from "../env/index.js"; 6 | 7 | export class MDNLoginButton extends L10nMixin(LitElement) { 8 | static ssr = false; 9 | 10 | get _loginUrl() { 11 | const next = location.href.replace(location.origin, ""); 12 | // TODO: deal with local login 13 | const loginUrl = new URL(FXA_SIGNIN_URL, location.origin); 14 | loginUrl.search = new URLSearchParams({ next }).toString(); 15 | return loginUrl.toString(); 16 | } 17 | 18 | render() { 19 | return html`${this.l10n`Login`}`; 22 | } 23 | } 24 | 25 | customElements.define("mdn-login-button", MDNLoginButton); 26 | -------------------------------------------------------------------------------- /components/logo/server.css: -------------------------------------------------------------------------------- 1 | .logo { 2 | display: block; 3 | padding: 0.5rem; 4 | } 5 | 6 | .logo__image { 7 | display: block; 8 | } 9 | 10 | .logo__letter, 11 | .logo__cursor { 12 | fill: light-dark(var(--color-blue-50), var(--color-blue-80)); 13 | } 14 | 15 | .logo__text { 16 | fill: var(--color-text-primary); 17 | } 18 | -------------------------------------------------------------------------------- /components/lorem-ipsum/pure.js: -------------------------------------------------------------------------------- 1 | export function LoremIpsum() { 2 | return "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."; 3 | } 4 | -------------------------------------------------------------------------------- /components/mandala/server.css: -------------------------------------------------------------------------------- 1 | .mandala { 2 | svg { 3 | font-size: var(--font-size-large); 4 | font-weight: 300; 5 | user-select: none; 6 | } 7 | 8 | svg > text { 9 | fill: var(--color-border-primary); 10 | } 11 | 12 | textPath[href="#circle1"] { 13 | font-size: 1.5rem; 14 | } 15 | 16 | textPath[href="#circle2"] { 17 | font-size: 1.3rem; 18 | } 19 | 20 | textPath[href="#circle3"] { 21 | font-size: 1.2rem; 22 | } 23 | 24 | textPath[href="#circle4"] { 25 | font-size: 1.1rem; 26 | } 27 | 28 | textPath[href="#circle5"] { 29 | font-size: var(--font-size-normal); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /components/menu/server.css: -------------------------------------------------------------------------------- 1 | /* Menu */ 2 | 3 | @import url("base.css"); 4 | @import url("mobile.css"); 5 | @import url("desktop.css"); 6 | -------------------------------------------------------------------------------- /components/modal/element.css: -------------------------------------------------------------------------------- 1 | dialog { 2 | padding: 0; 3 | border: 1px solid var(--color-border-primary); 4 | border-radius: 0.25rem; 5 | } 6 | 7 | header { 8 | display: flex; 9 | padding: 0.5rem; 10 | 11 | h2 { 12 | margin: 0; 13 | } 14 | 15 | mdn-button { 16 | margin-left: auto; 17 | } 18 | } 19 | 20 | slot { 21 | display: block; 22 | padding: 1rem; 23 | padding-top: 0; 24 | } 25 | -------------------------------------------------------------------------------- /components/navigation/base.css: -------------------------------------------------------------------------------- 1 | /* Navigation */ 2 | 3 | .navigation { 4 | background-color: var(--color-background-page); 5 | 6 | &[data-scheme="dark"] { 7 | color: var(--color-text-primary); 8 | color-scheme: dark; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /components/navigation/hook.js: -------------------------------------------------------------------------------- 1 | const button = document.querySelector(`[aria-controls="navigation__popup"]`); 2 | const navigation = document.querySelector(".navigation"); 3 | if (button instanceof HTMLElement && navigation instanceof HTMLElement) { 4 | button.addEventListener("click", () => { 5 | const open = (!(navigation.dataset.open === "true")).toString(); 6 | navigation.dataset.open = open; 7 | button.setAttribute("aria-expanded", open); 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /components/navigation/server.css: -------------------------------------------------------------------------------- 1 | /* Navigation */ 2 | 3 | @import url("base.css"); 4 | @import url("mobile.css"); 5 | @import url("desktop.css"); 6 | -------------------------------------------------------------------------------- /components/not-found/element.css: -------------------------------------------------------------------------------- 1 | @import url("../global/global.css"); 2 | @import url("../notecard/index.css"); 3 | 4 | code { 5 | padding: 0.125em 0.25em; 6 | 7 | font-family: var(--font-family-code); 8 | 9 | background-color: var(--color-background-secondary); 10 | border-radius: 0.25em; 11 | } 12 | -------------------------------------------------------------------------------- /components/not-found/server.css: -------------------------------------------------------------------------------- 1 | .not-found { 2 | display: grid; 3 | grid-template-columns: var(--layout-no-sidebar-extended); 4 | 5 | > * { 6 | grid-column: content; 7 | } 8 | 9 | h1 { 10 | margin-top: 0; 11 | 12 | font-size: var(--font-size-largest); 13 | font-weight: normal; 14 | 15 | line-height: var(--font-line-content); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /components/not-found/server.js: -------------------------------------------------------------------------------- 1 | import { html } from "@lit-labs/ssr"; 2 | 3 | import { PageLayout } from "../page-layout/server.js"; 4 | import { ServerComponent } from "../server/index.js"; 5 | 6 | export class NotFound extends ServerComponent { 7 | /** 8 | * @param {import("@fred").Context} context 9 | */ 10 | render(context) { 11 | return PageLayout.render( 12 | context, 13 | html`
14 |

${context.l10n("not-found-title")}

15 | 16 |

17 | ${context.l10n("not-found-back")} 18 |

19 |
`, 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /components/observatory-header-link/element.css: -------------------------------------------------------------------------------- 1 | @import url("../global/global.css"); 2 | -------------------------------------------------------------------------------- /components/observatory-human-duration/element.css: -------------------------------------------------------------------------------- 1 | @import url("../global/global.css"); 2 | -------------------------------------------------------------------------------- /components/observatory-rescan-button/element.css: -------------------------------------------------------------------------------- 1 | @import url("../global/global.css"); 2 | -------------------------------------------------------------------------------- /components/observatory-results/comparison.js: -------------------------------------------------------------------------------- 1 | import { html } from "lit"; 2 | 3 | /** 4 | * 5 | * @param {{result: import("@observatory").Result}} result 6 | * @returns { import("@lit").TemplateResult } 7 | */ 8 | export function Comparison({ result }) { 9 | return html` 10 |

Performance trends from the past year

11 | 14 |

15 | Refer to this graph to assess the website's current status. By following 16 | the recommendations provided and rescanning, you can expect an improvement 17 | in the website's grade. 18 |

19 | `; 20 | } 21 | -------------------------------------------------------------------------------- /components/observatory-results/print.css: -------------------------------------------------------------------------------- 1 | /* Print styles for Observatory Results */ 2 | @media print { 3 | .observatory-results-wrapper { 4 | grid-template-columns: [full-start] minmax(0, 1fr) [full-end] !important; 5 | background-color: white !important; 6 | } 7 | 8 | .observatory-results { 9 | grid-area: full !important; 10 | } 11 | 12 | /* Hide non-essential elements */ 13 | .observatory-results-toc, 14 | .actions, 15 | .rescan-button, 16 | .page_layout__banner, 17 | .page-layout__header, 18 | .button-wrap { 19 | display: none !important; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /components/observatory/assets/alert-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /components/observatory/assets/fail-icon.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /components/observatory/assets/mdn.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /components/observatory/assets/pass-icon.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /components/observatory/assets/results-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /components/observatory/assets/security.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /components/observatory/assets/stars.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /components/observatory/assets/tooltip-arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /components/observatory/common.css: -------------------------------------------------------------------------------- 1 | /* observatory assets */ 2 | .observatory { 3 | line-height: var(--font-line-content); 4 | color: var(--color-text-primary); 5 | } 6 | -------------------------------------------------------------------------------- /components/page-layout/server.css: -------------------------------------------------------------------------------- 1 | .page-layout { 2 | display: grid; 3 | 4 | grid-template-rows: 5 | min-content 6 | min-content 7 | 1fr 8 | min-content; 9 | grid-template-columns: minmax(0, 1fr); 10 | 11 | min-height: 100svh; 12 | 13 | margin: 0; 14 | margin-inline: auto; 15 | } 16 | 17 | .page-layout__header { 18 | position: sticky; 19 | top: 0; 20 | z-index: var(--z-index-sticky-header); 21 | } 22 | 23 | .page-layout__banner { 24 | height: calc(5.625rem + 1px); 25 | background-color: var(--color-background-primary); 26 | border-bottom: 1px solid var(--color-border-primary); 27 | 28 | &[data-scheme="dark"] { 29 | color-scheme: dark; 30 | } 31 | 32 | @media (--screen-small-and-narrower) { 33 | display: none; 34 | } 35 | } 36 | 37 | :root[data-nop="yes"] { 38 | .page-layout__banner { 39 | display: none; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /components/page-not-created/global.css: -------------------------------------------------------------------------------- 1 | .page-not-created { 2 | color: var(--color-text-red); 3 | text-decoration: underline wavy !important; 4 | } 5 | -------------------------------------------------------------------------------- /components/placement-no/element.css: -------------------------------------------------------------------------------- 1 | :host { 2 | line-height: 1rem; 3 | } 4 | 5 | .placement-no { 6 | width: 100%; 7 | max-width: 12rem; 8 | 9 | padding: 0; 10 | margin-bottom: 0.5rem; 11 | 12 | font-size: 0.6rem; 13 | 14 | color: inherit; 15 | 16 | &:focus, 17 | &:hover { 18 | text-decoration: none; 19 | opacity: unset; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /components/placement-note/element.css: -------------------------------------------------------------------------------- 1 | .placement-note { 2 | display: block; 3 | 4 | width: max-content; 5 | 6 | padding: 0 0.25rem; 7 | margin: 0.25rem; 8 | 9 | font-size: 0.625rem; 10 | 11 | color: #313131; 12 | 13 | text-transform: uppercase; 14 | 15 | text-decoration: underline; 16 | 17 | background-color: #f9f9fbd0; 18 | border: 1px solid #313131; 19 | border-radius: 0.25rem; 20 | 21 | opacity: 0.85; 22 | 23 | &:focus, 24 | &:hover { 25 | text-decoration: none; 26 | opacity: unset; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /components/placement-note/element.js: -------------------------------------------------------------------------------- 1 | import { LitElement, html } from "lit"; 2 | 3 | import { L10nMixin } from "../../l10n/mixin.js"; 4 | 5 | import styles from "./element.css?lit"; 6 | 7 | class MDNPlacementNote extends L10nMixin(LitElement) { 8 | static styles = styles; 9 | 10 | render() { 11 | return html`about`} 15 | target="_blank" 16 | rel="noreferrer" 17 | >${this.l10n("placement-note")}`; 19 | } 20 | } 21 | 22 | customElements.define("mdn-placement-note", MDNPlacementNote); 23 | -------------------------------------------------------------------------------- /components/play-console/element.css: -------------------------------------------------------------------------------- 1 | :host { 2 | box-sizing: border-box; 3 | display: flex; 4 | 5 | flex-direction: column; 6 | 7 | width: 100%; 8 | 9 | margin: 0; 10 | 11 | overflow: auto; 12 | 13 | font-size: var(--font-size-small); 14 | 15 | background-color: var(--color-background-secondary); 16 | } 17 | 18 | ul { 19 | padding: 0; 20 | margin: 0; 21 | list-style: none; 22 | } 23 | 24 | li { 25 | padding: 0 0.5em; 26 | 27 | &::before { 28 | content: "> "; 29 | } 30 | } 31 | 32 | code { 33 | font-family: var(--font-family-code); 34 | tab-size: 4; 35 | } 36 | -------------------------------------------------------------------------------- /components/play-console/types.d.ts: -------------------------------------------------------------------------------- 1 | export interface VConsole { 2 | prop: string; 3 | args: any[]; 4 | } 5 | -------------------------------------------------------------------------------- /components/play-editor/element.css: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | } 4 | 5 | .editor { 6 | height: 100%; 7 | 8 | &.minimal { 9 | display: flex; 10 | flex-direction: column; 11 | justify-content: center; 12 | 13 | .cm-content { 14 | align-self: center; 15 | min-height: auto; 16 | } 17 | 18 | .cm-focused { 19 | outline: none; 20 | } 21 | 22 | .cm-line { 23 | padding: 0 12px; 24 | } 25 | } 26 | 27 | .cm-editor { 28 | width: 100%; 29 | height: 100%; 30 | 31 | * { 32 | /* codemirror uses `font-family: monospace;` in multiple places 33 | which causes weird fonts to be used in various locales */ 34 | font-family: var(--font-family-code) !important; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /components/play-runner/element.css: -------------------------------------------------------------------------------- 1 | iframe { 2 | box-sizing: content-box; 3 | 4 | width: 100%; 5 | height: 100%; 6 | 7 | border: none; 8 | } 9 | -------------------------------------------------------------------------------- /components/play-runner/types.d.ts: -------------------------------------------------------------------------------- 1 | export type RunnerDefaults = "ix-tabbed" | "ix-wat" | "ix-choice"; 2 | -------------------------------------------------------------------------------- /components/playground/server.css: -------------------------------------------------------------------------------- 1 | .playground { 2 | --border: 1px solid var(--color-border-primary); 3 | 4 | display: grid; 5 | 6 | grid-template-columns: 7 | [content-start] 8 | 1fr 9 | [content-end sidebar-start] 10 | min-content 11 | [sidebar-end]; 12 | 13 | height: 100%; 14 | min-height: calc( 15 | 100vh - var(--top-banner-height) - var(--sticky-header-height) 16 | ); 17 | 18 | padding: 1rem; 19 | 20 | mdn-playground { 21 | display: block; 22 | grid-column: content; 23 | height: 100%; 24 | } 25 | 26 | .sidebar { 27 | display: flex; 28 | 29 | grid-column: sidebar; 30 | 31 | justify-content: center; 32 | 33 | min-width: 12rem; 34 | 35 | margin-left: 1rem; 36 | 37 | @media (--screen-small-and-narrower) { 38 | grid-column: content; 39 | } 40 | } 41 | } 42 | 43 | :root[data-nop="yes"] .playground { 44 | .sidebar { 45 | display: none; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /components/playground/server.js: -------------------------------------------------------------------------------- 1 | import { html } from "@lit-labs/ssr"; 2 | 3 | import { PageLayout } from "../page-layout/server.js"; 4 | import { ServerComponent } from "../server/index.js"; 5 | 6 | export class Playground extends ServerComponent { 7 | /** @param {import("@fred").Context} context */ 8 | render(context) { 9 | return PageLayout.render( 10 | context, 11 | html` 12 |
13 | 14 | 17 |
18 | `, 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /components/playground/types.d.ts: -------------------------------------------------------------------------------- 1 | export interface PlaygroundStateParam { 2 | css: string; 3 | html: string; 4 | js: string; 5 | src?: string; 6 | } 7 | 8 | export interface PlaygroundSession { 9 | srcPrefix: string; 10 | code: Record; 11 | initialCode?: Record; 12 | autoRun?: boolean; 13 | } 14 | -------------------------------------------------------------------------------- /components/plus/assets/ai-help/code-examples-playground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/ai-help/code-examples-playground.png -------------------------------------------------------------------------------- /components/plus/assets/ai-help/code-examples-queue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/ai-help/code-examples-queue.png -------------------------------------------------------------------------------- /components/plus/assets/ai-help/example-question-answering.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/ai-help/example-question-answering.png -------------------------------------------------------------------------------- /components/plus/assets/ai-help/example-question-editing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/ai-help/example-question-editing.png -------------------------------------------------------------------------------- /components/plus/assets/ai-help/history-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/ai-help/history-banner.png -------------------------------------------------------------------------------- /components/plus/assets/ai-help/history-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/ai-help/history-settings.png -------------------------------------------------------------------------------- /components/plus/assets/ai-help/issue-template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/ai-help/issue-template.png -------------------------------------------------------------------------------- /components/plus/assets/ai-help/login-signup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/ai-help/login-signup.png -------------------------------------------------------------------------------- /components/plus/assets/ai-help/rate-answers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/ai-help/rate-answers.png -------------------------------------------------------------------------------- /components/plus/assets/ai-help/report-feedback.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/ai-help/report-feedback.png -------------------------------------------------------------------------------- /components/plus/assets/collections/collections-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/collections/collections-dashboard.png -------------------------------------------------------------------------------- /components/plus/assets/collections/desktop-collections-dashboard-delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/collections/desktop-collections-dashboard-delete.png -------------------------------------------------------------------------------- /components/plus/assets/collections/desktop-collections-delete-fva.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/collections/desktop-collections-delete-fva.png -------------------------------------------------------------------------------- /components/plus/assets/collections/desktop-collections-edit-dialog-add-note.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/collections/desktop-collections-edit-dialog-add-note.png -------------------------------------------------------------------------------- /components/plus/assets/collections/desktop-collections-edit-dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/collections/desktop-collections-edit-dialog.png -------------------------------------------------------------------------------- /components/plus/assets/collections/desktop-collections-edit-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/collections/desktop-collections-edit-menu.png -------------------------------------------------------------------------------- /components/plus/assets/collections/desktop-collections-filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/collections/desktop-collections-filter.png -------------------------------------------------------------------------------- /components/plus/assets/collections/desktop-collections-fva.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/collections/desktop-collections-fva.png -------------------------------------------------------------------------------- /components/plus/assets/collections/desktop-collections-sort.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/collections/desktop-collections-sort.png -------------------------------------------------------------------------------- /components/plus/assets/collections/desktop-collections-three-dot-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/collections/desktop-collections-three-dot-menu.png -------------------------------------------------------------------------------- /components/plus/assets/collections/desktop-collections-undo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/collections/desktop-collections-undo.png -------------------------------------------------------------------------------- /components/plus/assets/collections/desktop-collections-user-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/collections/desktop-collections-user-menu.png -------------------------------------------------------------------------------- /components/plus/assets/collections/desktop-page-add-note.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/collections/desktop-page-add-note.png -------------------------------------------------------------------------------- /components/plus/assets/collections/desktop-page-dialog-save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/collections/desktop-page-dialog-save.png -------------------------------------------------------------------------------- /components/plus/assets/collections/desktop-page-open-dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/collections/desktop-page-open-dialog.png -------------------------------------------------------------------------------- /components/plus/assets/collections/desktop-remove-saved-delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/collections/desktop-remove-saved-delete.png -------------------------------------------------------------------------------- /components/plus/assets/collections/desktop-remove-saved.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/collections/desktop-remove-saved.png -------------------------------------------------------------------------------- /components/plus/assets/collections/desktop-saving-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/collections/desktop-saving-page.png -------------------------------------------------------------------------------- /components/plus/assets/collections/mobile-add-note.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/collections/mobile-add-note.png -------------------------------------------------------------------------------- /components/plus/assets/collections/mobile-burger-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/collections/mobile-burger-menu.png -------------------------------------------------------------------------------- /components/plus/assets/collections/mobile-collections-dashboard-delete-entry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/collections/mobile-collections-dashboard-delete-entry.png -------------------------------------------------------------------------------- /components/plus/assets/collections/mobile-collections-dashboard-edit-entry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/collections/mobile-collections-dashboard-edit-entry.png -------------------------------------------------------------------------------- /components/plus/assets/collections/mobile-collections-dashboard-edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/collections/mobile-collections-dashboard-edit.png -------------------------------------------------------------------------------- /components/plus/assets/collections/mobile-collections-dashboard-three-dots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/collections/mobile-collections-dashboard-three-dots.png -------------------------------------------------------------------------------- /components/plus/assets/collections/mobile-collections-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/collections/mobile-collections-dashboard.png -------------------------------------------------------------------------------- /components/plus/assets/collections/mobile-collections-delete-entry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/collections/mobile-collections-delete-entry.png -------------------------------------------------------------------------------- /components/plus/assets/collections/mobile-collections-fva-undo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/collections/mobile-collections-fva-undo.png -------------------------------------------------------------------------------- /components/plus/assets/collections/mobile-collections-menu-item.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/collections/mobile-collections-menu-item.png -------------------------------------------------------------------------------- /components/plus/assets/collections/mobile-collections-undo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/collections/mobile-collections-undo.png -------------------------------------------------------------------------------- /components/plus/assets/collections/mobile-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/collections/mobile-menu.png -------------------------------------------------------------------------------- /components/plus/assets/collections/mobile-open-article-actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/collections/mobile-open-article-actions.png -------------------------------------------------------------------------------- /components/plus/assets/collections/mobile-plus-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/collections/mobile-plus-menu.png -------------------------------------------------------------------------------- /components/plus/assets/collections/mobile-save-page-step-one.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/collections/mobile-save-page-step-one.png -------------------------------------------------------------------------------- /components/plus/assets/collections/mobile-save-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/collections/mobile-save-page.png -------------------------------------------------------------------------------- /components/plus/assets/collections/mobile-saved-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/collections/mobile-saved-page.png -------------------------------------------------------------------------------- /components/plus/assets/notifications/access-notifications-from-main-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/notifications/access-notifications-from-main-menu.png -------------------------------------------------------------------------------- /components/plus/assets/notifications/bulk-unwatch-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/notifications/bulk-unwatch-dashboard.png -------------------------------------------------------------------------------- /components/plus/assets/notifications/mobile-notifications-user-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/notifications/mobile-notifications-user-menu.png -------------------------------------------------------------------------------- /components/plus/assets/notifications/notifications-user-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/notifications/notifications-user-menu.png -------------------------------------------------------------------------------- /components/plus/assets/notifications/star-notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/notifications/star-notification.png -------------------------------------------------------------------------------- /components/plus/assets/notifications/unwatch-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/notifications/unwatch-dashboard.png -------------------------------------------------------------------------------- /components/plus/assets/notifications/unwatch-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/notifications/unwatch-page.png -------------------------------------------------------------------------------- /components/plus/assets/notifications/watch-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/notifications/watch-list.png -------------------------------------------------------------------------------- /components/plus/assets/notifications/watch-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/notifications/watch-page.png -------------------------------------------------------------------------------- /components/plus/assets/offline/desktop-offline-clear-data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/offline/desktop-offline-clear-data.png -------------------------------------------------------------------------------- /components/plus/assets/offline/desktop-offline-enable-auto-update.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/offline/desktop-offline-enable-auto-update.png -------------------------------------------------------------------------------- /components/plus/assets/offline/desktop-offline-enable-offline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/offline/desktop-offline-enable-offline.png -------------------------------------------------------------------------------- /components/plus/assets/offline/desktop-offline-manual-update.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/offline/desktop-offline-manual-update.png -------------------------------------------------------------------------------- /components/plus/assets/offline/desktop-offline-user-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/offline/desktop-offline-user-menu.png -------------------------------------------------------------------------------- /components/plus/assets/playground/playground-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/playground/playground-example.png -------------------------------------------------------------------------------- /components/plus/assets/playground/playground-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/playground/playground-menu.png -------------------------------------------------------------------------------- /components/plus/assets/playground/playground-sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/playground/playground-sample.png -------------------------------------------------------------------------------- /components/plus/assets/updates/collections.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/updates/collections.png -------------------------------------------------------------------------------- /components/plus/assets/updates/updates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/updates/updates.png -------------------------------------------------------------------------------- /components/plus/assets/updates/updates_bcd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/updates/updates_bcd.png -------------------------------------------------------------------------------- /components/plus/assets/updates/updates_collection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/updates/updates_collection.png -------------------------------------------------------------------------------- /components/plus/assets/updates/updates_filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/updates/updates_filter.png -------------------------------------------------------------------------------- /components/plus/assets/updates/updates_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/plus/assets/updates/updates_search.png -------------------------------------------------------------------------------- /components/plus/server.js: -------------------------------------------------------------------------------- 1 | import { html } from "@lit-labs/ssr"; 2 | 3 | import { PageLayout } from "../page-layout/server.js"; 4 | import { ServerComponent } from "../server/index.js"; 5 | 6 | export class Plus extends ServerComponent { 7 | static legacy = true; 8 | 9 | /** 10 | * @param {import("@fred").Context} context 11 | */ 12 | render(context) { 13 | return PageLayout.render(context, html`
`); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /components/preferred-locale/utils.js: -------------------------------------------------------------------------------- 1 | import { 2 | deleteCookie, 3 | getCookieValue, 4 | setCookieValue, 5 | } from "../cookie/utils.js"; 6 | 7 | const COOKIE_NAME = "preferredlocale"; 8 | const COOKIE_MAX_AGE = 60 * 60 * 24 * 365 * 3; // 3 years. 9 | 10 | /** @returns {string|undefined} */ 11 | export function getPreferredLocale() { 12 | return getCookieValue(COOKIE_NAME); 13 | } 14 | 15 | /** @param {string} locale */ 16 | export function setPreferredLocale(locale) { 17 | setCookieValue(COOKIE_NAME, locale, { 18 | maxAge: COOKIE_MAX_AGE, 19 | }); 20 | } 21 | 22 | export function resetPreferredLocale() { 23 | deleteCookie(COOKIE_NAME); 24 | } 25 | -------------------------------------------------------------------------------- /components/progress-bar/element.js: -------------------------------------------------------------------------------- 1 | import { LitElement, html } from "lit"; 2 | 3 | import styles from "./index.css?lit"; 4 | 5 | /** 6 | * This is an indefinite progress bar that can be used to indicate that a process is ongoing. It is used in Observatory. 7 | * CSS variables: 8 | * --progress-color: The color of the progress bar. Default is #aaa 9 | * --border-radius: The border radius of the progress bar. Default is 0 10 | */ 11 | export class MDNProgressBar extends LitElement { 12 | static styles = styles; 13 | 14 | render() { 15 | return html`
`; 16 | } 17 | } 18 | 19 | customElements.define("mdn-progress-bar", MDNProgressBar); 20 | -------------------------------------------------------------------------------- /components/progress-bar/index.css: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | } 4 | 5 | .progress-bar { 6 | position: relative; 7 | 8 | width: 100%; 9 | height: 20px; 10 | 11 | overflow: hidden; 12 | 13 | background-color: var(--background-color, #eeeeee); 14 | border-radius: var(--border-radius, 0); 15 | } 16 | 17 | .progress-bar::before { 18 | position: absolute; 19 | top: 0; 20 | left: -200%; 21 | 22 | width: 200%; 23 | height: 100%; 24 | 25 | content: ""; 26 | 27 | background: linear-gradient( 28 | to right, 29 | transparent 0%, 30 | var(--progress-color, #aaaaaa) 50%, 31 | transparent 100% 32 | ); 33 | 34 | animation: scan 2s infinite alternate ease-in-out; 35 | } 36 | 37 | @keyframes scan { 38 | from { 39 | left: -200%; 40 | } 41 | 42 | to { 43 | left: 100%; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /components/radius/global.css: -------------------------------------------------------------------------------- 1 | /* Radius */ 2 | 3 | :root { 4 | --radius-normal: 0.25rem; 5 | --radius-full: 9999px; 6 | } 7 | -------------------------------------------------------------------------------- /components/recently-visited/element.css: -------------------------------------------------------------------------------- 1 | @import url("../global/global.css"); 2 | 3 | h2 { 4 | font-size: var(--font-size-large); 5 | font-weight: 500; 6 | } 7 | 8 | ul { 9 | padding: 0; 10 | 11 | overflow: hidden; 12 | 13 | list-style: none; 14 | 15 | border: 1px solid var(--color-border-primary); 16 | border-radius: 0.5rem; 17 | } 18 | 19 | li { 20 | border-top: 1px solid var(--color-border-primary); 21 | 22 | &:first-child { 23 | border-top: none; 24 | } 25 | } 26 | 27 | a { 28 | display: block; 29 | padding: 1rem; 30 | color: var(--color-text-primary); 31 | 32 | &:not(:hover) { 33 | text-decoration: none; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /components/recently-visited/element.js: -------------------------------------------------------------------------------- 1 | import { LitElement, html } from "lit"; 2 | 3 | import { L10nMixin } from "../../l10n/mixin.js"; 4 | 5 | import styles from "./element.css?lit"; 6 | 7 | import { RecentlyVisitedPages } from "./index.js"; 8 | 9 | export class MDNRecentlyVisited extends L10nMixin(LitElement) { 10 | static ssr = false; 11 | static styles = styles; 12 | 13 | constructor() { 14 | super(); 15 | this._pages = new RecentlyVisitedPages(); 16 | } 17 | 18 | render() { 19 | return html`

${this.l10n`Recently visited`}

20 |
    21 | ${this._pages.map( 22 | ({ path, title }) => html`
  • ${title}
  • `, 23 | )} 24 |
`; 25 | } 26 | } 27 | 28 | customElements.define("mdn-recently-visited", MDNRecentlyVisited); 29 | -------------------------------------------------------------------------------- /components/record-visit/element.js: -------------------------------------------------------------------------------- 1 | import { LitElement } from "lit"; 2 | 3 | import { 4 | RecentlyVisitedPage, 5 | RecentlyVisitedPages, 6 | } from "../recently-visited/index.js"; 7 | 8 | export class MDNRecordVisit extends LitElement { 9 | static ssr = false; 10 | static properties = { 11 | pageTitle: { type: String, attribute: "page-title" }, 12 | }; 13 | 14 | constructor() { 15 | super(); 16 | /** @type {string | undefined} */ 17 | this.pageTitle; 18 | } 19 | 20 | connectedCallback() { 21 | super.connectedCallback(); 22 | if (this.pageTitle) { 23 | const visited = new RecentlyVisitedPages(); 24 | visited.add( 25 | new RecentlyVisitedPage({ 26 | title: this.pageTitle, 27 | path: location.pathname, 28 | }), 29 | ); 30 | } 31 | } 32 | } 33 | 34 | customElements.define("mdn-record-visit", MDNRecordVisit); 35 | -------------------------------------------------------------------------------- /components/reference-toc/server.js: -------------------------------------------------------------------------------- 1 | import { html } from "@lit-labs/ssr"; 2 | import { nothing } from "lit"; 3 | 4 | import { unsafeHTML } from "lit/directives/unsafe-html.js"; 5 | 6 | import { ServerComponent } from "../server/index.js"; 7 | 8 | export class ReferenceToc extends ServerComponent { 9 | /** 10 | * @param {import("@fred").Context} context 11 | */ 12 | render(context) { 13 | const toc = context?.doc?.toc; 14 | 15 | if (!toc || toc.length === 0) { 16 | return nothing; 17 | } 18 | 19 | return html``; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /components/sandbox/class.js: -------------------------------------------------------------------------------- 1 | export class SandboxComponent { 2 | /** 3 | * @template {typeof SandboxComponent} T 4 | * @this {T} 5 | * @returns {ReturnType["render"]>} 6 | */ 7 | static render() { 8 | return new this().render(); 9 | } 10 | 11 | /** 12 | * @abstract 13 | * @returns {any} 14 | */ 15 | render() { 16 | throw new Error("Must be implemented by subclass"); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /components/sandbox/server.css: -------------------------------------------------------------------------------- 1 | .sandbox { 2 | display: grid; 3 | 4 | grid-template-rows: 100vh; 5 | grid-template-columns: 200px 1fr; 6 | 7 | margin: 0; 8 | 9 | > * { 10 | overflow: auto; 11 | } 12 | } 13 | 14 | .sandbox__sidebar { 15 | mdn-color-theme { 16 | display: block; 17 | margin: 1rem; 18 | } 19 | } 20 | 21 | .sandbox__section { 22 | display: none; 23 | 24 | &:target { 25 | display: block; 26 | } 27 | } 28 | 29 | .sandbox__list { 30 | padding: 0; 31 | 32 | > li { 33 | margin-block: 1rem; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /components/scrim-inline/assets/BarlowCondensed-SemiBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/scrim-inline/assets/BarlowCondensed-SemiBold.woff2 -------------------------------------------------------------------------------- /components/scrim-inline/assets/landing-scrim.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/scrim-inline/assets/landing-scrim.png -------------------------------------------------------------------------------- /components/scrim-inline/assets/scrim-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/components/scrim-inline/assets/scrim-bg.png -------------------------------------------------------------------------------- /components/scrim-inline/assets/scrim-hexagons.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/scrim-inline/assets/scrim-play.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/scrim-inline/global.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "BarlowCondensed-SemiBold"; 3 | font-weight: var(--font-weight-bold); 4 | 5 | src: 6 | local("BarlowCondensed-SemiBold"), 7 | url("./assets/BarlowCondensed-SemiBold.woff2") format("woff2"); 8 | font-display: block; 9 | } 10 | -------------------------------------------------------------------------------- /components/search-button/element.css: -------------------------------------------------------------------------------- 1 | .mdn-search-button { 2 | display: flex; 3 | 4 | align-items: center; 5 | justify-content: space-between; 6 | 7 | width: 5rem; 8 | 9 | padding: 0.25rem; 10 | padding-left: 0.75rem; 11 | margin: 0; 12 | 13 | color: var(--color-text-primary); 14 | 15 | cursor: pointer; 16 | 17 | background-color: var(--color-background-page); 18 | border: 1px solid var(--color-border-primary); 19 | border-radius: var(--radius-full); 20 | 21 | &:hover { 22 | background-color: var(--color-background-secondary); 23 | } 24 | 25 | &::before { 26 | width: 15px; 27 | height: 18px; 28 | 29 | content: ""; 30 | 31 | border-bottom: 2px solid 32 | light-dark(var(--color-blue-50), var(--color-blue-80)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /components/search-modal/types.d.ts: -------------------------------------------------------------------------------- 1 | export interface SearchIndexItem { 2 | url: string; 3 | title: string; 4 | } 5 | 6 | export interface SearchIndexFlexItem { 7 | index: number; 8 | title: string; 9 | slugTail: string; 10 | } 11 | 12 | export interface SearchIndex { 13 | flex: SearchIndexFlexItem[]; 14 | items: Item[]; 15 | } 16 | 17 | export type SearchResultItem = { 18 | title: string; 19 | url: string; 20 | }; 21 | -------------------------------------------------------------------------------- /components/server/async-local-storage.js: -------------------------------------------------------------------------------- 1 | import { AsyncLocalStorage } from "node:async_hooks"; 2 | 3 | /** @type {AsyncLocalStorage} */ 4 | export const asyncLocalStorage = new AsyncLocalStorage(); 5 | -------------------------------------------------------------------------------- /components/server/types.d.ts: -------------------------------------------------------------------------------- 1 | export interface AsyncLocalStorageContents { 2 | componentsUsed: Set; 3 | componentsWithStylesInHead: Set; 4 | compilationStats: import("@fred").CompilationStats; 5 | } 6 | -------------------------------------------------------------------------------- /components/sidebar-filter/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Used by quicksearch and sidebar filters. 3 | * @param {string} term 4 | * @returns {string[]} 5 | */ 6 | export function splitQuery(term) { 7 | term = term.trim().toLowerCase(); 8 | 9 | return term.startsWith(".") || term.endsWith(".") 10 | ? // Dot is probably meaningful. 11 | term.split(/[ ,]+/) 12 | : // Dot is probably just a word separator. 13 | term.split(/[ ,.]+/); 14 | } 15 | -------------------------------------------------------------------------------- /components/site-search/server.css: -------------------------------------------------------------------------------- 1 | .site-search { 2 | display: grid; 3 | grid-template-columns: var(--layout-1-sidebar-right); 4 | padding-inline: var(--layout-side-padding); 5 | 6 | @media (--screen-layout-no-sidebar) { 7 | grid-template-columns: var(--layout-no-sidebar); 8 | } 9 | } 10 | 11 | mdn-site-search { 12 | grid-column: content; 13 | } 14 | -------------------------------------------------------------------------------- /components/site-search/server.js: -------------------------------------------------------------------------------- 1 | import { html } from "@lit-labs/ssr"; 2 | 3 | import { PageLayout } from "../page-layout/server.js"; 4 | import { ServerComponent } from "../server/index.js"; 5 | 6 | export class SiteSearch extends ServerComponent { 7 | /** 8 | * @param {import("@fred").Context} context 9 | */ 10 | render(context) { 11 | return PageLayout.render( 12 | context, 13 | html``, 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /components/site-search/types.d.ts: -------------------------------------------------------------------------------- 1 | export interface SearchResponse { 2 | metadata: SearchMetadata; 3 | documents: SearchDocument[]; 4 | suggestions: SearchSuggestion[]; 5 | } 6 | 7 | export type SearchHighlight = { 8 | body?: string[]; 9 | title?: string[]; 10 | }; 11 | 12 | export interface SearchDocument { 13 | mdn_url: string; 14 | locale: string; 15 | title: string; 16 | highlight: SearchHighlight; 17 | summary: string; 18 | score: number; 19 | popularity: number; 20 | } 21 | 22 | export type SearchTotal = { 23 | value: number; 24 | relation: "eq" | "gt"; 25 | }; 26 | 27 | export interface SearchMetadata { 28 | took_ms: number; 29 | total: SearchTotal; 30 | page: number; 31 | size: 10; 32 | } 33 | 34 | export interface SearchSuggestion { 35 | text: string; 36 | total: SearchTotal; 37 | } 38 | -------------------------------------------------------------------------------- /components/survey/README.md: -------------------------------------------------------------------------------- 1 | # Surveys 2 | 3 | You can force show a survey by appending `?force_survey` to the URL of any doc page. 4 | 5 | If multiple surveys are configured, this will only show the first in the array: you can force a particular survey by appending `?force_survey=SURVEY_KEY` to the URL. 6 | -------------------------------------------------------------------------------- /components/table-container/README.md: -------------------------------------------------------------------------------- 1 | # Table container 2 | 3 | Adds horizontal scrolling to tables that dont fit the content section. 4 | 5 | ```html 6 |
7 | 8 | … 9 |
10 |
11 | ``` 12 | -------------------------------------------------------------------------------- /components/table-container/global.css: -------------------------------------------------------------------------------- 1 | .table-container { 2 | margin: 0; 3 | overflow-x: auto; 4 | } 5 | -------------------------------------------------------------------------------- /components/themed-image/element.css: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | 4 | picture { 5 | display: block; 6 | max-width: 100%; 7 | } 8 | 9 | img { 10 | width: 100%; 11 | height: auto; 12 | } 13 | 14 | picture { 15 | justify-self: end; 16 | width: 100%; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /components/toggle-sidebar/element.css: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | height: 100%; 4 | border-right: 1px solid var(--color-border-primary); 5 | } 6 | 7 | mdn-button { 8 | height: 100%; 9 | } 10 | -------------------------------------------------------------------------------- /components/translation-banner/server.css: -------------------------------------------------------------------------------- 1 | .translation-banner__switch { 2 | text-align: right; 3 | } 4 | -------------------------------------------------------------------------------- /components/user-menu/base.css: -------------------------------------------------------------------------------- 1 | /* Base */ 2 | 3 | /* User menu dropdown */ 4 | 5 | .user-menu__button { 6 | padding: 0; 7 | margin: 0; 8 | 9 | cursor: pointer; 10 | 11 | background-color: var(--color-background-primary); 12 | 13 | img { 14 | display: block; 15 | 16 | width: 2rem; 17 | height: 2rem; 18 | 19 | border-radius: 50%; 20 | } 21 | } 22 | 23 | .user-menu__dropdown { 24 | display: grid; 25 | row-gap: 1rem; 26 | 27 | p { 28 | margin: 0; 29 | } 30 | 31 | ul { 32 | display: grid; 33 | 34 | row-gap: 0.25rem; 35 | 36 | padding: 0; 37 | margin: 0; 38 | 39 | list-style: none; 40 | } 41 | 42 | a { 43 | color: var(--color-text-primary); 44 | 45 | &:hover { 46 | color: var(--color-text-secondary); 47 | } 48 | } 49 | 50 | form { 51 | display: contents; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /components/user-menu/desktop.css: -------------------------------------------------------------------------------- 1 | /* Desktop */ 2 | 3 | @media (--screen-menu-full) { 4 | .user-menu { 5 | position: relative; 6 | } 7 | 8 | .user-menu__login { 9 | width: 100%; 10 | height: 100%; 11 | 12 | &::part(label) { 13 | position: absolute; 14 | 15 | width: 0; 16 | height: 0; 17 | 18 | overflow: hidden; 19 | } 20 | } 21 | 22 | /* User menu dropdown */ 23 | 24 | .user-menu__button { 25 | border: 1px solid var(--color-border-primary); 26 | border-radius: 50%; 27 | } 28 | 29 | .user-menu__dropdown { 30 | position: absolute; 31 | right: 0; 32 | z-index: 1; 33 | 34 | padding: 0.75rem; 35 | margin: 0; 36 | 37 | background-color: var(--color-background-primary); 38 | border: 1px solid var(--color-border-primary); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /components/user-menu/element.css: -------------------------------------------------------------------------------- 1 | @import url("../global/global.css"); 2 | @import url("../button/server.css"); 3 | @import url("../external-link/global.css"); 4 | @import url("base.css"); 5 | @import url("mobile.css"); 6 | @import url("desktop.css"); 7 | -------------------------------------------------------------------------------- /components/utils/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Used to generate a random element id by combining a prefix with a random string. 3 | * 4 | * @param {string} prefix 5 | * @returns {string} 6 | */ 7 | export function randomIdString(prefix = "id-") { 8 | return Math.random().toString(36).replace("0.", prefix); 9 | } 10 | -------------------------------------------------------------------------------- /components/vars/global.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --top-banner-height: 3.375rem; 3 | --sticky-header-height: calc( 4 | var(--navigation-height) + var(--breadcrumbs-bar-height) 5 | ); 6 | 7 | --z-index-sticky-header: 100; 8 | --z-index-sidebar-mobile: 90; 9 | } 10 | -------------------------------------------------------------------------------- /components/visually-hidden/global.css: -------------------------------------------------------------------------------- 1 | .visually-hidden { 2 | position: absolute !important; 3 | 4 | width: 1px !important; 5 | height: 1px !important; 6 | 7 | padding: 0 !important; 8 | margin: -1px !important; 9 | 10 | overflow: hidden !important; 11 | 12 | white-space: nowrap !important; 13 | 14 | border: 0 !important; 15 | 16 | clip-path: inset(50%) !important; 17 | } 18 | -------------------------------------------------------------------------------- /components/writer-open-editor/element.js: -------------------------------------------------------------------------------- 1 | import { LitElement, html } from "lit"; 2 | 3 | import { L10nMixin } from "../../l10n/mixin.js"; 4 | 5 | import "../button/element.js"; 6 | 7 | export class MDNWriterOpenEditor extends L10nMixin(LitElement) { 8 | static properties = { 9 | filepath: { type: String }, 10 | }; 11 | 12 | constructor() { 13 | super(); 14 | this.filepath = ""; 15 | } 16 | 17 | async _open() { 18 | const params = new URLSearchParams({ 19 | filepath: this.filepath, 20 | }); 21 | await fetch(`/_open?${params}`); 22 | } 23 | 24 | render() { 25 | return html` 26 | ${this.l10n`Open in editor`} 27 | `; 28 | } 29 | } 30 | 31 | customElements.define("mdn-writer-open-editor", MDNWriterOpenEditor); 32 | -------------------------------------------------------------------------------- /components/writer-reload/element.css: -------------------------------------------------------------------------------- 1 | div { 2 | display: flex; 3 | 4 | align-items: center; 5 | 6 | height: 100%; 7 | 8 | padding-inline: 0.5em; 9 | } 10 | -------------------------------------------------------------------------------- /components/writer-toolbar/server.css: -------------------------------------------------------------------------------- 1 | .writer-toolbar { 2 | display: flex; 3 | 4 | flex-wrap: wrap; 5 | 6 | margin-top: 1.5rem; 7 | 8 | background: var(--color-background-primary); 9 | border: 1px solid var(--color-border-primary); 10 | border-radius: 0.25rem; 11 | 12 | mdn-writer-reload { 13 | margin-left: auto; 14 | font-size: 0.875em; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /entry.client.js: -------------------------------------------------------------------------------- 1 | import "./symmetric-context/client.js"; 2 | import "@lit-labs/ssr-client/lit-element-hydrate-support.js"; 3 | 4 | // hooks: 5 | import "./hooks/glean-init.js"; 6 | import "./hooks/ga-init.js"; 7 | import "./hooks/dialog-closedby.js"; 8 | import "./hooks/load-elements.js"; 9 | import "./components/baseline-indicator/hook.js"; 10 | import "./hooks/sidebar-scroll-to-current.js"; 11 | import "./components/navigation/hook.js"; 12 | import "./hooks/toc-highlight.js"; 13 | import "./hooks/code-examples.js"; 14 | import "./hooks/live-samples.js"; 15 | import "./hooks/skip-to-search.js"; 16 | import "./hooks/user-ping.js"; 17 | -------------------------------------------------------------------------------- /entry.inline.js: -------------------------------------------------------------------------------- 1 | try { 2 | document.documentElement.dataset.theme = 3 | localStorage.getItem("theme") || "light dark"; 4 | } catch (error) { 5 | console.warn("Unable to set theme", error); 6 | } 7 | 8 | try { 9 | if (localStorage.getItem("nop") === "yes") { 10 | document.documentElement.dataset["nop"] = "yes"; 11 | } 12 | } catch (error) { 13 | console.warn("Unable to set nop", error); 14 | } 15 | -------------------------------------------------------------------------------- /hooks/code-examples.js: -------------------------------------------------------------------------------- 1 | for (const pre of document.querySelectorAll( 2 | `div.code-example pre:not(.hidden):not([class*="live-sample"]):not([class*="interactive-example"])`, 3 | )) { 4 | const { upgradePre } = await import("../components/code-example/element.js"); 5 | upgradePre(pre); 6 | } 7 | -------------------------------------------------------------------------------- /hooks/ga-init.d.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | var dataLayer: any[]; 3 | } 4 | 5 | export {}; 6 | -------------------------------------------------------------------------------- /hooks/legacy-theme-controller.js: -------------------------------------------------------------------------------- 1 | globalThis.addEventListener("mdn-color-theme-update", (event) => { 2 | if (event instanceof CustomEvent && event.detail) { 3 | // legacy react compatibility: 4 | document.documentElement.classList.toggle("light", event.detail == "light"); 5 | document.documentElement.classList.toggle("dark", event.detail == "dark"); 6 | } 7 | }); 8 | -------------------------------------------------------------------------------- /hooks/load-elements.js: -------------------------------------------------------------------------------- 1 | const loaded = new Set(); 2 | for (const element of document.querySelectorAll("*")) { 3 | const tag = element.tagName.toLowerCase(); 4 | if (tag.startsWith("mdn-") || tag === "interactive-example") { 5 | const component = tag.replace("mdn-", ""); 6 | if (!loaded.has(component)) { 7 | loaded.add(component); 8 | // we don't want to await, because we want these to load in parallel 9 | // eslint-disable-next-line unicorn/prefer-top-level-await 10 | import(`../components/${component}/element.js`).catch((error) => { 11 | console.error( 12 | `couldn't load code for <${component}>: does the element's code not match the naming schema?`, 13 | error, 14 | ); 15 | }); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /hooks/sidebar-scroll-to-current.js: -------------------------------------------------------------------------------- 1 | const sidebar = document.querySelector(".left-sidebar"); 2 | const current = sidebar?.querySelector('[aria-current="page"]'); 3 | if (sidebar && current instanceof HTMLElement) { 4 | sidebar.scrollTo({ 5 | top: current.offsetTop - window.innerHeight / 4, 6 | }); 7 | } 8 | -------------------------------------------------------------------------------- /hooks/skip-to-search.js: -------------------------------------------------------------------------------- 1 | import { MDNSearchModal } from "../components/search-modal/element.js"; 2 | 3 | const skipToSearch = document.querySelector(`.a11y-menu a[href="#search"]`); 4 | 5 | if (skipToSearch instanceof HTMLAnchorElement) { 6 | const search = document.querySelector("#search"); 7 | 8 | if (search instanceof MDNSearchModal) { 9 | skipToSearch.addEventListener("click", (event) => { 10 | const { target } = event; 11 | 12 | if (target instanceof HTMLElement) { 13 | target.blur(); 14 | event.preventDefault(); 15 | } 16 | 17 | search.showModal(); 18 | }); 19 | } else { 20 | console.error("MDNSearchModal not found!"); 21 | skipToSearch.hidden = true; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /hooks/user-ping.js: -------------------------------------------------------------------------------- 1 | import { globalUser } from "../components/user/context.js"; 2 | 3 | try { 4 | const user = await globalUser(); 5 | const nextPing = new Date(localStorage.getItem("next-ping") || 0); 6 | if (navigator.sendBeacon && user.isAuthenticated && nextPing < new Date()) { 7 | const params = new URLSearchParams(); 8 | 9 | navigator.sendBeacon("/api/v1/ping", params); 10 | 11 | const newNextPing = new Date(); 12 | newNextPing.setUTCDate(newNextPing.getUTCDate() + 1); 13 | newNextPing.setUTCHours(0); 14 | newNextPing.setUTCMinutes(0); 15 | newNextPing.setUTCSeconds(0); 16 | newNextPing.setUTCMilliseconds(0); 17 | localStorage.setItem("next-ping", newNextPing.toISOString()); 18 | } 19 | } catch (error) { 20 | console.error("Failed to send ping", error); 21 | } 22 | -------------------------------------------------------------------------------- /l10n/context.js: -------------------------------------------------------------------------------- 1 | import getFluentContext from "./fluent.js"; 2 | 3 | /** 4 | * @param {string} locale 5 | * @returns {Promise} 6 | */ 7 | export async function addFluent(locale) { 8 | return { 9 | locale: locale, 10 | l10n: getFluentContext(locale), 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /l10n/es.ftl: -------------------------------------------------------------------------------- 1 | blog-toc-title = En este artículo 2 | 3 | -------------------------------------------------------------------------------- /l10n/ja.ftl: -------------------------------------------------------------------------------- 1 | blog-toc-title = この記事では 2 | 3 | -------------------------------------------------------------------------------- /l10n/ko.ftl: -------------------------------------------------------------------------------- 1 | blog-toc-title = 목차 2 | -------------------------------------------------------------------------------- /l10n/mixin.js: -------------------------------------------------------------------------------- 1 | import { getSymmetricContext } from "../symmetric-context/both.js"; 2 | 3 | import getFluentContext from "./fluent.js"; 4 | 5 | /** 6 | * @import { LitElement } from "lit"; 7 | */ 8 | 9 | /** 10 | * @template {new (...args: any[]) => LitElement} TBase 11 | * @param {TBase} Base 12 | */ 13 | export const L10nMixin = (Base) => 14 | class LocalizedElement extends Base { 15 | /** 16 | * @param {...any} args 17 | */ 18 | constructor(...args) { 19 | super(...args); 20 | const context = getSymmetricContext(); 21 | this.locale = context.locale; 22 | this.l10n = getFluentContext(this.locale); 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /l10n/pt-BR.ftl: -------------------------------------------------------------------------------- 1 | blog-toc-title = Neste artigo 2 | 3 | -------------------------------------------------------------------------------- /l10n/ru.ftl: -------------------------------------------------------------------------------- 1 | blog-toc-title = В этой статье 2 | -------------------------------------------------------------------------------- /l10n/zh-CN.ftl: -------------------------------------------------------------------------------- 1 | blog-toc-title = 在本文中 2 | -------------------------------------------------------------------------------- /l10n/zh-TW.ftl: -------------------------------------------------------------------------------- 1 | blog-toc-title = 在本文中 2 | -------------------------------------------------------------------------------- /legacy/legacy.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --top-nav-height: var(--sticky-header-height); 3 | --sticky-header-without-actions-height: var(--sticky-header-height); 4 | --z-index-mid: calc(var(--z-index-sticky-header) - 1); 5 | } 6 | -------------------------------------------------------------------------------- /legacy/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "checkJs": true, 5 | "noEmit": true, 6 | "jsx": "react-jsx", 7 | "lib": ["dom", "dom.iterable", "es2021"], 8 | "module": "ESNext", 9 | "moduleResolution": "bundler", 10 | "target": "ES2020" 11 | }, 12 | "include": ["**/*"] 13 | } 14 | -------------------------------------------------------------------------------- /public/apple-touch-icon.528534bba673c38049c2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/public/apple-touch-icon.528534bba673c38049c2.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/contribute.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Fred", 3 | "description": "MDN's fr(ont)e(n)d", 4 | "repository": { 5 | "url": "https://github.com/mdn/fred", 6 | "license": "MPL-2.0", 7 | "tests": "https://github.com/mdn/fred/actions" 8 | }, 9 | "participate": { 10 | "home": "https://developer.mozilla.org/en-US/community", 11 | "docs": "https://github.com/mdn/fred/blob/main/README.md", 12 | "chat": { 13 | "url": "https://mdn.dev/discord" 14 | } 15 | }, 16 | "bugs": { 17 | "list": "https://github.com/mdn/fred/issues", 18 | "report": "https://github.com/mdn/fred/issues/new" 19 | }, 20 | "urls": { 21 | "prod": "https://developer.mozilla.org/", 22 | "stage": "https://developer.allizom.org/" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/public/favicon-192x192.png -------------------------------------------------------------------------------- /public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/public/favicon-32x32.png -------------------------------------------------------------------------------- /public/favicon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/public/favicon-48x48.png -------------------------------------------------------------------------------- /public/favicon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/public/favicon-512x512.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/public/favicon.ico -------------------------------------------------------------------------------- /public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | MDN Logo 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /public/manifest.f42880861b394dd4dc9b.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "MDN", 3 | "name": "MDN Web Docs", 4 | "icons": [ 5 | { 6 | "src": "/favicon-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/favicon-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "start_url": ".", 17 | "display": "standalone", 18 | "theme_color": "#000000", 19 | "background_color": "#ffffff" 20 | } 21 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "MDN", 3 | "name": "MDN Web Docs", 4 | "icons": [ 5 | { 6 | "src": "/favicon-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/favicon-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "start_url": ".", 17 | "display": "standalone", 18 | "theme_color": "#000000", 19 | "background_color": "#ffffff" 20 | } 21 | -------------------------------------------------------------------------------- /public/mdn-social-share.d893525a4fb5fb1f67a2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/public/mdn-social-share.d893525a4fb5fb1f67a2.png -------------------------------------------------------------------------------- /public/mdn-social-share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/fred/2fd8d7a2237bf3eda659cdd69a76c8100184ee14/public/mdn-social-share.png -------------------------------------------------------------------------------- /public/opensearch.xml: -------------------------------------------------------------------------------- 1 | 2 | MDN Web Docs 3 | Search the MDN Web Docs 4 | UTF-8 5 | https://developer.mozilla.org/favicon.ico 6 | 7 | 8 | -------------------------------------------------------------------------------- /scripts/npm-test.js: -------------------------------------------------------------------------------- 1 | import { concurrently } from "concurrently"; 2 | 3 | concurrently( 4 | [ 5 | { 6 | command: `npm run wdio`, 7 | name: "wdio", 8 | prefixColor: "green", 9 | }, 10 | { 11 | cwd: process.env.CONTENT_REPO_ROOT, 12 | command: `yarn start`, 13 | name: "content", 14 | prefixColor: "blue", 15 | }, 16 | ], 17 | { 18 | killOthersOn: ["failure", "success"], 19 | restartTries: 0, 20 | successCondition: "first", 21 | }, 22 | ); 23 | -------------------------------------------------------------------------------- /scripts/tests.js: -------------------------------------------------------------------------------- 1 | import { rariBin } from "@mdn/rari"; 2 | import { concurrently } from "concurrently"; 3 | 4 | concurrently( 5 | [ 6 | { 7 | command: `npm run wdio`, 8 | name: "wdio", 9 | prefixColor: "green", 10 | }, 11 | { 12 | command: `npm run preview`, 13 | name: "server", 14 | prefixColor: "red", 15 | }, 16 | { 17 | command: `"${rariBin}" serve`, 18 | name: "rari", 19 | prefixColor: "blue", 20 | }, 21 | ], 22 | { 23 | killOthersOn: ["failure", "success"], 24 | restartTries: 0, 25 | successCondition: "first", 26 | }, 27 | ); 28 | -------------------------------------------------------------------------------- /svgo.config.js: -------------------------------------------------------------------------------- 1 | export default override(); 2 | 3 | /** 4 | * @param {object} [overrides] 5 | * @param {(string | object)[]} [plugins] 6 | * @returns 7 | */ 8 | export function override(overrides, plugins = []) { 9 | return { 10 | multipass: true, 11 | plugins: [ 12 | { 13 | name: "preset-default", 14 | params: { 15 | overrides: { 16 | removeViewBox: false, 17 | ...overrides, 18 | }, 19 | }, 20 | }, 21 | ...plugins, 22 | ], 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /symmetric-context/both.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Runs on either client or server, 3 | * and returns the client or server context respectively 4 | * @returns {import("./types.js").SymmetricContext} 5 | */ 6 | export function getSymmetricContext() { 7 | const serverStore = globalThis.__MDNServerContext?.getStore(); 8 | const clientStore = globalThis.__MDNClientContext; 9 | return serverStore || clientStore; 10 | } 11 | -------------------------------------------------------------------------------- /symmetric-context/client.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The client-derived values of the symmetric context: 3 | * this will run within a user's web browser 4 | * @type {import("./types.js").SymmetricContext} 5 | */ 6 | globalThis.__MDNClientContext = { 7 | locale: globalThis.location.pathname.split("/")[1] || "en-US", 8 | }; 9 | -------------------------------------------------------------------------------- /symmetric-context/server.js: -------------------------------------------------------------------------------- 1 | import { AsyncLocalStorage } from "node:async_hooks"; 2 | 3 | globalThis.__MDNServerContext = new AsyncLocalStorage(); 4 | 5 | /** 6 | * @template R 7 | * @param {import("./types.js").SymmetricContext} context The server-derived values of the symmetric context 8 | * @param {() => R} callback 9 | */ 10 | export function runWithContext(context, callback) { 11 | return globalThis.__MDNServerContext.run(context, callback); 12 | } 13 | -------------------------------------------------------------------------------- /symmetric-context/types.d.ts: -------------------------------------------------------------------------------- 1 | export interface SymmetricContext { 2 | locale: string; 3 | } 4 | -------------------------------------------------------------------------------- /template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /test/pageobjects/doc.page.js: -------------------------------------------------------------------------------- 1 | import { $ } from "@wdio/globals"; 2 | 3 | import Page from "./page.js"; 4 | 5 | class DocPage extends Page { 6 | get title() { 7 | return $("h1:first-of-type"); 8 | } 9 | } 10 | 11 | export default new DocPage(); 12 | -------------------------------------------------------------------------------- /test/pageobjects/page.js: -------------------------------------------------------------------------------- 1 | import { browser } from "@wdio/globals"; 2 | 3 | export default class Page { 4 | /** 5 | * Opens a page at path 6 | * @param {string} path path of page (e.g. /path/to/page.html) 7 | */ 8 | open(path) { 9 | return browser.url(path); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/specs/kitchensink.e2e.js: -------------------------------------------------------------------------------- 1 | import { expect } from "@wdio/globals"; 2 | 3 | import DocPage from "../pageobjects/doc.page.js"; 4 | import { captureLogs } from "../utils.js"; 5 | 6 | describe("Kitchensink", () => { 7 | it("shouldn't have unknown console error messages", async () => { 8 | const { messages } = await captureLogs(); 9 | await DocPage.open("en-US/docs/MDN/Kitchensink"); 10 | await expect( 11 | messages.filter( 12 | (message) => 13 | !( 14 | message.includes("Bad Gateway") || 15 | message.includes('Permission denied to access property "length"') 16 | ), 17 | ), 18 | ).toEqual([]); 19 | }); 20 | 21 | it("should have correct title", async () => { 22 | await DocPage.open("en-US/docs/MDN/Kitchensink"); 23 | await expect(DocPage.title).toBeExisting(); 24 | await expect(DocPage.title).toHaveText("The MDN Content Kitchensink"); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | import { browser } from "@wdio/globals"; 2 | 3 | export async function captureLogs() { 4 | await browser.sessionSubscribe({ events: ["log.entryAdded"] }); 5 | /** @type {import("webdriver").local.LogEntry[]} */ 6 | const logs = []; 7 | /** @type {string[]} */ 8 | const messages = []; 9 | browser.on("log.entryAdded", (event) => { 10 | if (event.level === "error") { 11 | logs.push(event); 12 | if (event.text) { 13 | messages.push(event.text); 14 | } 15 | } 16 | }); 17 | return { logs, messages }; 18 | } 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // general options: 4 | "allowJs": true, 5 | "checkJs": true, 6 | "maxNodeModuleJsDepth": 0, 7 | "noEmit": true, 8 | "target": "esnext", 9 | "module": "nodenext", 10 | "moduleResolution": "nodenext", 11 | "types": ["@wdio/globals/types", "mocha"], 12 | // fix for badly-formed types in dependencies: 13 | "skipLibCheck": true, 14 | // alias for imports: 15 | "paths": { 16 | "@*": ["./types/*.js"] 17 | }, 18 | // rules: 19 | "strict": true, 20 | "noUncheckedIndexedAccess": true, 21 | "noUnusedLocals": true, 22 | "noUnusedParameters": true, 23 | "noImplicitReturns": true, 24 | "noFallthroughCasesInSwitch": true, 25 | "plugins": [ 26 | { 27 | "name": "@jackolope/ts-lit-plugin", 28 | "strict": true 29 | } 30 | ] 31 | }, 32 | "exclude": ["node_modules", "dist", "out", "legacy", "vendor"] 33 | } 34 | -------------------------------------------------------------------------------- /types/bcd.ts: -------------------------------------------------------------------------------- 1 | export * from "@mdn/browser-compat-data"; 2 | -------------------------------------------------------------------------------- /types/fluent-2.ts: -------------------------------------------------------------------------------- 1 | import getFluentContext from "../l10n/fluent.js"; 2 | 3 | export type L10nFunction = ReturnType; 4 | -------------------------------------------------------------------------------- /types/fluent.ts: -------------------------------------------------------------------------------- 1 | import { unsafeHTML } from "lit/directives/unsafe-html.js"; 2 | 3 | export interface Element { 4 | tag: string; 5 | [k: string]: string; 6 | } 7 | 8 | export type L10nTag = ( 9 | strings: TemplateStringsArray, 10 | ) => string | ReturnType; 11 | -------------------------------------------------------------------------------- /types/fred.ts: -------------------------------------------------------------------------------- 1 | import { BuiltPage } from "@mdn/rari"; 2 | import { L10nFunction } from "./fluent-2.js"; 3 | import { StatsCompilation } from "@rspack/core"; 4 | 5 | export type Context = PartialContext & 6 | L10nContext & { 7 | pageTitle?: string; 8 | path: string; 9 | }; 10 | 11 | export type PartialContext = T & { 12 | localServer?: boolean; 13 | }; 14 | 15 | export type L10nContext = { 16 | locale: string; 17 | l10n: L10nFunction; 18 | }; 19 | 20 | export type CompilationStats = { 21 | client: StatsCompilation; 22 | legacy: StatsCompilation; 23 | }; 24 | -------------------------------------------------------------------------------- /types/lit.ts: -------------------------------------------------------------------------------- 1 | import { UnsafeHTMLDirective } from "lit-html/directives/unsafe-html.js"; 2 | import { DirectiveResult } from "lit/directive.js"; 3 | 4 | export * from "lit"; 5 | export * from "lit/directives/ref.js"; 6 | 7 | export type L10nResult = DirectiveResult; 8 | -------------------------------------------------------------------------------- /types/modules.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.css?lit" { 2 | const css: import("lit").CSSResult; 3 | export default css; 4 | } 5 | 6 | declare module "*.svg?lit" { 7 | const svg: import("lit").SVGTemplateResult; 8 | export default svg; 9 | } 10 | 11 | declare module "*.svg" { 12 | const url: string; 13 | export default url; 14 | } 15 | 16 | declare module "*.ftl" { 17 | const ftl: string; 18 | export default ftl; 19 | } 20 | 21 | declare module "*?url" { 22 | const url: string; 23 | export default url; 24 | } 25 | 26 | declare module "*?source" { 27 | const source: string; 28 | export default source; 29 | } 30 | 31 | declare module "*?source&csp=true" { 32 | const source: string; 33 | export default source; 34 | } 35 | -------------------------------------------------------------------------------- /types/rari.ts: -------------------------------------------------------------------------------- 1 | export * from "@mdn/rari"; 2 | -------------------------------------------------------------------------------- /utils/glean.js: -------------------------------------------------------------------------------- 1 | // @ts-expect-error "Could not find declaration file" 2 | import GleanMetrics from "@mozilla/glean/metrics"; 3 | 4 | /** 5 | * Records a click event. 6 | * 7 | * Use only if automatic click events are not an option. 8 | * See: https://mozilla.github.io/glean.js/automatic_instrumentation/click_events/ 9 | * 10 | * @param {string} source 11 | */ 12 | export function gleanClick(source) { 13 | GleanMetrics.recordElementClick({ 14 | id: source, 15 | url: globalThis.location.href, 16 | referrer: document.referrer, 17 | title: document.title, 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /utils/mdn-url2breadcrumb.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns an MDN URL into a Breadcrumb-like string representation. 3 | * 4 | * @param {string} url - the full MDN URL (e.g. "/en-US/docs/Web/HTML/Reference/Elements/a"). 5 | * @param {string} locale - the locale (to omit) 6 | * @returns {string} the breadcrumb-like string for the URL. 7 | */ 8 | export function mdnUrl2Breadcrumb(url, locale) { 9 | let parents = url 10 | .replaceAll("_", " ") 11 | .split("/") 12 | .filter((p) => !["", locale, "docs"].includes(p)); 13 | 14 | // Replace "API" for clarity. 15 | parents = parents.map((p) => (p === "API" ? "Web APIs" : p)); 16 | 17 | if (parents.length > 1 && parents.at(0) === "Web") { 18 | // Remove virtual "Web" path. 19 | parents.splice(0, 1); 20 | } 21 | 22 | if (parents.length > 1) { 23 | // Remove current item. 24 | parents.splice(-1, 1); 25 | } 26 | 27 | return parents.join(" / "); 28 | } 29 | -------------------------------------------------------------------------------- /utils/telemetry-opt-out.js: -------------------------------------------------------------------------------- 1 | const FIRST_PARTY_DATA_OPT_OUT_COOKIE_NAME = "moz-1st-party-data-opt-out"; 2 | 3 | /** 4 | * Checks if user has opted out of first-party data collection. 5 | * @param {string} [cookie] - An optional mock cookie string to ease unit testing. 6 | * @returns {boolean} True if user has opted out, false otherwise 7 | */ 8 | export function userIsOptedOut(cookie) { 9 | return (cookie || document.cookie) 10 | .split("; ") 11 | .includes(`${FIRST_PARTY_DATA_OPT_OUT_COOKIE_NAME}=true`); 12 | } 13 | -------------------------------------------------------------------------------- /vendor/yari/client/pwa/README.md: -------------------------------------------------------------------------------- 1 | # MDN Offline 2 | 3 | This is MDN's ServiceWorker that powers MDN Offline. It's not being build by 4 | default for local development. And for now it requires a running instance of 5 | [kuma](https://github.com/mdn/kuma) to work. We might add support for local 6 | development and update this README. 7 | -------------------------------------------------------------------------------- /vendor/yari/client/pwa/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mdn-worker", 3 | "version": "1.0.0", 4 | "description": "MDN Offline service worker", 5 | "license": "MPL-2.0", 6 | "author": "MDN Web Docs", 7 | "type": "module", 8 | "main": "service-worker.ts", 9 | "scripts": { 10 | "build": "webpack", 11 | "build:prod": "NODE_ENV=production webpack --config ./webpack.production.config.js", 12 | "dev": "webpack-cli --watch" 13 | }, 14 | "dependencies": { 15 | "@zip.js/zip.js": "2.7.71", 16 | "dexie": "4.0.11" 17 | }, 18 | "devDependencies": { 19 | "@types/dexie": "1.3.35", 20 | "ts-loader": "^9.5.2", 21 | "typescript": "^5.9.2", 22 | "webpack": "^5.101.0", 23 | "webpack-cli": "^6.0.1", 24 | "workers-preview": "^1.0.6" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /vendor/yari/client/pwa/src/caches.ts: -------------------------------------------------------------------------------- 1 | declare var __COMMIT_HASH__: string; 2 | 3 | export const cacheName = `mdn-app-${__COMMIT_HASH__}`; 4 | export const contentCache = "mdn-content-v1"; 5 | 6 | export function openCache(): Promise { 7 | return caches.open(cacheName); 8 | } 9 | 10 | export function openContentCache(): Promise { 11 | return caches.open(contentCache); 12 | } 13 | -------------------------------------------------------------------------------- /vendor/yari/client/pwa/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "lib": ["dom", "dom.iterable", "es2021", "webworker"], 5 | "allowImportingTsExtensions": false, 6 | "noEmit": false, 7 | "preserveConstEnums": true, 8 | "strictNullChecks": false, 9 | "target": "ES2021" 10 | }, 11 | "include": ["src"], 12 | "exclude": [] 13 | } 14 | -------------------------------------------------------------------------------- /vendor/yari/client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "checkJs": true, 5 | "allowImportingTsExtensions": true, 6 | "allowSyntheticDefaultImports": true, 7 | "esModuleInterop": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "isolatedModules": true, 10 | "jsx": "react-jsx", 11 | "lib": ["dom", "dom.iterable", "es2021"], 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "noEmit": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "noImplicitAny": false, 17 | "preserveWatchOutput": true, 18 | "resolveJsonModule": true, 19 | "skipLibCheck": true, 20 | "sourceMap": true, 21 | "strict": true, 22 | "target": "ES2020" 23 | }, 24 | "include": ["src"] 25 | } 26 | -------------------------------------------------------------------------------- /vendor/yari/libs/play/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@yari-internal/play", 3 | "version": "0.0.1", 4 | "private": true, 5 | "license": "MPL-2.0", 6 | "type": "module", 7 | "main": "index.js", 8 | "types": "index.js", 9 | "dependencies": { 10 | "he": "^1.2.0" 11 | }, 12 | "devDependencies": { 13 | "@types/he": "^1.2.3" 14 | }, 15 | "engines": { 16 | "node": ">=20" 17 | }, 18 | "export": "index.js" 19 | } 20 | -------------------------------------------------------------------------------- /vendor/yari/libs/play/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["*.js"], 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "strict": true, 7 | "declaration": true, 8 | "emitDeclarationOnly": true, 9 | "esModuleInterop": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /vendor/yari/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "esModuleInterop": true, 5 | "forceConsistentCasingInFileNames": true, 6 | "jsx": "react-jsx", 7 | "module": "NodeNext", 8 | "moduleResolution": "NodeNext", 9 | "noEmit": true, 10 | "noImplicitAny": false, 11 | "paths": { 12 | "front-matter": ["./type-fixes/front-matter.js"], 13 | "@mdn/browser-compat-data/types": [ 14 | "./node_modules/@mdn/browser-compat-data/types.d.ts" 15 | ] 16 | }, 17 | "preserveWatchOutput": true, 18 | "resolveJsonModule": true, 19 | "skipLibCheck": true, 20 | "sourceMap": true, 21 | "strict": true, 22 | "strictNullChecks": false, 23 | "target": "ES2020" 24 | }, 25 | "ts-node": { 26 | "esm": true, 27 | "swc": true 28 | }, 29 | "exclude": ["client", "ssr"] 30 | } 31 | --------------------------------------------------------------------------------