diff --git a/AGENTS.md b/AGENTS.md index 051982a20..20a6c4e85 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -154,14 +154,14 @@ let default: unit => React.element ## Styling - **Tailwind CSS v4** configured via the Vite plugin (`@tailwindcss/vite`). There is no `tailwind.config.js`. -- All Tailwind configuration is in `styles/main.css` using CSS-native `@theme` blocks. +- Tailwind design tokens and custom utilities live in `styles/_theme.css`. It is imported by `styles/main.css` and referenced by route-specific stylesheets. - **LightningCSS** is used as the CSS transformer. -- The project defines custom design tokens in `styles/main.css`: +- The project defines custom design tokens in `styles/_theme.css`: - Custom colors: `gray-*`, `fire-*`, `sky-*`, `berry-*`, `water`, `turtle`, `orange-*` - Custom font sizes: `text-11` through `text-68` - Custom utility classes: `hl-title`, `hl-1`–`hl-5`, `body-lg`, `body-md`, `body-sm`, `captions` - Fonts: Inter (sans), Roboto Mono (mono) -- Use existing custom utilities and design tokens. Check `styles/main.css` before creating new ones. +- Use existing custom utilities and design tokens. Check `styles/_theme.css` before creating new ones. ## Testing diff --git a/apps/docs/app/DocsRoot.res b/apps/docs/app/DocsRoot.res index f45def69d..3e38c1f3d 100644 --- a/apps/docs/app/DocsRoot.res +++ b/apps/docs/app/DocsRoot.res @@ -11,7 +11,6 @@ let default = () => { {CypressBootstrap.element()} - diff --git a/apps/docs/app/DocsRoot.resi b/apps/docs/app/DocsRoot.resi index 98c027984..e9a4c6ad1 100644 --- a/apps/docs/app/DocsRoot.resi +++ b/apps/docs/app/DocsRoot.resi @@ -1,4 +1,5 @@ /** Shared shell, token styles, and the stable Cypress bootstrap slot. Runtime grammars belong to content routes and the playground. */ +/** Keep server-rendered content visible; head stylesheets handle render blocking. */ @react.component let default: unit => Jsx.element diff --git a/apps/docs/app/DocsRoutes.res b/apps/docs/app/DocsRoutes.res index 326a19350..35886a279 100644 --- a/apps/docs/app/DocsRoutes.res +++ b/apps/docs/app/DocsRoutes.res @@ -73,7 +73,7 @@ let syntaxLookupDetailRoutes = ) let default = [ - index("./routes/LandingPageRoute.jsx"), + layout("./layouts/HomepageLayoutRoute.jsx", [index("./routes/LandingPageRoute.jsx")]), route("try", "./routes/TryRoute.jsx"), layout( "./layouts/ContentLayoutRoute.jsx", diff --git a/apps/docs/app/DocsRoutes.resi b/apps/docs/app/DocsRoutes.resi index c60e74bfd..ba1514a46 100644 --- a/apps/docs/app/DocsRoutes.resi +++ b/apps/docs/app/DocsRoutes.resi @@ -10,5 +10,5 @@ let docsReactRoutes: array let docsGuidesRoutes: array let communityRoutes: array let syntaxLookupDetailRoutes: array -/** Keep home, the lazy playground, and not-found outside the content highlighting boundary. */ +/** Keep homepage, playground, and content styles scoped to their route groups. */ let default: array diff --git a/apps/docs/app/layouts/ContentLayoutRoute.res b/apps/docs/app/layouts/ContentLayoutRoute.res index 5d0015195..a03d7b926 100644 --- a/apps/docs/app/layouts/ContentLayoutRoute.res +++ b/apps/docs/app/layouts/ContentLayoutRoute.res @@ -1,3 +1,10 @@ +@module("../../styles/content.css?url") +external contentCss: string = "default" + +type stylesheet = {rel: string, href: string} + +let links = () => [{rel: "stylesheet", href: contentCss}] + // Route-module initialization runs before any content children render. let () = ContentHighlighting.register(HighlightLanguages.defaultInstance) diff --git a/apps/docs/app/layouts/ContentLayoutRoute.resi b/apps/docs/app/layouts/ContentLayoutRoute.resi index a5c43f841..381da2b21 100644 --- a/apps/docs/app/layouts/ContentLayoutRoute.resi +++ b/apps/docs/app/layouts/ContentLayoutRoute.resi @@ -1,3 +1,7 @@ -/** Own runtime highlighting for content routes without adding a DOM wrapper. */ +type stylesheet = {rel: string, href: string} + +let links: unit => array + +/** Own content styles and runtime highlighting without adding a DOM wrapper. */ @react.component let default: unit => React.element diff --git a/apps/docs/app/layouts/HomepageLayoutRoute.res b/apps/docs/app/layouts/HomepageLayoutRoute.res new file mode 100644 index 000000000..c8bdddb7d --- /dev/null +++ b/apps/docs/app/layouts/HomepageLayoutRoute.res @@ -0,0 +1,9 @@ +@module("../../styles/homepage.css?url") +external homepageCss: string = "default" + +type stylesheet = {rel: string, href: string} + +let links = () => [{rel: "stylesheet", href: homepageCss}] + +@react.component +let default = () => diff --git a/apps/docs/app/layouts/HomepageLayoutRoute.resi b/apps/docs/app/layouts/HomepageLayoutRoute.resi new file mode 100644 index 000000000..bf0517cf9 --- /dev/null +++ b/apps/docs/app/layouts/HomepageLayoutRoute.resi @@ -0,0 +1,7 @@ +type stylesheet = {rel: string, href: string} + +let links: unit => array + +/** Own homepage styles without adding a DOM wrapper. */ +@react.component +let default: unit => React.element diff --git a/apps/docs/app/routes/TryRoute.res b/apps/docs/app/routes/TryRoute.res index d3636e6f6..22785fe5b 100644 --- a/apps/docs/app/routes/TryRoute.res +++ b/apps/docs/app/routes/TryRoute.res @@ -1,3 +1,10 @@ +@module("../../styles/playground.css?url") +external playgroundCss: string = "default" + +type stylesheet = {rel: string, href: string} + +let links = () => [{rel: "stylesheet", href: playgroundCss}] + type props = { bundleBaseUrl: string, versions: array, diff --git a/apps/docs/app/routes/TryRoute.resi b/apps/docs/app/routes/TryRoute.resi index 28764a056..65e640435 100644 --- a/apps/docs/app/routes/TryRoute.resi +++ b/apps/docs/app/routes/TryRoute.resi @@ -1,3 +1,7 @@ +type stylesheet = {rel: string, href: string} + +let links: unit => array + type props = { bundleBaseUrl: string, versions: array, diff --git a/apps/docs/cypress.homepage.config.mjs b/apps/docs/cypress.homepage.config.mjs index 93fe3f1b2..484a30a2f 100644 --- a/apps/docs/cypress.homepage.config.mjs +++ b/apps/docs/cypress.homepage.config.mjs @@ -9,7 +9,7 @@ export default defineConfig({ e2e: { ...config.e2e, baseUrl: "http://127.0.0.1:4173", - specPattern: ["e2e/homepage/**/*.cy.jsx", "e2e/homepage/**/*.cy.js"], + specPattern: "e2e/homepage/**/*.cy.jsx", excludeSpecPattern: [], supportFile: "e2e/homepage/HomepageSupport.jsx", screenshotOnRunFailure: true, diff --git a/apps/docs/e2e/Playground.cy.res b/apps/docs/e2e/Playground.cy.res index 1b77dc10e..f4fd5eb09 100644 --- a/apps/docs/e2e/Playground.cy.res +++ b/apps/docs/e2e/Playground.cy.res @@ -66,6 +66,8 @@ describe("Playground", () => { it("should highlight JavaScript after a direct playground load", () => { visit("/try") get(".cm-editor")->shouldBeVisible->ignore + get("main")->shouldWithKeyValue("have.css", "background-color", "rgb(11, 13, 34)")->ignore + get(".cm-editor")->shouldWithKeyValue("have.css", "background-color", "rgb(11, 13, 34)")->ignore get("pre.whitespace-pre-wrap")->shouldContainText("react/jsx-runtime")->ignore get("pre code.lang-js span[class^='hljs-']")->should("exist")->ignore }) @@ -134,6 +136,10 @@ describe("Playground", () => { // Verify playground shell is in light mode get("main")->shouldWithValue("have.class", "playground-theme-light")->ignore + get("main")->shouldWithKeyValue("have.css", "background-color", "rgb(250, 251, 252)")->ignore + get(".cm-editor") + ->shouldWithKeyValue("have.css", "background-color", "rgb(255, 255, 255)") + ->ignore cyWindow() ->its("localStorage") ->invokeWithArg("getItem", "playgroundTheme") @@ -150,6 +156,8 @@ describe("Playground", () => { // Verify playground shell is back to dark mode get("main")->shouldWithValue("have.class", "playground-theme-dark")->ignore + get("main")->shouldWithKeyValue("have.css", "background-color", "rgb(11, 13, 34)")->ignore + get(".cm-editor")->shouldWithKeyValue("have.css", "background-color", "rgb(11, 13, 34)")->ignore cyWindow() ->its("localStorage") ->invokeWithArg("getItem", "playgroundTheme") diff --git a/apps/docs/e2e/bindings/Cypress.res b/apps/docs/e2e/bindings/Cypress.res index 793df1e4c..be8c3c607 100644 --- a/apps/docs/e2e/bindings/Cypress.res +++ b/apps/docs/e2e/bindings/Cypress.res @@ -15,6 +15,9 @@ type rec window = { } and clipboard type response = {status: int, body: string, headers: Dict.t} +type replyResponse = {statusCode: int, headers: Dict.t, body: string} +type networkError = {forceNetworkError: bool} +type fetchResponse type automation = {command: string, params?: {permissions: array, origin: string}} type request = {url: string, resourceType: string} type routeMatcher = {resourceType?: string, pathname?: string} @@ -59,6 +62,10 @@ external interceptStatic: (searchRouteMatcher, staticResponse) => chain = @val @scope("cy") external interceptPattern: (RegExp.t, request => unit) => chain = "intercept" @val @scope("cy") +external interceptFailurePattern: (RegExp.t, networkError) => chain = "intercept" +@val @scope("cy") +external interceptFailureString: (string, networkError) => chain = "intercept" +@val @scope("cy") external interceptDeferred: (RegExp.t, unit => promise) => chain = "intercept" @val @scope("cy") external interceptDeferredRequest: (routeMatcher, request => promise) => chain = @@ -110,6 +117,9 @@ external shouldProperty: (chain, @as("have.prop") _, string, string) = "should" @send external attribute: (chain, @as("attr") _, string) => chain = "invoke" @send external propertyInt: (chain<'a>, string) => chain = "its" +@send external propertyString: (chain<'a>, string) => chain = "its" +@send +external shouldPropertyExist: (chain<'a>, @as("have.property") _, string) => chain<'a> = "should" @send external shouldSatisfy: (chain<'a>, 'a => unit) => chain<'a> = "should" @send external click: chain => chain = "click" @send external first: chain => chain = "first" @@ -127,6 +137,8 @@ external containsChildRegex: (chain, string, RegExp.t) => chain int = "naturalWidth" @send external readText: clipboard => promise = "readText" @send external destroy: request => unit = "destroy" +@set external setRequestAlias: (request, string) => unit = "alias" +@send external reply: (request, replyResponse) => unit = "reply" @val external expect: ('a, ~message: string=?) => assertion = "expect" @send @scope("to") external equal: (assertion, 'a) => unit = "equal" @@ -164,6 +176,7 @@ external addEventListenerOnce: (Dom.document, string, unit => unit, listenerOpti @get external baseURI: Dom.element => string = "baseURI" @get external currentSrc: Dom.element => string = "currentSrc" @get external parentElement: Dom.element => Nullable.t = "parentElement" +@get external elementHref: Dom.element => string = "href" @send external decode: Dom.element => promise = "decode" @val @scope("Array") external elementsFrom: elementList => array = "from" @@ -183,6 +196,9 @@ external addEventListenerOnce: (Dom.document, string, unit => unit, listenerOpti @get external fontLoaded: fontFace => promise = "loaded" @send external windowRequestAnimationFrame: (window, float => unit) => int = "requestAnimationFrame" +@send external windowFetch: (window, string) => promise = "fetch" +@get external fetchStatus: fetchResponse => int = "status" +@send external responseText: fetchResponse => promise = "text" @get external styleSheets: Dom.document => styleSheetList = "styleSheets" @val @scope("Array") external styleSheetsFrom: styleSheetList => array = "from" @@ -190,7 +206,10 @@ external addEventListenerOnce: (Dom.document, string, unit => unit, listenerOpti @val @scope("Array") external rulesFrom: cssRuleList => array = "from" @get external ruleType: cssRule => int = "type" @get external nestedRules: cssRule => Nullable.t = "cssRules" +@get external cssText: cssRule => string = "cssText" +@get external selectorText: cssRule => string = "selectorText" @get external ruleStyle: cssRule => cssStyle = "style" +@get external boxSizing: cssStyle => string = "boxSizing" @get external fontFamily: cssStyle => string = "fontFamily" @get external fontWeight: cssStyle => string = "fontWeight" @get external fontStyle: cssStyle => string = "fontStyle" diff --git a/apps/docs/e2e/homepage/HomepageInitialContent.cy.res b/apps/docs/e2e/homepage/HomepageInitialContent.cy.res new file mode 100644 index 000000000..9116e5dd9 --- /dev/null +++ b/apps/docs/e2e/homepage/HomepageInitialContent.cy.res @@ -0,0 +1,45 @@ +open Cypress +open HomepageHelpers + +let visitWithoutHydration = path => { + // Cypress needs JavaScript itself; empty application modules keep the AUT prerender-only. + interceptPattern(/\/assets\/[^/]+\.js(?:\?.*)?$/, request => { + if /\/entry\.client-[^/]+\.js/->RegExp.test(request.url) { + request->setRequestAlias("clientEntry") + } + request->reply({ + statusCode: 200, + headers: Dict.fromArray([("content-type", "text/javascript")]), + body: "", + }) + })->ignore + visit(path) + wait("@clientEntry")->propertyString("response.body")->shouldEqual("")->ignore +} + +it("homepage paragraphs render without hydration or downloaded fonts", () => { + interceptFailurePattern(/\.(?:woff2?|ttf|otf)(?:\?|$)/, {forceNetworkError: true})->ignore + visitWithoutHydration("/") + get("html")->shouldCss("opacity", "1")->ignore + containsIn("p", "ReScript is a strongly typed language that compiles to clean,") + ->should("be.visible") + ->shouldCss("color", "rgb(105, 107, 125)") + ->ignore + containsIn("p", "Its fast compiler and static type system")->should("be.visible")->ignore +}) + +["/", "/docs/manual/introduction/"]->Array.forEach(path => { + it(`${path} remains readable when the shared stylesheet fails`, () => { + interceptFailureString("**/assets/main-*.css", {forceNetworkError: true}) + ->as_("sharedStylesheet") + ->ignore + visitWithoutHydration(path) + wait("@sharedStylesheet")->shouldPropertyExist("error")->ignore + get("html")->shouldCss("opacity", "1")->ignore + if path === "/" { + containsIn("h1", headline)->should("be.visible")->ignore + } else { + containsInRegex("h1", /^ReScript$/)->should("be.visible")->ignore + } + }) +}) diff --git a/apps/docs/e2e/homepage/HomepageRouteCss.cy.res b/apps/docs/e2e/homepage/HomepageRouteCss.cy.res new file mode 100644 index 000000000..1b3b679d4 --- /dev/null +++ b/apps/docs/e2e/homepage/HomepageRouteCss.cy.res @@ -0,0 +1,183 @@ +open Cypress +open HomepageHelpers + +type foundationCounts = { + fonts: int, + uniqueFonts: int, + tokens: array, + resets: int, +} + +let homepageLayoutSelectors = [ + ".max-w-1060", + ".md\\:grid-cols-10", + ".md\\:col-span-6", + ".min-h-148", +] + +let expectDesktopLogo = () => { + get(`a[aria-label="homepage"]`) + ->shouldCss("width", "128px") + ->shouldCss("height", "40px") + ->ignore + get(`a[aria-label="homepage"] img[alt="ReScript Home"]`)->should("be.visible")->ignore +} + +let loadedStyles = () => + cyWindow()->thenPromise(async window => { + let styles = await window.document + ->querySelectorAll(`link[rel="stylesheet"]`) + ->elementsFrom + ->Array.map(async link => { + let response = await window->windowFetch(link->elementHref) + expect(response->fetchStatus, ~message=link->elementHref)->equal(200) + await response->responseText + }) + ->Promise.all + styles->Array.join("\n") + }) + +let rec flattenRules = rules => + rules->Array.flatMap(rule => + switch rule->nestedRules->Nullable.toOption { + | Some(nested) => [rule]->Array.concat(nested->rulesFrom->flattenRules) + | None => [rule] + } + ) + +let foundationCounts = window => { + let rules = + window.document + ->styleSheets + ->styleSheetsFrom + ->Array.flatMap(sheet => sheet->sheetRules->rulesFrom->flattenRules) + let fonts = rules->Array.filter(rule => rule->ruleType === 5)->Array.map(rule => rule->cssText) + let styles = rules->Array.filter(rule => rule->ruleType === 1) + let tokens = + ["--font-sans", "--color-gray-90", "--color-fire", "--text-48"]->Array.map(token => + styles->Array.filter(rule => rule->ruleStyle->propertyValue(token) !== "")->Array.length + ) + let uniqueFonts = + fonts->Array.reduce([], (unique, font) => + unique->Array.includes(font) ? unique : unique->Array.concat([font]) + ) + let resets = styles->Array.filter(rule => + rule + ->selectorText + ->String.split(",") + ->Array.some(selector => selector->String.trim === "*") && + rule->ruleStyle->boxSizing === "border-box" + ) + { + fonts: fonts->Array.length, + uniqueFonts: uniqueFonts->Array.length, + tokens, + resets: resets->Array.length, + } +} + +let expectContentStyles = () => + loadedStyles()->then(styles => { + expect(styles)->include_(".markdown-body") + expect(styles)->notInclude(".playground-theme") + homepageLayoutSelectors->Array.forEach(selector => expect(styles)->notInclude(selector)) + }) + +it("homepage styles exclude content, search, and playground rules", () => { + visit("/") + containsIn("h1", headline)->should("be.visible")->ignore + loadedStyles() + ->then(styles => { + expect(styles)->include_(".gallery-selector") + homepageLayoutSelectors->Array.forEach(selector => expect(styles)->include_(selector)) + [".markdown-body", ".playground-theme", ".DocSearch-Modal"]->Array.forEach( + selector => expect(styles)->notInclude(selector), + ) + }) + ->ignore +}) + +it("shared foundations are emitted once across route navigation", () => { + visit("/") + cyWindow() + ->thenMap(foundationCounts) + ->then(initial => { + expect(initial.fonts)->greaterThan(0) + expect(initial.uniqueFonts)->equal(initial.fonts) + expect(initial.tokens)->deepEqual([1, 1, 1, 1]) + expect(initial.resets)->equal(1) + containsInRegex("a", /^Docs$/)->click->ignore + containsInRegex("h1", /^ReScript$/)->should("be.visible")->ignore + cyWindow()->thenMap(foundationCounts)->shouldDeepEqual(initial)->ignore + get(`a[aria-label="homepage"]`)->click->ignore + containsIn("h1", headline)->shouldCss("font-size", "68px")->ignore + cyWindow()->thenMap(foundationCounts)->shouldDeepEqual(initial)->ignore + }) + ->ignore +}) + +it("desktop navigation keeps its responsive logo across route stylesheets", () => { + visit("/") + expectDesktopLogo() + containsInRegex("a", /^Docs$/)->click->ignore + containsInRegex("h1", /^ReScript$/)->should("be.visible")->ignore + expectDesktopLogo() + get(`a[aria-label="homepage"]`)->click->ignore + containsIn("h1", headline)->should("be.visible")->ignore + expectDesktopLogo() +}) + +it("documentation styles are prefetched only after navigation intent", () => { + visit("/") + let prefetch = `link[rel="prefetch"][as="style"][href*="/content-"]` + get(prefetch)->should("not.exist")->ignore + containsInRegex("a", /^Get started$/)->focusElement->ignore + get(prefetch)->shouldInt("have.length", 1)->ignore + containsInRegex("a", /^Docs$/)->focusElement->ignore + get(prefetch)->should("not.exist")->ignore +}) + +[ + ("/docs/manual/introduction/", "ReScript"), + ("/brand/", "Brand Assets"), + ("/packages/", "Libraries & Bindings"), +]->Array.forEach(((path, title)) => { + it(`cold ${path} loads its content styles`, () => { + visit(path) + containsIn("h1", title) + ->should("be.visible") + ->shouldCss("font-weight", "600") + ->shouldCss("font-size", "48px") + ->ignore + expectContentStyles()->ignore + }) +}) + +it("cold blog styles preserve article typography", () => { + visit("/blog/") + get("h2") + ->first + ->should("be.visible") + ->shouldCss("font-size", "48px") + ->shouldCss("font-weight", "600") + ->ignore + expectContentStyles()->ignore +}) + +it("mobile documentation drawer retains its layout after navigation", () => { + viewport(375, 812) + visit("/") + containsInRegex("a", /^Docs$/)->click->ignore + containsInRegex("h1", /^ReScript$/)->should("be.visible")->ignore + get(`button[aria-label="Toggle navigation menu"]`)->click->ignore + get("dialog#mobile-tertiary-drawer") + ->as_("drawer") + ->should("be.visible") + ->shouldCss("background-color", "rgb(255, 255, 255)") + ->shouldCss("margin-left", "0px") + ->ignore + alias("@drawer")->containsChildRegex("a", /^Installation$/)->click->ignore + containsInRegex("h1", /^Installation$/)->should("be.visible")->ignore + realPressKey("Escape")->ignore + alias("@drawer")->should("not.be.visible")->ignore +}) diff --git a/apps/docs/src/components/LandingPageIntro.res b/apps/docs/src/components/LandingPageIntro.res index fcf9d1163..17e368c4d 100644 --- a/apps/docs/src/components/LandingPageIntro.res +++ b/apps/docs/src/components/LandingPageIntro.res @@ -18,7 +18,7 @@ let make = () => { confidence as your codebase grows.`)}

diff --git a/apps/docs/src/components/LandingPageIntro.resi b/apps/docs/src/components/LandingPageIntro.resi index 1ca44ce26..26c2771b5 100644 --- a/apps/docs/src/components/LandingPageIntro.resi +++ b/apps/docs/src/components/LandingPageIntro.resi @@ -1,2 +1,3 @@ +/** Prefetch installation content only after hover or focus intent. */ @react.component let make: unit => React.element diff --git a/apps/docs/styles/_shared-sources.css b/apps/docs/styles/_shared-sources.css new file mode 100644 index 000000000..ffaf36f90 --- /dev/null +++ b/apps/docs/styles/_shared-sources.css @@ -0,0 +1,5 @@ +/* Repeat shell utilities in each route so responsive variants follow every base utility. */ +@source "../app/DocsRoot.{res,jsx}"; +@source "../app/routes/NotFoundRoute.{res,jsx}"; +@source "../src/components/{NavbarPrimary,NavbarMobileOverlay,NavbarUtils,Search,SearchNotice}.{res,jsx}"; +@source "../../../packages/shared/src/Icon.{res,jsx}"; diff --git a/apps/docs/styles/_theme.css b/apps/docs/styles/_theme.css new file mode 100644 index 000000000..5982f28f0 --- /dev/null +++ b/apps/docs/styles/_theme.css @@ -0,0 +1,252 @@ +/* Inline defaults keep route-only utilities independent of root source detection. */ +@import "tailwindcss/theme.css" layer(theme) theme(inline); + +/* Emit custom tokens once at the root, including those referenced by lazy routes. */ +@theme static { + --radius-*: initial; + --radius-none: 0; + --radius-sm: 0.125rem; + --radius: 0.25rem; + --radius-lg: 0.5rem; + --radius-xl: 1rem; + --radius-full: 9999px; + + --breakpoint-*: initial; + --breakpoint-xs: 599px; + --breakpoint-sm: 576px; + --breakpoint-md: 768px; + --breakpoint-lg: 1024px; + --breakpoint-xl: 1440px; + + --text-*: initial; + --text-11: 0.6875rem; + --text-12: 0.75rem; + --text-14: 0.875rem; + --text-16: 1rem; + --text-18: 1.125rem; + --text-24: 1.5rem; + --text-32: 2rem; + --text-48: 3rem; + --text-68: 4.25rem; + + --font-weight-*: initial; + --font-weight-normal: 400; + --font-weight-medium: 500; + --font-weight-semibold: 600; + --font-weight-bold: 700; + + --leading-*: initial; + --leading-2: 1.35; + --leading-4: 1.5; + --leading-5: 1.75; + --leading-none: 1; + --leading-tight: 1.25; + + --tracking-*: initial; + --tracking-tighter: -0.03em; + --tracking-tight: -0.02em; + --tracking-normal: 0; + --tracking-wide: 0.075em; + --tracking-widest: 0.15em; + + --z-index-*: initial; + --z-index-0: 0; + --z-index-1: 1; + --z-index-2: 2; + --z-index-3: 3; + --z-index-4: 4; + --z-index-5: 5; + --z-index-10: 10; + --z-index-20: 20; + --z-index-25: 25; + --z-index-30: 30; + --z-index-40: 40; + --z-index-50: 50; + --z-index-75: 75; + --z-index-100: 100; + --z-index-auto: auto; + + --font-*: initial; + --font-sans: + Inter, SF Pro Text, Roboto, -apple-system, BlinkMacSystemFont, + Helvetica Neue, Arial, sans-serif; + --font-mono: Roboto Mono, SFMono-Regular, Menlo, Segoe UI, Courier, monospace; + + --color-gray-5: #fafbfc; + --color-gray-10: #fafbfc; + --color-gray-20: #edf0f2; + --color-gray-30: #cdcdd6; + --color-gray-40: #979aad; + --color-gray-60: #696b7d; + --color-gray-70: #3c3d4e; + --color-gray-80: #232538; + --color-gray-90: #14162c; + --color-gray-95: #14162c; + --color-gray-100: #0b0d22; + --color-gray-5-tr: rgba(1, 20, 29, 0.02); + --color-gray-10-tr: rgba(1, 20, 38, 0.02); + --color-gray-60-tr: rgba(1, 4, 39, 0.6); + --color-gray-80-tr: rgba(1, 4, 39, 0.8); + --color-gray-90-tr: rgba(1, 4, 39, 0.95); + + --color-white: #ffffff; + --color-white-80-tr: rgba(255, 255, 255, 0.8); + --color-white-60-tr: rgba(255, 255, 255, 0.6); + --color-white-40-tr: rgba(255, 255, 255, 0.4); + + --color-fire-5: #fcf1f1; + --color-fire-10: #fbb8bb; + --color-fire-30: #f4646a; + --color-fire-50: #e6484f; + --color-fire-70: #c3373d; + --color-fire-90: #790c10; + --color-fire-100: #211332; + --color-fire: #e6484f; + --color-fire-dark: #e06c75; + + --color-sky-5: #ecf1fc; + --color-sky-10: #dde8fd; + --color-sky-30: #638fe6; + --color-sky-70: #2258c3; + --color-sky-90: #0c2e6f; + --color-sky: #376fdd; + + --color-berry-15: rgba(171, 94, 163, 0.15); + --color-berry-40: #a766d0; + --color-berry: #b151dd; + --color-berry-dark-50: #b984db; + + --color-water: #5e5ede; + --color-water-dark: #637cc1; + + --color-ocean-dark: #8aaec8; + + --color-turtle: #02a875; + --color-turtle-dark: #388b72; + + --color-orange-10: rgba(224, 172, 0, 0.1); + --color-orange-15: rgba(224, 172, 0, 0.15); + --color-orange: #dd8c1b; + --color-orange-dark: #d59b74; + + --height-18: 4.5rem; + --height-inherit: inherit; + + --min-width-320: 20rem; + + --container-320: 20rem; + --container-400: 25rem; + --container-576: 36rem; + --container-740: 46.25rem; + --container-1060: 66.25rem; + --container-1280: 80rem; + --container-sm: 30rem; + --container-md: 40rem; + --container-xl: 66.25rem; + --container-none: none; + --container-full: 100%; + + --inset-16: 4rem; + --inset-18: 4.5rem; + + --spacing-2\/3: 66.666667%; + --spacing-9\/16: 56.25%; + --spacing-0_75: 0.135rem; + + --animate-pulse: pulse 0.5s cubic-bezier(0.4, 0, 0.6, 1); + + --shadow-sm: 0 0.5px 0.5px 0 rgba(0, 0, 0, 0.05); + + @keyframes pulse { + 0%, + 100% { + opacity: 1; + } + + 50% { + opacity: 0.5; + } + } +} + +@utility hl-title { + @apply text-[2.5rem] tracking-tighter leading-tight font-semibold text-gray-80; + + @media (width >=theme(--breakpoint-xs)) { + @apply text-48; + } + + @media (width >=theme(--breakpoint-md)) { + @apply text-68; + } + + @media (width >=theme(--breakpoint-lg)) { + @apply font-bold; + } +} + +@utility hl-1 { + @layer components { + @apply text-48 font-semibold tracking-tighter leading-tight text-black; + } +} + +@utility hl-2 { + @layer components { + @apply text-32 font-semibold tracking-tighter leading-tight text-black; + } +} + +@utility hl-3 { + @layer components { + @apply text-24 font-semibold tracking-tight leading-tight text-black; + } +} + +@utility hl-4 { + @layer components { + @apply text-18 font-semibold tracking-tight leading-tight text-gray-80; + } +} + +@utility hl-5 { + @layer components { + @apply text-14 font-bold tracking-normal leading-tight text-gray-80; + } +} + +@utility hl-overline { + @layer components { + @apply text-11 font-semibold tracking-wide leading-tight uppercase text-gray-80; + } +} + +@utility body-lg { + @layer components { + @apply text-18 font-normal tracking-tight leading-4; + } +} + +@utility body-button { + @layer components { + @apply text-16 font-semibold tracking-normal leading-2; + } +} + +@utility body-md { + @layer components { + @apply text-16 font-normal tracking-tight leading-4; + } +} + +@utility body-sm { + @layer components { + @apply text-14 font-medium tracking-normal leading-2; + } +} + +@utility captions { + @layer components { + @apply text-12 font-medium tracking-normal leading-2; + } +} diff --git a/apps/docs/styles/content.css b/apps/docs/styles/content.css new file mode 100644 index 000000000..bee0e3cd0 --- /dev/null +++ b/apps/docs/styles/content.css @@ -0,0 +1,89 @@ +@reference "./_theme.css"; +@import "tailwindcss/utilities.css" layer(utilities) source(none); +@import "./_shared-sources.css"; +@import "./_markdown.css" layer(base); + +@source "../app/routes/**/*.{res,jsx}"; +@source not "../app/routes/LandingPage*.{res,jsx}"; +@source not "../app/routes/TryRoute.{res,jsx}"; +@source "../app/layouts/**/*.{res,jsx}"; +@source "../src/**/*.{res,jsx,js,mjs}"; +@source not "../src/components/LandingPage*.{res,jsx}"; +@source "../markdown-pages/**/*.mdx"; +@source "../../../packages/shared/src/**/*.{res,jsx}"; + +@layer components { + a > code { + @apply text-fire; + } +} + +.wrapper { + position: relative; + display: inline-block; +} + +.version-popover[popover] { + inset: unset; + width: 100%; + height: auto; + /* padding: 0; */ + background: transparent; + border: none; + position: fixed; + top: 0px; + right: 0px; + pointer-events: none; + + overlay: none; + /* this alone would do the trick, but its not supported yet in all major browsers*/ +} + +.menu { + visibility: hidden; + position: absolute; + left: 0; + top: 100%; + opacity: 0; + transition: + visibility 0s ease, + opacity 0.3s ease, + transform 0.3s ease; +} + +.version-popover[popover]:popover-open ~ .menu { + opacity: 1; + visibility: visible; +} + +.trigger { + svg { + display: inline-block; + height: 0.75rem; + width: 0.75rem; + transform: rotate(0); + } +} + +.version-popover[popover]:popover-open ~ .trigger { + svg { + transform: rotate(180deg); + } +} + +#sidebar[popover] { + inset: unset; + width: unset; + border: none; + position: relative; + top: 0px; + left: 0px; + overlay: none; + height: 100%; + @apply min-w-64 bg-white border-gray-20 border-r-2 overflow-y-scroll h-full md:block; +} + +#mobile-tertiary-drawer { + max-width: 100%; + margin: 0; +} diff --git a/apps/docs/styles/homepage.css b/apps/docs/styles/homepage.css new file mode 100644 index 000000000..675a10b53 --- /dev/null +++ b/apps/docs/styles/homepage.css @@ -0,0 +1,30 @@ +@reference "./_theme.css"; +@import "tailwindcss/utilities.css" layer(utilities) source(none); +@import "./_shared-sources.css"; + +@source "../app/routes/LandingPage*.{res,jsx}"; +@source "../src/components/LandingPage*.{res,jsx}"; +@source "../src/components/{Footer,Button,ImageGallery,ResponsiveImage}.{res,jsx}"; + +.gallery-selector::after { + content: ""; + inline-size: 100%; + block-size: 1px; + background-color: currentColor; +} + +@media (prefers-reduced-motion: no-preference) { + .gallery-photo { + animation: gallery-fade-in 1s ease-in-out; + } +} + +@keyframes gallery-fade-in { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} diff --git a/apps/docs/styles/main.css b/apps/docs/styles/main.css index 045eb257d..fefc0ceca 100644 --- a/apps/docs/styles/main.css +++ b/apps/docs/styles/main.css @@ -1,180 +1,10 @@ -@import "./_markdown.css" layer(base); -@import "./_fonts.css" layer(base); - -@import "tailwindcss"; - -@source '../src/**/*.{jsx,res}'; -@source '../pages/**/*.{jsx,mdx}'; -@source '../app/**/*.{jsx,mdx}'; -/* Include workspace packages that render inside the docs app, e.g. the playground. */ -@source '../../../packages/*/src/**/*.{jsx,res}'; - -@theme { - --radius-*: initial; - --radius-none: 0; - --radius-sm: 0.125rem; - --radius: 0.25rem; - --radius-lg: 0.5rem; - --radius-xl: 1rem; - --radius-full: 9999px; - - --breakpoint-*: initial; - --breakpoint-xs: 599px; - --breakpoint-sm: 576px; - --breakpoint-md: 768px; - --breakpoint-lg: 1024px; - --breakpoint-xl: 1440px; - - --text-*: initial; - --text-11: 0.6875rem; - --text-12: 0.75rem; - --text-14: 0.875rem; - --text-16: 1rem; - --text-18: 1.125rem; - --text-24: 1.5rem; - --text-32: 2rem; - --text-48: 3rem; - --text-68: 4.25rem; - - --font-weight-*: initial; - --font-weight-normal: 400; - --font-weight-medium: 500; - --font-weight-semibold: 600; - --font-weight-bold: 700; - - --leading-*: initial; - --leading-2: 1.35; - --leading-4: 1.5; - --leading-5: 1.75; - --leading-none: 1; - --leading-tight: 1.25; - - --tracking-*: initial; - --tracking-tighter: -0.03em; - --tracking-tight: -0.02em; - --tracking-normal: 0; - --tracking-wide: 0.075em; - --tracking-widest: 0.15em; - - --z-index-*: initial; - --z-index-0: 0; - --z-index-1: 1; - --z-index-2: 2; - --z-index-3: 3; - --z-index-4: 4; - --z-index-5: 5; - --z-index-10: 10; - --z-index-20: 20; - --z-index-25: 25; - --z-index-30: 30; - --z-index-40: 40; - --z-index-50: 50; - --z-index-75: 75; - --z-index-100: 100; - --z-index-auto: auto; - - --font-*: initial; - --font-sans: - Inter, SF Pro Text, Roboto, -apple-system, BlinkMacSystemFont, - Helvetica Neue, Arial, sans-serif; - --font-mono: Roboto Mono, SFMono-Regular, Menlo, Segoe UI, Courier, monospace; - - --color-gray-5: #fafbfc; - --color-gray-10: #fafbfc; - --color-gray-20: #edf0f2; - --color-gray-30: #cdcdd6; - --color-gray-40: #979aad; - --color-gray-60: #696b7d; - --color-gray-70: #3c3d4e; - --color-gray-80: #232538; - --color-gray-90: #14162c; - --color-gray-95: #14162c; - --color-gray-100: #0b0d22; - --color-gray-5-tr: rgba(1, 20, 29, 0.02); - --color-gray-10-tr: rgba(1, 20, 38, 0.02); - --color-gray-60-tr: rgba(1, 4, 39, 0.6); - --color-gray-80-tr: rgba(1, 4, 39, 0.8); - --color-gray-90-tr: rgba(1, 4, 39, 0.95); - - --color-white: #ffffff; - --color-white-80-tr: rgba(255, 255, 255, 0.8); - --color-white-60-tr: rgba(255, 255, 255, 0.6); - --color-white-40-tr: rgba(255, 255, 255, 0.4); - - --color-fire-5: #fcf1f1; - --color-fire-10: #fbb8bb; - --color-fire-30: #f4646a; - --color-fire-50: #e6484f; - --color-fire-70: #c3373d; - --color-fire-90: #790c10; - --color-fire-100: #211332; - --color-fire: #e6484f; - --color-fire-dark: #e06c75; - - --color-sky-5: #ecf1fc; - --color-sky-10: #dde8fd; - --color-sky-30: #638fe6; - --color-sky-70: #2258c3; - --color-sky-90: #0c2e6f; - --color-sky: #376fdd; - - --color-berry-15: rgba(171, 94, 163, 0.15); - --color-berry-40: #a766d0; - --color-berry: #b151dd; - --color-berry-dark-50: #b984db; - - --color-water: #5e5ede; - --color-water-dark: #637cc1; - - --color-ocean-dark: #8aaec8; - - --color-turtle: #02a875; - --color-turtle-dark: #388b72; - - --color-orange-10: rgba(224, 172, 0, 0.1); - --color-orange-15: rgba(224, 172, 0, 0.15); - --color-orange: #dd8c1b; - --color-orange-dark: #d59b74; - - --height-18: 4.5rem; - --height-inherit: inherit; - - --min-width-320: 20rem; - - --container-320: 20rem; - --container-400: 25rem; - --container-576: 36rem; - --container-740: 46.25rem; - --container-1060: 66.25rem; - --container-1280: 80rem; - --container-sm: 30rem; - --container-md: 40rem; - --container-xl: 66.25rem; - --container-none: none; - --container-full: 100%; +@layer base, theme, components, utilities; - --inset-16: 4rem; - --inset-18: 4.5rem; - - --spacing-2\/3: 66.666667%; - --spacing-9\/16: 56.25%; - --spacing-0_75: 0.135rem; - - --animate-pulse: pulse 0.5s cubic-bezier(0.4, 0, 0.6, 1); - - --shadow-sm: 0 0.5px 0.5px 0 rgba(0, 0, 0, 0.05); - - @keyframes pulse { - 0%, - 100% { - opacity: 1; - } - - 50% { - opacity: 0.5; - } - } -} +@import "./_fonts.css" layer(base); +@import "./_theme.css"; +@import "tailwindcss/preflight.css" layer(base); +@import "tailwindcss/utilities.css" layer(utilities) source(none); +@import "./_shared-sources.css"; /* The default border color has changed to `currentcolor` in Tailwind CSS v4, @@ -194,324 +24,6 @@ } } -@layer components { - .playground-theme { - --playground-bg: var(--color-gray-100); - --playground-surface: var(--color-gray-100); - --playground-surface-border: transparent; - --playground-control-bg: var(--color-gray-100); - --playground-control-border: transparent; - --playground-elevated-bg: var(--color-gray-100); - --playground-overlay-bg: var(--color-gray-90); - --playground-overlay-border: var(--color-gray-70); - --playground-text-primary: var(--color-gray-20); - --playground-text-secondary: var(--color-gray-40); - --playground-border: var(--color-gray-60); - --playground-border-strong: var(--color-gray-80); - --playground-hover-surface: var(--color-gray-40); - --playground-hover-elevated: var(--color-gray-80); - --playground-active-surface: var(--color-gray-40); - --playground-input-bg: var(--color-gray-90); - --playground-placeholder: rgba(237, 240, 242, 0.5); - --playground-divider-bg: var(--color-gray-70); - --playground-divider-border: transparent; - --playground-divider-opacity: 0.3; - --playground-divider-hover-opacity: 0.5; - --playground-divider-handle: var(--color-gray-20); - --playground-scrollbar-track: var(--color-gray-100); - --playground-scrollbar-thumb: var(--color-gray-70); - --playground-scrollbar-thumb-hover: var(--color-gray-60); - --playground-toggle-track: #374151; - --playground-toggle-track-border: #4b5563; - --playground-toggle-thumb: var(--color-white); - --playground-toggle-label: #d1d5db; - --playground-tab-active: var(--color-white); - --playground-editor-bg: var(--color-gray-100); - --playground-editor-text: var(--color-gray-20); - --playground-editor-cursor: var(--color-orange); - --playground-editor-active-line: rgba(255, 255, 255, 0.02); - --playground-editor-gutter-bg: transparent; - --playground-editor-gutter-text: var(--color-gray-60); - --playground-editor-gutter-border: transparent; - --playground-editor-active-gutter-bg: transparent; - --playground-editor-active-gutter-text: var(--color-white); - --playground-editor-selection: rgba(255, 255, 255, 0.2); - --playground-editor-selection-match: #aafe661a; - --playground-editor-syntax-keyword: var(--color-berry-dark-50); - --playground-editor-syntax-variable: var(--color-gray-30); - --playground-editor-syntax-type: var(--color-orange-dark); - --playground-editor-syntax-string: var(--color-turtle-dark); - --playground-editor-syntax-comment: var(--color-gray-60); - --playground-editor-syntax-namespace-def: var(--color-orange); - --playground-editor-syntax-namespace: var(--color-water-dark); - --playground-editor-syntax-property: var(--color-ocean-dark); - --playground-editor-syntax-attribute: #bcc9ab; - } - - .playground-theme-light { - --playground-bg: var(--color-gray-5); - --playground-surface: var(--color-white); - --playground-surface-border: var(--color-gray-20); - --playground-control-bg: #f3f5f7; - --playground-control-border: var(--color-gray-20); - --playground-elevated-bg: var(--color-white); - --playground-overlay-bg: var(--color-white); - --playground-overlay-border: var(--color-gray-30); - --playground-text-primary: var(--color-gray-80); - --playground-text-secondary: var(--color-gray-80); - --playground-border: var(--color-gray-30); - --playground-border-strong: var(--color-gray-60); - --playground-hover-surface: var(--color-gray-20); - --playground-hover-elevated: var(--color-gray-10); - --playground-active-surface: var(--color-gray-20); - --playground-input-bg: var(--color-gray-10); - --playground-placeholder: var(--color-gray-60); - --playground-divider-bg: #f5f7f9; - --playground-divider-border: var(--color-gray-20); - --playground-divider-opacity: 1; - --playground-divider-hover-opacity: 1; - --playground-divider-handle: var(--color-gray-40); - --playground-scrollbar-track: #f5f7f9; - --playground-scrollbar-thumb: var(--color-gray-30); - --playground-scrollbar-thumb-hover: var(--color-gray-40); - --playground-toggle-track: var(--color-gray-30); - --playground-toggle-track-border: var(--color-gray-40); - --playground-toggle-thumb: var(--color-white); - --playground-toggle-label: var(--color-gray-80); - --playground-tab-active: var(--color-gray-80); - --playground-editor-bg: var(--color-white); - --playground-editor-text: var(--color-gray-80); - --playground-editor-cursor: var(--color-sky-70); - --playground-editor-active-line: rgba(34, 88, 195, 0.07); - --playground-editor-gutter-bg: var(--color-gray-10); - --playground-editor-gutter-text: var(--color-gray-60); - --playground-editor-gutter-border: var(--color-gray-20); - --playground-editor-active-gutter-bg: var(--color-gray-20); - --playground-editor-active-gutter-text: var(--color-gray-80); - --playground-editor-selection: rgba(34, 88, 195, 0.22); - --playground-editor-selection-match: rgba(94, 94, 222, 0.18); - --playground-editor-syntax-keyword: var(--color-berry); - --playground-editor-syntax-variable: var(--color-gray-80); - --playground-editor-syntax-type: var(--color-orange); - --playground-editor-syntax-string: var(--color-turtle); - --playground-editor-syntax-comment: var(--color-gray-60); - --playground-editor-syntax-namespace-def: var(--color-orange); - --playground-editor-syntax-namespace: var(--color-water); - --playground-editor-syntax-property: var(--color-sky); - --playground-editor-syntax-attribute: #4f5f78; - } - - .playground-main { - background-color: var(--playground-bg); - color: var(--playground-text-secondary); - } - - .playground-surface { - background-color: var(--playground-surface); - border-color: var(--playground-surface-border); - } - - .playground-text-primary { - color: var(--playground-text-primary) !important; - } - - .playground-text-secondary { - color: var(--playground-text-secondary) !important; - } - - .playground-select { - background-color: var(--playground-elevated-bg); - border-color: var(--playground-border); - color: var(--playground-text-primary) !important; - } - - .playground-selection-option { - background-color: var(--playground-elevated-bg); - border: 1px solid var(--playground-border); - color: var(--playground-text-primary); - } - - .playground-selection-option:hover { - background-color: var(--playground-hover-surface); - } - - .playground-selection-option-active { - background-color: var(--color-fire); - border-color: var(--color-fire); - color: var(--color-white); - } - - .playground-selection-option-active:hover { - background-color: var(--color-fire); - } - - .playground-overlay { - background-color: var(--playground-overlay-bg); - border-color: var(--playground-overlay-border); - color: var(--playground-text-primary); - } - - .playground-field { - border-color: var(--playground-border); - } - - .playground-field-active { - border-color: var(--playground-text-primary); - } - - .playground-icon-button:hover { - background-color: var(--playground-hover-surface); - } - - .playground-input { - background-color: var(--playground-input-bg); - color: var(--playground-text-primary); - } - - .playground-input::placeholder { - color: var(--playground-placeholder); - } - - .playground-chip-active { - background-color: var(--playground-active-surface); - } - - .playground-suggestion-active { - background-color: var(--playground-active-surface); - } - - .playground-control-panel { - background-color: var(--playground-control-bg); - border-bottom: 1px solid var(--playground-control-border); - } - - .playground-toast { - background-color: var(--playground-overlay-bg); - border-color: var(--playground-overlay-border); - color: var(--playground-text-primary); - } - - .playground-toggle-label { - color: var(--playground-toggle-label); - } - - .playground-toggle-track { - background-color: var(--playground-toggle-track); - border-color: var(--playground-toggle-track-border); - } - - .playground-toggle-track::after { - background-color: var(--playground-toggle-thumb); - border-color: var(--playground-toggle-track-border); - } - - .playground-tab-active { - color: var(--playground-tab-active); - border-top-color: var(--color-sky-70) !important; - } - - .playground-editor-shell { - background-color: var(--playground-editor-bg); - } - - .playground-divider { - background-color: var(--playground-divider-bg); - border: 1px solid var(--playground-divider-border); - opacity: var(--playground-divider-opacity); - } - - .playground-divider:hover { - opacity: var(--playground-divider-hover-opacity); - background-color: var(--playground-hover-surface); - } - - .playground-divider-handle { - color: var(--playground-divider-handle); - } -} - -@utility hl-title { - @apply text-[2.5rem] tracking-tighter leading-tight font-semibold text-gray-80; - - @media (width >=theme(--breakpoint-xs)) { - @apply text-48; - } - - @media (width >=theme(--breakpoint-md)) { - @apply text-68; - } - - @media (width >=theme(--breakpoint-lg)) { - @apply font-bold; - } -} - -@utility hl-1 { - @layer components { - @apply text-48 font-semibold tracking-tighter leading-tight text-black; - } -} - -@utility hl-2 { - @layer components { - @apply text-32 font-semibold tracking-tighter leading-tight text-black; - } -} - -@utility hl-3 { - @layer components { - @apply text-24 font-semibold tracking-tight leading-tight text-black; - } -} - -@utility hl-4 { - @layer components { - @apply text-18 font-semibold tracking-tight leading-tight text-gray-80; - } -} - -@utility hl-5 { - @layer components { - @apply text-14 font-bold tracking-normal leading-tight text-gray-80; - } -} - -@utility hl-overline { - @layer components { - @apply text-11 font-semibold tracking-wide leading-tight uppercase text-gray-80; - } -} - -@utility body-lg { - @layer components { - @apply text-18 font-normal tracking-tight leading-4; - } -} - -@utility body-button { - @layer components { - @apply text-16 font-semibold tracking-normal leading-2; - } -} - -@utility body-md { - @layer components { - @apply text-16 font-normal tracking-tight leading-4; - } -} - -@utility body-sm { - @layer components { - @apply text-14 font-medium tracking-normal leading-2; - } -} - -@utility captions { - @layer components { - @apply text-12 font-medium tracking-normal leading-2; - } -} - @layer components { /* @import "./_typography.css"; */ @@ -531,124 +43,6 @@ background: transparent; /* Chrome/Safari/Webkit */ } - - .playground-scrollbar { - scrollbar-width: thin; - scrollbar-color: var(--playground-scrollbar-thumb) - var(--playground-scrollbar-track); - scrollbar-gutter: stable; /* Reserve only at the scrollbar edge to avoid padding shifts */ - } - - .playground-scrollbar::-webkit-scrollbar { - width: 0.65rem; - height: 0.65rem; - } - - .playground-scrollbar::-webkit-scrollbar-track { - background: var(--playground-scrollbar-track); - } - - .playground-scrollbar::-webkit-scrollbar-thumb { - background-color: var(--playground-scrollbar-thumb); - border-radius: 9999px; - border: 2px solid var(--playground-scrollbar-track); - } - - .playground-scrollbar::-webkit-scrollbar-thumb:hover { - background-color: var(--playground-scrollbar-thumb-hover); - } - - a > code { - @apply text-fire; - } -} - -.wrapper { - position: relative; - display: inline-block; -} - -.gallery-selector::after { - content: ""; - inline-size: 100%; - block-size: 1px; - background-color: currentColor; -} - -@media (prefers-reduced-motion: no-preference) { - .gallery-photo { - animation: gallery-fade-in 1s ease-in-out; - } -} - -@keyframes gallery-fade-in { - from { - opacity: 0; - } - - to { - opacity: 1; - } -} - -.version-popover[popover] { - inset: unset; - width: 100%; - height: auto; - /* padding: 0; */ - background: transparent; - border: none; - position: fixed; - top: 0px; - right: 0px; - pointer-events: none; - - overlay: none; - /* this alone would do the trick, but its not supported yet in all major browsers*/ -} - -.menu { - visibility: hidden; - position: absolute; - left: 0; - top: 100%; - opacity: 0; - transition: - visibility 0s ease, - opacity 0.3s ease, - transform 0.3s ease; -} - -.version-popover[popover]:popover-open ~ .menu { - opacity: 1; - visibility: visible; -} - -.trigger { - svg { - display: inline-block; - height: 0.75rem; - width: 0.75rem; - transform: rotate(0); - } -} - -.version-popover[popover]:popover-open ~ .trigger { - svg { - transform: rotate(180deg); - } -} - -#sidebar[popover] { - inset: unset; - width: unset; - border: none; - position: relative; - top: 0px; - left: 0px; - overlay: none; - height: 100%; - @apply min-w-64 bg-white border-gray-20 border-r-2 overflow-y-scroll h-full md:block; } /* When any dialog is open as a modal, lock the body scroll */ @@ -661,19 +55,8 @@ body:has(dialog[open]) { margin: 0; } -#mobile-tertiary-drawer { - max-width: 100%; - margin: 0; -} - body { scrollbar-gutter: stable; height: 100%; min-height: 100vh; } - -/* This has to stay at the end! */ -/* This is to prevent FOUC (flash of unstyled content) */ -html { - opacity: 1; -} diff --git a/apps/docs/styles/playground.css b/apps/docs/styles/playground.css new file mode 100644 index 000000000..dd6955bee --- /dev/null +++ b/apps/docs/styles/playground.css @@ -0,0 +1,271 @@ +@reference "./_theme.css"; +@import "tailwindcss/utilities.css" layer(utilities) source(none); +@import "./_shared-sources.css"; + +@source "../app/routes/TryRoute.{res,jsx}"; +@source "../../../packages/playground/src/**/*.{res,jsx}"; +@source "../../../packages/shared/src/**/*.{res,jsx}"; + +@layer components { + .playground-theme { + --playground-bg: var(--color-gray-100); + --playground-surface: var(--color-gray-100); + --playground-surface-border: transparent; + --playground-control-bg: var(--color-gray-100); + --playground-control-border: transparent; + --playground-elevated-bg: var(--color-gray-100); + --playground-overlay-bg: var(--color-gray-90); + --playground-overlay-border: var(--color-gray-70); + --playground-text-primary: var(--color-gray-20); + --playground-text-secondary: var(--color-gray-40); + --playground-border: var(--color-gray-60); + --playground-border-strong: var(--color-gray-80); + --playground-hover-surface: var(--color-gray-40); + --playground-hover-elevated: var(--color-gray-80); + --playground-active-surface: var(--color-gray-40); + --playground-input-bg: var(--color-gray-90); + --playground-placeholder: rgba(237, 240, 242, 0.5); + --playground-divider-bg: var(--color-gray-70); + --playground-divider-border: transparent; + --playground-divider-opacity: 0.3; + --playground-divider-hover-opacity: 0.5; + --playground-divider-handle: var(--color-gray-20); + --playground-scrollbar-track: var(--color-gray-100); + --playground-scrollbar-thumb: var(--color-gray-70); + --playground-scrollbar-thumb-hover: var(--color-gray-60); + --playground-toggle-track: #374151; + --playground-toggle-track-border: #4b5563; + --playground-toggle-thumb: var(--color-white); + --playground-toggle-label: #d1d5db; + --playground-tab-active: var(--color-white); + --playground-editor-bg: var(--color-gray-100); + --playground-editor-text: var(--color-gray-20); + --playground-editor-cursor: var(--color-orange); + --playground-editor-active-line: rgba(255, 255, 255, 0.02); + --playground-editor-gutter-bg: transparent; + --playground-editor-gutter-text: var(--color-gray-60); + --playground-editor-gutter-border: transparent; + --playground-editor-active-gutter-bg: transparent; + --playground-editor-active-gutter-text: var(--color-white); + --playground-editor-selection: rgba(255, 255, 255, 0.2); + --playground-editor-selection-match: #aafe661a; + --playground-editor-syntax-keyword: var(--color-berry-dark-50); + --playground-editor-syntax-variable: var(--color-gray-30); + --playground-editor-syntax-type: var(--color-orange-dark); + --playground-editor-syntax-string: var(--color-turtle-dark); + --playground-editor-syntax-comment: var(--color-gray-60); + --playground-editor-syntax-namespace-def: var(--color-orange); + --playground-editor-syntax-namespace: var(--color-water-dark); + --playground-editor-syntax-property: var(--color-ocean-dark); + --playground-editor-syntax-attribute: #bcc9ab; + } + + .playground-theme-light { + --playground-bg: var(--color-gray-5); + --playground-surface: var(--color-white); + --playground-surface-border: var(--color-gray-20); + --playground-control-bg: #f3f5f7; + --playground-control-border: var(--color-gray-20); + --playground-elevated-bg: var(--color-white); + --playground-overlay-bg: var(--color-white); + --playground-overlay-border: var(--color-gray-30); + --playground-text-primary: var(--color-gray-80); + --playground-text-secondary: var(--color-gray-80); + --playground-border: var(--color-gray-30); + --playground-border-strong: var(--color-gray-60); + --playground-hover-surface: var(--color-gray-20); + --playground-hover-elevated: var(--color-gray-10); + --playground-active-surface: var(--color-gray-20); + --playground-input-bg: var(--color-gray-10); + --playground-placeholder: var(--color-gray-60); + --playground-divider-bg: #f5f7f9; + --playground-divider-border: var(--color-gray-20); + --playground-divider-opacity: 1; + --playground-divider-hover-opacity: 1; + --playground-divider-handle: var(--color-gray-40); + --playground-scrollbar-track: #f5f7f9; + --playground-scrollbar-thumb: var(--color-gray-30); + --playground-scrollbar-thumb-hover: var(--color-gray-40); + --playground-toggle-track: var(--color-gray-30); + --playground-toggle-track-border: var(--color-gray-40); + --playground-toggle-thumb: var(--color-white); + --playground-toggle-label: var(--color-gray-80); + --playground-tab-active: var(--color-gray-80); + --playground-editor-bg: var(--color-white); + --playground-editor-text: var(--color-gray-80); + --playground-editor-cursor: var(--color-sky-70); + --playground-editor-active-line: rgba(34, 88, 195, 0.07); + --playground-editor-gutter-bg: var(--color-gray-10); + --playground-editor-gutter-text: var(--color-gray-60); + --playground-editor-gutter-border: var(--color-gray-20); + --playground-editor-active-gutter-bg: var(--color-gray-20); + --playground-editor-active-gutter-text: var(--color-gray-80); + --playground-editor-selection: rgba(34, 88, 195, 0.22); + --playground-editor-selection-match: rgba(94, 94, 222, 0.18); + --playground-editor-syntax-keyword: var(--color-berry); + --playground-editor-syntax-variable: var(--color-gray-80); + --playground-editor-syntax-type: var(--color-orange); + --playground-editor-syntax-string: var(--color-turtle); + --playground-editor-syntax-comment: var(--color-gray-60); + --playground-editor-syntax-namespace-def: var(--color-orange); + --playground-editor-syntax-namespace: var(--color-water); + --playground-editor-syntax-property: var(--color-sky); + --playground-editor-syntax-attribute: #4f5f78; + } + + .playground-main { + background-color: var(--playground-bg); + color: var(--playground-text-secondary); + } + + .playground-surface { + background-color: var(--playground-surface); + border-color: var(--playground-surface-border); + } + + .playground-text-primary { + color: var(--playground-text-primary) !important; + } + + .playground-text-secondary { + color: var(--playground-text-secondary) !important; + } + + .playground-select { + background-color: var(--playground-elevated-bg); + border-color: var(--playground-border); + color: var(--playground-text-primary) !important; + } + + .playground-selection-option { + background-color: var(--playground-elevated-bg); + border: 1px solid var(--playground-border); + color: var(--playground-text-primary); + } + + .playground-selection-option:hover { + background-color: var(--playground-hover-surface); + } + + .playground-selection-option-active { + background-color: var(--color-fire); + border-color: var(--color-fire); + color: var(--color-white); + } + + .playground-selection-option-active:hover { + background-color: var(--color-fire); + } + + .playground-overlay { + background-color: var(--playground-overlay-bg); + border-color: var(--playground-overlay-border); + color: var(--playground-text-primary); + } + + .playground-field { + border-color: var(--playground-border); + } + + .playground-field-active { + border-color: var(--playground-text-primary); + } + + .playground-icon-button:hover { + background-color: var(--playground-hover-surface); + } + + .playground-input { + background-color: var(--playground-input-bg); + color: var(--playground-text-primary); + } + + .playground-input::placeholder { + color: var(--playground-placeholder); + } + + .playground-chip-active { + background-color: var(--playground-active-surface); + } + + .playground-suggestion-active { + background-color: var(--playground-active-surface); + } + + .playground-control-panel { + background-color: var(--playground-control-bg); + border-bottom: 1px solid var(--playground-control-border); + } + + .playground-toast { + background-color: var(--playground-overlay-bg); + border-color: var(--playground-overlay-border); + color: var(--playground-text-primary); + } + + .playground-toggle-label { + color: var(--playground-toggle-label); + } + + .playground-toggle-track { + background-color: var(--playground-toggle-track); + border-color: var(--playground-toggle-track-border); + } + + .playground-toggle-track::after { + background-color: var(--playground-toggle-thumb); + border-color: var(--playground-toggle-track-border); + } + + .playground-tab-active { + color: var(--playground-tab-active); + border-top-color: var(--color-sky-70) !important; + } + + .playground-editor-shell { + background-color: var(--playground-editor-bg); + } + + .playground-divider { + background-color: var(--playground-divider-bg); + border: 1px solid var(--playground-divider-border); + opacity: var(--playground-divider-opacity); + } + + .playground-divider:hover { + opacity: var(--playground-divider-hover-opacity); + background-color: var(--playground-hover-surface); + } + + .playground-divider-handle { + color: var(--playground-divider-handle); + } +} + +@layer components { + .playground-scrollbar { + scrollbar-width: thin; + scrollbar-color: var(--playground-scrollbar-thumb) + var(--playground-scrollbar-track); + scrollbar-gutter: stable; /* Reserve only at the scrollbar edge to avoid padding shifts */ + } + + .playground-scrollbar::-webkit-scrollbar { + width: 0.65rem; + height: 0.65rem; + } + + .playground-scrollbar::-webkit-scrollbar-track { + background: var(--playground-scrollbar-track); + } + + .playground-scrollbar::-webkit-scrollbar-thumb { + background-color: var(--playground-scrollbar-thumb); + border-radius: 9999px; + border: 2px solid var(--playground-scrollbar-track); + } + + .playground-scrollbar::-webkit-scrollbar-thumb:hover { + background-color: var(--playground-scrollbar-thumb-hover); + } +} diff --git a/apps/docs/styles/search.css b/apps/docs/styles/search.css index fa6ddef6a..9ff7d5318 100644 --- a/apps/docs/styles/search.css +++ b/apps/docs/styles/search.css @@ -1,3 +1,3 @@ /* Reuse tokens and utilities without duplicating the global theme or preflight. */ -@reference "./main.css"; +@reference "./_theme.css"; @import "./_docsearch.css" layer(base); diff --git a/apps/docs/styles/test-utilities.css b/apps/docs/styles/test-utilities.css new file mode 100644 index 000000000..096e01272 --- /dev/null +++ b/apps/docs/styles/test-utilities.css @@ -0,0 +1,12 @@ +@reference "./_theme.css"; +@import "tailwindcss/utilities.css" layer(utilities) source(none); +@import "./_shared-sources.css"; + +/* Tests render every route together, so their final utility sheet needs the complete union. */ +@source "../app/**/*.{res,jsx}"; +@source "../src/**/*.{res,jsx,js,mjs}"; +@source "../markdown-pages/**/*.mdx"; +@source "../../../packages/shared/src/**/*.{res,jsx}"; +@source "../../../packages/playground/src/**/*.{res,jsx}"; +@source "../__tests__/**/*.{res,jsx}"; +@source "../test-utils/**/*.{res,jsx}"; diff --git a/apps/docs/vitest.setup.mjs b/apps/docs/vitest.setup.mjs index acb31a714..7c5201827 100644 --- a/apps/docs/vitest.setup.mjs +++ b/apps/docs/vitest.setup.mjs @@ -1,4 +1,8 @@ import "./styles/main.css"; +import "./styles/homepage.css"; +import "./styles/content.css"; +import "./styles/playground.css"; +import "./styles/test-utilities.css"; import "./styles/_hljs.css"; import "./styles/utils.css"; import "./styles/test-overrides.css";