mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-08-31 02:41:44 +00:00
* docs: add the shared page components Adds the six React snippets the rebuilt documentation pages compose against, plus the styles they need. Nothing imports them yet, so this lands with no user-visible change and no navigation churn. - DocsVideo / ShowcaseWall — the film player and the Showcase grid - LiveReferenceProject — embeds the Reference Project via <hyperframes-player> - WorkflowChooser, AgentAction, and the two grid snippets The scrub indicator is a timecode bubble rather than a thumbnail. Mounting a second <video> with the same src to drive a preview frame made every page carrying a film download the whole file twice, which is not worth a thumbnail. * docs: add the Reference Project example One real 10-second project the documentation can point at instead of describing a hypothetical one: a live capture of example.com, synthesised narration, and caption timings measured from that narration. It passes its own gates — `hyperframes lint` clean, `hyperframes check` passed, 28/28 text checks WCAG AA. No page imports it yet, so this lands without touching navigation. Only the two WAV masters exceed the repository's 500 KB non-LFS limit, so only those go through LFS. The MP3 stings and the capture PNG stay plain, which keeps the example usable after a clone without `git lfs pull`. `bun run docs:bundle-reference` regenerates the single-file embed the Introduction page loads from the CDN. * docs: keep the Reference Project verification report The Examples page links this file twice — as "What changed after review" and as "The real verification report" — in the section that makes the project's brief, source, revision notes, and checks public end to end. It is a published artifact, not leftover scaffolding. * docs: state the Reference Project embed's isolation contract The composition is fetched from the CDN and handed to the player as a blob: URL, which inherits the docs origin, and <hyperframes-player> sandboxes its iframe with allow-scripts + allow-same-origin. So the embedded composition runs with script access to this origin. That is a consequence of how the player works — it drives seeking through the iframe's document, which a cross-origin frame does not expose — not something this component can fix. Serving the CDN URL directly would isolate the frame and break playback. The guard is therefore the source, so the comment says so out loud: src must stay a first-party path we publish, never user- or community-supplied HTML. * fix(docs): resolve reduced-motion on the first render, and the embed's dep gap Both defects from Rames Jusso's review on #2977. Neither is visible today because nothing imports these files yet, which is what makes them cheap now. **Reduced motion resolved one paint too late, in all three grids.** `useState(false)` plus a `matchMedia` read in an effect meant the first committed render always emitted `<video src autoPlay loop>`; a reduce-motion visitor had 6 + 8 + 4 tiles already fetching before the attributes came off. `autoPlay` also overrides `preload="metadata"`, so those were the files, not metadata probes — and dropping `src` with no following `load()` is not a reliable abort. A lazy initializer knows the answer on the first render. **LiveReferenceProject never sent the initial variables.** The sending effect read `playerRef.current`, assigned by the effect above it on the commit where `compositionSrc` lands — a commit with nothing in the sending effect's dep array. So it ran once against a null ref and never again. It looked correct only because the three defaults match what the composition already renders. Also from the same review: - The object URL could outlive its revoke: once the body resolves, `abort()` no longer stops the chain, so the blob could be minted after cleanup ran with `objectUrl` still undefined. Same `cancelled` guard the effect above uses. - `postMessage` targeted `"*"` while the isolation comment argues the frame is same-origin. Naming `window.location.origin` turns that prose guard into an enforced one. - Nothing reached a terminal state when the player script never arrived: `whenDefined()` does not reject, and a later mount reuses the tag without its error listener. A CSP rule or content blocker never fires `error` at all. A deadline covers every path instead of sitting on "Loading…" forever. - `loadFailed` was never cleared, so one transient failure stuck. - The README claimed a clone works without `git lfs pull`. It does for the visuals; both WAVs are pointers and they are the bed and the voiceover, so the captions would play over silence. Says so now. - The bundler stripped trailing whitespace document-wide while inlining the runtime, which reaches inside script template literals where those spaces are data. It also assumed a literal `<head>` and would silently ship an embed with no `<base>`. Strip removed, anchor asserted. Copilot's five "missing hook imports" comments are wrong — Mintlify pre-injects the hooks, and `TemplateCard.jsx`, cited as the counter-example, uses the `export function` form the same page says is unsupported. * fix(docs): stop preview loops when Reduce Motion is turned on mid-session Miguel's changes-requested on #2977. He is right about the mechanism: dropping `src` and `autoPlay` through React props neither pauses a playing element nor aborts its selected resource, so a visitor who turned Reduce Motion on with the page already open kept every tile running. Measured in a browser rather than argued from the spec, same clip, same sequence: playing paused=false t=2.90 readyState=4 networkState=1 React props only paused=false t=3.90 readyState=4 networkState=1 + pause/removeAttr/load paused=true t=0 readyState=0 networkState=0 The middle row is the bug: time still advancing, resource still held. Rames' follow-up asked for a remount-to-poster instead, because a video that ends with `src` removed holds its last frame and `poster` only paints before playback begins. `load()` covers that too — it drops readyState to HAVE_NOTHING, which is precisely the state that paints the poster. Confirmed side by side on screen: the React-props-only tile sits on an arbitrary mid-clip frame, the pause/load tile shows the poster again. So no remount is needed. The guard cannot be shared as code — Mintlify compiles each snippet in isolation and forbids one importing another — so it is copy-pasted into all three grids. A duplicated invariant is the kind that rots, and a rendering test would mean adding React to a repo that only carries it inside packages/studio, plus mocking Mintlify's hook-injection contract with a mock that can stay green while the page breaks. `scripts/check-docs-snippet-motion.mjs` asserts the source instead, wired into `bun run lint`, with unit tests covering both edges. That gate immediately found `docs/snippets/TemplateCard.jsx`: autoplays with no reduced-motion handling at all. It is imported by zero pages, and it uses the `export function` form Mintlify's constraints page says is unsupported, so it would not work if it were. Deleted rather than fixed. * refactor(scripts): split the motion guard into named predicates fallow flagged findMotionGuardViolations at CRAP 42 — a finding this branch introduced, so it gets fixed rather than suppressed, same as the catalog generator earlier in the stack. The two conditions are now their own predicates behind a small requirements table, which drops the branch count under the threshold and makes each rule readable on its own line. Same output, same tests. * fix(docs): move the stop effect above ShowcaseWall's early return Rames' changes-requested on `e1a03c63`. The effect I added in the previous commit landed below `if (open) return`, so `ShowcaseWall` called five hooks on the grid render and four once a tile was open. That is a conditional hook: clicking a tile — the component's primary interaction — threw "Rendered fewer hooks than expected". Worth naming why it landed in one of three. `workflow-chooser` and `advanced-path-grid` have no early return, so the same paste position was fine there. `ShowcaseWall` is the only one with a conditional return and it got the same copy. That is the duplication cost this script's own header warns about, showing up in the commit that added the script. **The bespoke gate could not have caught it, and now the generic one does.** `.oxlintrc.json` already loaded the `react` plugin and never excluded `docs/` — only `.prettierignore` does, which is why formatting is not a finding here but linting reaches these files. Naming the two hook rules in an override scoped to `docs/snippets/**` reports this bug directly, and also reports the `compositionSrc` dependency gap from round one that was found by reading. Verified both ways: reintroducing the conditional hook produces `react-hooks(rules-of-hooks)`, and `bunx oxlint .` is clean repo-wide, so nothing lit up in `packages/studio`. **Two holes in the script itself, both from the same review.** It matched whole files while the invariant is per component, so a second unguarded grid in `docs-video.jsx` would have ridden in on `ShowcaseWall`'s guard. It now splits by component. That immediately surfaced the distinction between a component that decides to autoplay and one that forwards its caller's `autoPlay` prop — `DocsVideo` only ever plays because a reader clicked, so it does not owe a preference check. And `readsPreferenceLazily` never tied its halves: any lazy initializer plus the media-query string anywhere in the file passed, which is the original bug satisfying the check written to prevent it. The query now has to sit inside the initializer's own expression. Both holes have tests. fallow is clean at 0 introduced. * fix(scripts): close the two silent gaps in the motion gate Both from Rames' approval pass on #2977, and both found by running these functions rather than reading them. Both fail the same quiet way: a component `autoplays` misses is filtered out before any requirement runs, so the gate reports zero problems instead of a violation. `autoplays` had become narrower than the version it replaced. Excluding the `autoPlay={autoPlay}` passthrough was right, but the replacement only matched `autoPlay={` or `autoPlay` alone on a line, so `<video autoPlay muted />` on one line slipped through. Restored the old breadth. Two things are stripped first rather than one — the passthrough, and the prop's own default in the signature, which is a declaration and not a use. Without the second strip, `DocsVideo` is asked to own a decision it only forwards. `splitComponents` anchored on `^export`, so anything not exported folded into the previous exported component and inherited its guard. Same hole as the whole-file match, narrowed from file scope to non-export scope. The anchor no longer requires `export`. Ten tests now, including his exact examples for both. * docs: remove the live-composition embed and its build apparatus The Introduction no longer carries the embed (removed in #2979), and nothing else used any of this: the 200-line snippet, 26 CSS rules, the bundler that built the single-file HTML for the CDN, its npm script, and the README section explaining how to regenerate it. The Reference Project itself stays — Examples, Developers, and Go further all link to it as the worked example; only the interactive embed of it is gone. This also retires the isolation contract I documented two rounds ago. That comment existed because the embed handed CDN HTML to a same-origin blob; with the embed gone there is no such surface to reason about, which is a better outcome than a comment explaining why it was acceptable. * docs: remove the AgentAction snippet Its only consumer is gone. The Quickstart now shows the agent instruction in a plain fence instead, because this component rendered a Copy button and never displayed the request — a reader copied text they could not read, which is the wrong shape for the one affordance a non-technical visitor depends on. Mintlify fences already carry a copy button and show their contents.
1119 lines
25 KiB
CSS
1119 lines
25 KiB
CSS
/* HyperFrames Design System — Mintlify Theme Overrides */
|
|
|
|
@import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&display=swap");
|
|
|
|
@font-face {
|
|
font-family: "TT Norms Pro";
|
|
src: url("https://www-static-assets.heygen.com/fonts/tt-norms/TT_Norms_Pro_Normal.woff2")
|
|
format("woff2");
|
|
font-weight: 400;
|
|
font-style: normal;
|
|
font-display: swap;
|
|
}
|
|
|
|
@font-face {
|
|
font-family: "TT Norms Pro";
|
|
src: url("https://www-static-assets.heygen.com/fonts/tt-norms/TT_Norms_Pro_Medium.woff2")
|
|
format("woff2");
|
|
font-weight: 500;
|
|
font-style: normal;
|
|
font-display: swap;
|
|
}
|
|
|
|
@font-face {
|
|
font-family: "TT Norms Pro";
|
|
src: url("https://www-static-assets.heygen.com/fonts/tt-norms/TT_Norms_Pro_DemiBold.woff2")
|
|
format("woff2");
|
|
font-weight: 600;
|
|
font-style: normal;
|
|
font-display: swap;
|
|
}
|
|
|
|
@font-face {
|
|
font-family: "TT Norms Pro";
|
|
src: url("https://www-static-assets.heygen.com/fonts/tt-norms/TT_Norms_Pro_Bold.woff2")
|
|
format("woff2");
|
|
font-weight: 700;
|
|
font-style: normal;
|
|
font-display: swap;
|
|
}
|
|
|
|
/* ── Light mode (default) ── */
|
|
/*
|
|
* NOTE: All custom variables are namespaced with `--hf-` to avoid
|
|
* colliding with Mintlify's Tailwind color system, which owns names
|
|
* like `--background-light` / `--background-dark` and uses them via
|
|
* `rgb(var(--background-light)/<alpha>)`. Overriding those with hex
|
|
* values breaks the rgb() call and makes surfaces transparent (e.g.
|
|
* the "Copy page" dropdown panel in light mode).
|
|
*/
|
|
|
|
:root {
|
|
--hf-background: #f6f5f1;
|
|
--hf-background-light: #ffffff;
|
|
--hf-surface2: #eeedea;
|
|
--hf-border-color: #e0dfdb;
|
|
--hf-border-color-light: #d0cfcb;
|
|
--hf-text: #1a1a1a;
|
|
--hf-text-secondary: #6b6b6b;
|
|
--hf-text-tertiary: #999999;
|
|
--hf-heading: #0a0a0a;
|
|
--hf-code-bg: #ffffff;
|
|
--hf-ui-border: #eeeeee;
|
|
--hf-sidebar-rail: #eeeeee;
|
|
--hf-sidebar-text: #707070;
|
|
--hf-floating-input-shadow: 0 8px 28px rgba(10, 10, 10, 0.08), 0 1px 4px rgba(10, 10, 10, 0.08);
|
|
--mintlify-slot-header-height: 3rem;
|
|
--hf-brand: #16785b;
|
|
--hf-brand-soft: rgba(22, 120, 91, 0.1);
|
|
--hf-video-accent: #3ce6ac;
|
|
|
|
--hf-accent-green: #1a7a0a;
|
|
--hf-accent-green-light: rgba(26, 122, 10, 0.07);
|
|
--hf-accent-green-border: rgba(26, 122, 10, 0.25);
|
|
--hf-accent-blue: #2563eb;
|
|
--hf-accent-blue-light: rgba(37, 99, 235, 0.06);
|
|
--hf-accent-blue-border: rgba(37, 99, 235, 0.2);
|
|
--hf-accent-purple: #7c3aed;
|
|
--hf-accent-purple-light: rgba(124, 58, 237, 0.06);
|
|
--hf-accent-purple-border: rgba(124, 58, 237, 0.2);
|
|
|
|
--hf-selection-bg: rgba(128, 128, 128, 0.2);
|
|
}
|
|
|
|
/* ── Dark mode ── */
|
|
|
|
.dark,
|
|
[data-theme="dark"] {
|
|
--hf-background: #0a0a0a;
|
|
--hf-background-light: #141414;
|
|
--hf-surface2: #1a1a1a;
|
|
--hf-border-color: #2a2a2a;
|
|
--hf-border-color-light: #3a3a3a;
|
|
--hf-text: #e5e5e5;
|
|
--hf-text-secondary: #a0a0a0;
|
|
--hf-text-tertiary: #666666;
|
|
--hf-heading: #f5f5f5;
|
|
--hf-code-bg: #141414;
|
|
--hf-ui-border: #2a2a2a;
|
|
--hf-sidebar-rail: #2a2a2a;
|
|
--hf-sidebar-text: #a0a0a0;
|
|
--hf-floating-input-shadow: 0 8px 28px rgba(0, 0, 0, 0.4), 0 1px 4px rgba(0, 0, 0, 0.4);
|
|
--hf-brand: #3ce6ac;
|
|
--hf-brand-soft: rgba(60, 230, 172, 0.12);
|
|
|
|
--hf-accent-green: #22c55e;
|
|
--hf-accent-green-light: rgba(34, 197, 94, 0.1);
|
|
--hf-accent-green-border: rgba(34, 197, 94, 0.3);
|
|
--hf-accent-blue: #3b82f6;
|
|
--hf-accent-blue-light: rgba(59, 130, 246, 0.1);
|
|
--hf-accent-blue-border: rgba(59, 130, 246, 0.3);
|
|
--hf-accent-purple: #a78bfa;
|
|
--hf-accent-purple-light: rgba(167, 139, 250, 0.1);
|
|
--hf-accent-purple-border: rgba(167, 139, 250, 0.3);
|
|
|
|
--hf-selection-bg: rgba(255, 255, 255, 0.15);
|
|
}
|
|
|
|
/* ── Typography ── */
|
|
|
|
body {
|
|
font-family:
|
|
"TT Norms Pro",
|
|
"Inter",
|
|
-apple-system,
|
|
BlinkMacSystemFont,
|
|
"Segoe UI",
|
|
sans-serif;
|
|
}
|
|
|
|
h1,
|
|
h2,
|
|
h3,
|
|
h4,
|
|
h5,
|
|
h6 {
|
|
font-family:
|
|
"TT Norms Pro",
|
|
"Inter",
|
|
-apple-system,
|
|
BlinkMacSystemFont,
|
|
"Segoe UI",
|
|
sans-serif;
|
|
}
|
|
|
|
h1,
|
|
h2 {
|
|
letter-spacing: -0.02em;
|
|
}
|
|
|
|
h3 {
|
|
letter-spacing: -0.01em;
|
|
}
|
|
|
|
/* ── Workflow routing ── */
|
|
.hf-workflow-routes {
|
|
margin: 1.5rem 0;
|
|
border-top: 1px solid var(--hf-border-color);
|
|
}
|
|
|
|
.hf-workflow-route {
|
|
display: grid;
|
|
grid-template-columns: 11rem minmax(0, 1fr);
|
|
gap: 1rem;
|
|
align-items: center;
|
|
padding: 1rem 0;
|
|
border-bottom: 1px solid var(--hf-border-color);
|
|
color: inherit;
|
|
text-decoration: none;
|
|
}
|
|
|
|
.hf-workflow-route:hover .hf-workflow-route-title {
|
|
color: var(--hf-accent-green);
|
|
}
|
|
|
|
.hf-workflow-route video {
|
|
display: block;
|
|
width: 100%;
|
|
aspect-ratio: 16 / 9;
|
|
margin: 0;
|
|
border-radius: 0.5rem;
|
|
background: #000;
|
|
object-fit: cover;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.hf-workflow-route-title {
|
|
display: block;
|
|
color: var(--hf-heading);
|
|
font-weight: 650;
|
|
line-height: 1.35;
|
|
transition: color 120ms ease;
|
|
}
|
|
|
|
.hf-workflow-route-copy {
|
|
display: block;
|
|
margin-top: 0.3rem;
|
|
color: var(--hf-text-secondary);
|
|
font-size: 0.9rem;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
@media (max-width: 639px) {
|
|
.hf-workflow-route {
|
|
grid-template-columns: 7rem minmax(0, 1fr);
|
|
gap: 0.8rem;
|
|
}
|
|
}
|
|
|
|
/* Code font — IBM Plex Mono (loaded via Google Fonts above).
|
|
*
|
|
* Deliberately scoped to real code elements. The previous rule also matched
|
|
* [class*="code"] / [class*="Code"], which caught any element whose class
|
|
* merely contained that substring — including every Accordion, whose wrapper
|
|
* carries the Tailwind utility `dark:bg-codeblock`. That set the whole
|
|
* accordion (and, by inheritance, its title and prose) in monospace, so plain
|
|
* sentences rendered like terminal output. Syntax tokens inside `pre` inherit
|
|
* from `pre`, so they do not need a selector of their own. */
|
|
code,
|
|
pre,
|
|
pre code,
|
|
kbd,
|
|
samp {
|
|
font-family: "IBM Plex Mono", "SF Mono", "Fira Code", monospace;
|
|
}
|
|
|
|
/* ── Selection ── */
|
|
|
|
::selection {
|
|
background: var(--hf-selection-bg);
|
|
}
|
|
|
|
/* ── Links ── */
|
|
|
|
#content-area :where(p, li, td, blockquote) a:not([class]) {
|
|
border-bottom: 1px solid color-mix(in srgb, var(--hf-brand) 72%, transparent);
|
|
color: inherit;
|
|
text-decoration: none;
|
|
}
|
|
|
|
#content-area :where(p, li, td, blockquote) a:not([class]):hover {
|
|
border-bottom-color: var(--hf-brand);
|
|
color: var(--hf-brand);
|
|
}
|
|
|
|
/* ── Documentation navigation ── */
|
|
|
|
/*
|
|
* Keep Aspen's full-width header, but place its desktop controls on one row:
|
|
* logo → section tabs → search → actions. Mobile keeps Mintlify's layout.
|
|
*/
|
|
@media (min-width: 1024px) {
|
|
#navbar {
|
|
height: 3rem;
|
|
}
|
|
|
|
#navbar > div:has(.nav-tabs) {
|
|
display: grid;
|
|
grid-template-columns: max-content max-content minmax(10.25rem, 1fr) max-content;
|
|
column-gap: 1rem;
|
|
align-items: center;
|
|
height: 3rem;
|
|
}
|
|
|
|
#navbar > div:has(.nav-tabs) > .relative,
|
|
#navbar > div:has(.nav-tabs) > .relative > div:first-child,
|
|
#navbar > div:has(.nav-tabs) > .relative > div:first-child > div:first-child {
|
|
display: contents;
|
|
}
|
|
|
|
#navbar > div:has(.nav-tabs) > .relative > div:first-child > div:first-child > div:first-child {
|
|
grid-column: 1;
|
|
grid-row: 1;
|
|
}
|
|
|
|
#navbar div:has(> .nav-tabs) {
|
|
grid-column: 2;
|
|
grid-row: 1;
|
|
height: 3rem;
|
|
padding: 0;
|
|
}
|
|
|
|
#navbar .nav-tabs {
|
|
align-items: center;
|
|
gap: 0.25rem;
|
|
height: 3rem;
|
|
}
|
|
|
|
/* Use the same quiet pill treatment as Mintlify's own documentation. */
|
|
#navbar .nav-tabs-item {
|
|
height: 2.25rem !important;
|
|
padding: 0 0.8rem;
|
|
border-radius: 999px;
|
|
color: var(--hf-text);
|
|
transition:
|
|
background-color 140ms ease,
|
|
color 140ms ease;
|
|
}
|
|
|
|
#navbar .nav-tabs-item:hover {
|
|
background: color-mix(in srgb, var(--hf-heading) 6%, transparent);
|
|
color: var(--hf-heading);
|
|
}
|
|
|
|
#navbar .nav-tabs-item.text-primary {
|
|
background: color-mix(in srgb, var(--hf-heading) 9%, transparent);
|
|
color: var(--hf-heading) !important;
|
|
}
|
|
|
|
#navbar .nav-tabs-item > .absolute.bottom-0 {
|
|
display: none;
|
|
}
|
|
|
|
#navbar div:has(> #search-bar-entry) {
|
|
grid-column: 3;
|
|
grid-row: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
#navbar .topbar-right-container {
|
|
grid-column: 4;
|
|
grid-row: 1;
|
|
}
|
|
|
|
#sidebar-content {
|
|
top: 3rem !important;
|
|
height: calc(100vh - 3rem) !important;
|
|
}
|
|
|
|
/* Keep the repository compact, but retain its useful live star count. */
|
|
#navbar a[title="heygen-com/hyperframes"] {
|
|
width: 7.5rem;
|
|
min-width: 7.5rem;
|
|
height: 2.25rem;
|
|
padding: 0 0.75rem;
|
|
justify-content: center;
|
|
}
|
|
|
|
#navbar a[title="heygen-com/hyperframes"] > span.truncate {
|
|
display: none;
|
|
}
|
|
|
|
#navbar a[title="heygen-com/hyperframes"] > span:not(.truncate) {
|
|
display: flex !important;
|
|
color: var(--hf-heading);
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
/*
|
|
* Mintlify removes the live count for roughly one animation frame during
|
|
* client-side navigation. Keep a truthful placeholder in its place so the
|
|
* control does not collapse while the live count reloads.
|
|
*/
|
|
#navbar a[title="heygen-com/hyperframes"]:not(:has(> span:not(.truncate)))::after {
|
|
color: var(--hf-heading);
|
|
content: "★";
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
}
|
|
|
|
/* The native gutter was almost 15px wide. Keep the rail quiet and compact. */
|
|
#navigation-items {
|
|
scrollbar-color: color-mix(in srgb, var(--hf-text) 30%, transparent) transparent;
|
|
scrollbar-gutter: auto !important;
|
|
scrollbar-width: thin;
|
|
}
|
|
|
|
#navigation-items::-webkit-scrollbar {
|
|
width: 5px;
|
|
}
|
|
|
|
#navigation-items::-webkit-scrollbar-track {
|
|
background: transparent;
|
|
}
|
|
|
|
#navigation-items::-webkit-scrollbar-thumb {
|
|
border-radius: 999px;
|
|
background: color-mix(in srgb, var(--hf-text) 30%, transparent);
|
|
}
|
|
|
|
#navigation-items::-webkit-scrollbar-thumb:hover {
|
|
background: color-mix(in srgb, var(--hf-text) 46%, transparent);
|
|
}
|
|
|
|
/* Preserve a useful search field on smaller desktop widths. */
|
|
@media (min-width: 1024px) and (max-width: 1199px) {
|
|
#navbar #assistant-entry {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
/* Use main's quieter structural border color across Aspen surfaces. */
|
|
#body-content [class*="border-gray-200"],
|
|
#body-content [class*="border-gray-100"] {
|
|
border-color: var(--hf-ui-border) !important;
|
|
}
|
|
|
|
/* Keep the floating agent input noticeable against the page background. */
|
|
#body-content .chat-assistant-floating-input > div > div {
|
|
border-color: var(--hf-border-color-light) !important;
|
|
background-color: var(--hf-background-light) !important;
|
|
box-shadow: var(--hf-floating-input-shadow);
|
|
}
|
|
|
|
/*
|
|
* Match the navigation rhythm and active-page rail used on the main docs.
|
|
* Each link owns one rail segment, so the current page can highlight only
|
|
* its segment. These rules are visual only; groups remain non-collapsible.
|
|
*/
|
|
#navigation-items .sidebar-group-header {
|
|
color: var(--hf-text);
|
|
font-size: 0.875rem;
|
|
font-weight: 500;
|
|
line-height: 1.25rem;
|
|
}
|
|
|
|
#navigation-items .sidebar-group {
|
|
padding-left: 0;
|
|
}
|
|
|
|
#navigation-items .sidebar-group::before {
|
|
content: none;
|
|
}
|
|
|
|
#navigation-items .sidebar-group > li {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
#navigation-items .sidebar-group > li + li {
|
|
margin-top: 0 !important;
|
|
}
|
|
|
|
#navigation-items .sidebar-group > li > a {
|
|
width: calc(100% - 1rem);
|
|
margin-left: 1rem;
|
|
padding: 0.375rem 0.75rem 0.375rem 1rem;
|
|
border-left: 1px solid var(--hf-sidebar-rail);
|
|
border-radius: 0;
|
|
background: transparent !important;
|
|
color: var(--hf-sidebar-text);
|
|
}
|
|
|
|
#navigation-items .sidebar-group > li > a:hover {
|
|
background: transparent !important;
|
|
color: var(--hf-heading);
|
|
}
|
|
|
|
#navigation-items .sidebar-group > li[data-active="true"] > a,
|
|
#navigation-items .sidebar-group > li[data-active-nav-item="true"] > a,
|
|
#navigation-items .sidebar-group > li > a[aria-current="page"] {
|
|
border-left-color: currentColor;
|
|
color: var(--hf-heading);
|
|
}
|
|
|
|
/* Aspen inserts horizontal separators; main uses the same space without a rule. */
|
|
#navigation-items div:has(> .sidebar-group) + div {
|
|
height: 2rem;
|
|
padding: 0;
|
|
}
|
|
|
|
#navigation-items div:has(> .sidebar-group) + div > * {
|
|
display: none;
|
|
}
|
|
|
|
/*
|
|
* Aspen assumes a 96px header and fades scrolled navigation before it reaches
|
|
* that boundary. Our header is 48px; align the sticky sidebar to it and let
|
|
* links clip exactly at the header instead of disappearing early.
|
|
*/
|
|
#navigation-items,
|
|
#navigation-items [data-id] {
|
|
-webkit-mask-image: none !important;
|
|
mask-image: none !important;
|
|
}
|
|
|
|
/* Catalog texture examples */
|
|
|
|
.hf-texture-preview-panel {
|
|
display: grid;
|
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
gap: 12px;
|
|
margin: 24px 0 40px;
|
|
}
|
|
|
|
.hf-texture-preview-card {
|
|
min-width: 0;
|
|
border: 1px solid rgba(127, 127, 127, 0.22);
|
|
border-radius: 8px;
|
|
padding: 16px;
|
|
background: #101010;
|
|
}
|
|
|
|
.hf-texture-preview-label {
|
|
margin-bottom: 12px;
|
|
color: rgba(255, 255, 255, 0.6);
|
|
font-size: 12px;
|
|
line-height: 1.2;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.hf-texture-preview-shadow {
|
|
filter: drop-shadow(1px 2px 1px rgba(0, 0, 0, 0.48));
|
|
}
|
|
|
|
.hf-texture-preview-word {
|
|
color: #fff;
|
|
font-family: Impact, "Arial Black", sans-serif;
|
|
font-size: 42px;
|
|
line-height: 0.9;
|
|
letter-spacing: 0;
|
|
text-align: center;
|
|
text-transform: uppercase;
|
|
white-space: nowrap;
|
|
-webkit-mask-image: var(--mask-url);
|
|
mask-image: var(--mask-url);
|
|
-webkit-mask-size: cover;
|
|
mask-size: cover;
|
|
-webkit-mask-position: center;
|
|
mask-position: center;
|
|
-webkit-mask-mode: luminance;
|
|
mask-mode: luminance;
|
|
}
|
|
|
|
.hf-texture-animate-demo {
|
|
border: 1px solid rgba(127, 127, 127, 0.22);
|
|
border-radius: 8px;
|
|
padding: 22px;
|
|
margin: 20px 0 24px;
|
|
background: linear-gradient(90deg, rgba(255, 255, 255, 0.54) 1px, transparent 1px), #f1f1f1;
|
|
background-size: 44px 100%;
|
|
}
|
|
|
|
.hf-texture-animate-meta {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 12px;
|
|
margin-bottom: 18px;
|
|
}
|
|
|
|
.hf-texture-animate-label {
|
|
min-width: 0;
|
|
color: rgba(24, 24, 24, 0.62);
|
|
font-size: 13px;
|
|
line-height: 1.2;
|
|
}
|
|
|
|
.hf-texture-animate-class {
|
|
max-width: 62%;
|
|
padding: 2px 6px;
|
|
border: 1px solid rgba(24, 24, 24, 0.12);
|
|
border-radius: 6px;
|
|
background: rgba(255, 255, 255, 0.5);
|
|
color: rgba(24, 24, 24, 0.78);
|
|
font-size: 11px;
|
|
line-height: 1.3;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.hf-texture-animate-shadow {
|
|
filter: drop-shadow(1px 2px 1px rgba(0, 0, 0, 0.48));
|
|
}
|
|
|
|
.hf-texture-animate-word {
|
|
color: #181818;
|
|
font-family: Impact, "Arial Black", sans-serif;
|
|
font-size: 92px;
|
|
line-height: 0.9;
|
|
letter-spacing: 0;
|
|
text-align: center;
|
|
text-transform: uppercase;
|
|
white-space: nowrap;
|
|
-webkit-mask-image: var(--mask-url);
|
|
mask-image: var(--mask-url);
|
|
-webkit-mask-size: 180% 180%;
|
|
mask-size: 180% 180%;
|
|
-webkit-mask-position: 0% 50%;
|
|
mask-position: 0% 50%;
|
|
-webkit-mask-mode: luminance;
|
|
mask-mode: luminance;
|
|
animation: hf-texture-mask-pan 2.1s ease-in-out infinite alternate;
|
|
}
|
|
|
|
@keyframes hf-texture-mask-pan {
|
|
from {
|
|
-webkit-mask-position: 0% 50%;
|
|
mask-position: 0% 50%;
|
|
}
|
|
|
|
to {
|
|
-webkit-mask-position: 100% 50%;
|
|
mask-position: 100% 50%;
|
|
}
|
|
}
|
|
|
|
.hf-texture-example-groups {
|
|
display: grid;
|
|
gap: 32px;
|
|
margin: 20px 0 40px;
|
|
}
|
|
|
|
.hf-texture-example-groups > div,
|
|
.hf-texture-example-card {
|
|
min-width: 0;
|
|
}
|
|
|
|
.hf-texture-example-title {
|
|
margin: 0 0 10px;
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.hf-texture-example-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(min(100%, 320px), 1fr));
|
|
gap: 16px;
|
|
}
|
|
|
|
.hf-texture-example-card {
|
|
border: 1px solid rgba(127, 127, 127, 0.22);
|
|
border-radius: 8px;
|
|
padding: 20px;
|
|
background: #f1f1f1;
|
|
}
|
|
|
|
.hf-texture-example-meta {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 12px;
|
|
margin-bottom: 14px;
|
|
}
|
|
|
|
.hf-texture-example-label {
|
|
min-width: 0;
|
|
color: rgba(24, 24, 24, 0.62);
|
|
font-size: 13px;
|
|
line-height: 1.2;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.hf-texture-example-class {
|
|
max-width: 62%;
|
|
padding: 2px 6px;
|
|
border: 1px solid rgba(24, 24, 24, 0.12);
|
|
border-radius: 6px;
|
|
background: rgba(255, 255, 255, 0.5);
|
|
color: rgba(24, 24, 24, 0.78);
|
|
font-size: 11px;
|
|
line-height: 1.3;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.hf-texture-example-shadow {
|
|
filter: drop-shadow(1px 2px 1px rgba(0, 0, 0, 0.48));
|
|
}
|
|
|
|
.hf-texture-example-word {
|
|
color: #181818;
|
|
font-family: Impact, "Arial Black", sans-serif;
|
|
font-size: 54px;
|
|
line-height: 0.9;
|
|
letter-spacing: 0;
|
|
text-align: center;
|
|
text-transform: uppercase;
|
|
white-space: nowrap;
|
|
-webkit-mask-image: var(--mask-url);
|
|
mask-image: var(--mask-url);
|
|
-webkit-mask-size: cover;
|
|
mask-size: cover;
|
|
-webkit-mask-position: center;
|
|
mask-position: center;
|
|
-webkit-mask-mode: luminance;
|
|
mask-mode: luminance;
|
|
}
|
|
|
|
.hf-texture-example-usage {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 6px;
|
|
min-width: 0;
|
|
margin-top: 14px;
|
|
padding-top: 12px;
|
|
border-top: 1px solid rgba(24, 24, 24, 0.1);
|
|
color: rgba(24, 24, 24, 0.56);
|
|
font-size: 12px;
|
|
line-height: 1.3;
|
|
}
|
|
|
|
.hf-texture-example-usage code {
|
|
min-width: 0;
|
|
color: rgba(24, 24, 24, 0.82);
|
|
font-size: 11px;
|
|
line-height: 1.3;
|
|
white-space: normal;
|
|
overflow-wrap: anywhere;
|
|
}
|
|
|
|
/* Portrait proof clips should read as examples inside the page, not become the
|
|
* page. Landscape films keep the full content width; only explicitly marked
|
|
* portrait media gets this compact, centered treatment. */
|
|
.hf-portrait-video {
|
|
display: block;
|
|
width: auto;
|
|
max-width: min(100%, 18rem);
|
|
max-height: min(32rem, 68vh);
|
|
margin: 0.75rem auto 0;
|
|
border-radius: 0.5rem;
|
|
background: #111;
|
|
}
|
|
|
|
/* Full narrated films use a quiet custom control layer over the native video
|
|
* engine. Preview loops remain plain muted videos. */
|
|
.hf-docs-video-block {
|
|
width: 100%;
|
|
}
|
|
|
|
.hf-docs-video-block[data-portrait="true"] {
|
|
width: min(100%, 18rem);
|
|
margin: 0.75rem auto 0;
|
|
}
|
|
|
|
.hf-docs-video-block[data-portrait="true"] .hf-docs-video {
|
|
aspect-ratio: 9 / 16;
|
|
max-height: min(32rem, 68vh);
|
|
}
|
|
|
|
/*
|
|
* Mintlify loads custom React snippets after the page shell. Reserve the
|
|
* film's real shape so the unloaded component never flashes as a thin Frame
|
|
* rail, then let the hydrated player and chapter guide size themselves.
|
|
*/
|
|
.hf-docs-video-frame {
|
|
width: 100%;
|
|
aspect-ratio: 16 / 9;
|
|
overflow: hidden;
|
|
border-radius: 0.75rem;
|
|
background: #070707;
|
|
}
|
|
|
|
.hf-docs-video-frame:has(.hf-docs-video-block) {
|
|
aspect-ratio: auto;
|
|
overflow: visible;
|
|
border-radius: 0;
|
|
background: transparent;
|
|
}
|
|
|
|
.hf-docs-video {
|
|
position: relative;
|
|
aspect-ratio: 16 / 9;
|
|
overflow: hidden;
|
|
border-radius: 0.75rem;
|
|
background: #070707;
|
|
color: #fff;
|
|
isolation: isolate;
|
|
outline: none;
|
|
}@media (max-width: 720px) {}
|
|
|
|
@media (max-width: 520px) {}
|
|
|
|
.hf-docs-video:focus-visible {
|
|
box-shadow: 0 0 0 3px color-mix(in srgb, var(--hf-video-accent) 78%, transparent);
|
|
}
|
|
|
|
.hf-docs-video video {
|
|
display: block;
|
|
width: 100%;
|
|
height: 100%;
|
|
margin: 0 !important;
|
|
background: #070707;
|
|
object-fit: contain;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.hf-docs-video-hero-play {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 3.35rem;
|
|
height: 3.35rem;
|
|
padding: 0;
|
|
border: 1px solid rgba(255, 255, 255, 0.18);
|
|
border-radius: 50%;
|
|
background: rgba(8, 8, 8, 0.56);
|
|
color: #fff;
|
|
font: inherit;
|
|
cursor: pointer;
|
|
transform: translate(-50%, -50%);
|
|
backdrop-filter: blur(8px);
|
|
-webkit-backdrop-filter: blur(8px);
|
|
box-shadow: 0 10px 28px rgba(0, 0, 0, 0.3);
|
|
z-index: 3;
|
|
}
|
|
|
|
.hf-docs-video-hero-play:hover {
|
|
border-color: var(--hf-video-accent);
|
|
background: rgba(8, 8, 8, 0.78);
|
|
}
|
|
|
|
.hf-docs-video-hero-play:focus-visible,
|
|
.hf-docs-video-control:focus-visible,
|
|
.hf-docs-video-rate:focus-visible,
|
|
.hf-docs-video-progress:focus-visible {
|
|
outline: 2px solid var(--hf-video-accent);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
.hf-docs-video-hero-icon {
|
|
display: grid;
|
|
width: 2rem;
|
|
height: 2rem;
|
|
flex: 0 0 auto;
|
|
place-items: center;
|
|
border-radius: 0;
|
|
background: transparent;
|
|
color: #fff;
|
|
}
|
|
|
|
.hf-docs-video-hero-icon svg {
|
|
width: 1.25rem;
|
|
height: 1.25rem;
|
|
margin-left: 0.1rem;
|
|
fill: currentColor;
|
|
}
|
|
|
|
.hf-docs-video-controls {
|
|
position: absolute;
|
|
right: 0;
|
|
bottom: 0;
|
|
left: 0;
|
|
display: grid;
|
|
gap: 0.45rem;
|
|
padding: 3.75rem 1rem 0.78rem;
|
|
background: linear-gradient(to bottom, transparent, rgba(0, 0, 0, 0.84));
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
visibility: visible;
|
|
transition:
|
|
opacity 160ms ease,
|
|
transform 160ms ease,
|
|
visibility 0s linear;
|
|
z-index: 2;
|
|
}
|
|
|
|
.hf-docs-video-controls[data-visible="false"] {
|
|
pointer-events: none;
|
|
opacity: 0;
|
|
transform: translateY(0.4rem);
|
|
visibility: hidden;
|
|
transition:
|
|
opacity 160ms ease,
|
|
transform 160ms ease,
|
|
visibility 0s linear 160ms;
|
|
}
|
|
|
|
.hf-docs-video-progress {
|
|
width: 100%;
|
|
height: 1rem;
|
|
margin: 0;
|
|
border: 0;
|
|
background: transparent;
|
|
cursor: pointer;
|
|
appearance: none;
|
|
-webkit-appearance: none;
|
|
}
|
|
|
|
/* Timecode bubble that tracks the pointer along the progress bar. It is a label,
|
|
not a thumbnail: rendering a second <video> here made every page with a film
|
|
download the whole file twice. */
|
|
.hf-docs-video-scrub-preview {
|
|
position: absolute;
|
|
bottom: 3.6rem;
|
|
left: clamp(2rem, var(--hf-video-preview-x), calc(100% - 2rem));
|
|
border-radius: 0.35rem;
|
|
background: rgba(0, 0, 0, 0.82);
|
|
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.38);
|
|
pointer-events: none;
|
|
opacity: 1;
|
|
transform: translate(-50%, 0);
|
|
transition:
|
|
opacity 120ms ease,
|
|
transform 120ms ease;
|
|
z-index: 4;
|
|
}
|
|
|
|
.hf-docs-video-scrub-preview[data-visible="false"] {
|
|
visibility: hidden;
|
|
opacity: 0;
|
|
transform: translate(-50%, 0.3rem);
|
|
}
|
|
|
|
.hf-docs-video-scrub-preview span {
|
|
display: block;
|
|
padding: 0.16rem 0.4rem;
|
|
color: #fff;
|
|
font-variant-numeric: tabular-nums;
|
|
font-size: 0.7rem;
|
|
line-height: 1.2;
|
|
}
|
|
|
|
.hf-docs-video-progress::-webkit-slider-runnable-track {
|
|
height: 0.22rem;
|
|
border-radius: 999px;
|
|
background: linear-gradient(
|
|
to right,
|
|
var(--hf-video-accent) 0,
|
|
var(--hf-video-accent) var(--hf-video-progress),
|
|
rgba(255, 255, 255, 0.34) var(--hf-video-progress),
|
|
rgba(255, 255, 255, 0.34) 100%
|
|
);
|
|
}
|
|
|
|
.hf-docs-video-progress::-moz-range-track {
|
|
height: 0.22rem;
|
|
border-radius: 999px;
|
|
background: rgba(255, 255, 255, 0.34);
|
|
}
|
|
|
|
.hf-docs-video-progress::-moz-range-progress {
|
|
height: 0.22rem;
|
|
border-radius: 999px;
|
|
background: var(--hf-video-accent);
|
|
}
|
|
|
|
.hf-docs-video-progress::-webkit-slider-thumb {
|
|
width: 0.85rem;
|
|
height: 0.85rem;
|
|
margin-top: -0.315rem;
|
|
border: 2px solid #121212;
|
|
border-radius: 50%;
|
|
background: var(--hf-video-accent);
|
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
|
|
appearance: none;
|
|
-webkit-appearance: none;
|
|
}
|
|
|
|
.hf-docs-video-progress::-moz-range-thumb {
|
|
width: 0.85rem;
|
|
height: 0.85rem;
|
|
border: 2px solid #121212;
|
|
border-radius: 50%;
|
|
background: var(--hf-video-accent);
|
|
}
|
|
|
|
.hf-docs-video-control-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.3rem;
|
|
min-width: 0;
|
|
}
|
|
|
|
.hf-docs-video-control,
|
|
.hf-docs-video-rate {
|
|
display: grid;
|
|
height: 2rem;
|
|
min-width: 2rem;
|
|
padding: 0;
|
|
place-items: center;
|
|
border: 0;
|
|
border-radius: 0.45rem;
|
|
background: transparent;
|
|
color: rgba(255, 255, 255, 0.92);
|
|
font: inherit;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.hf-docs-video-control:hover,
|
|
.hf-docs-video-rate:hover {
|
|
background: rgba(255, 255, 255, 0.13);
|
|
color: #fff;
|
|
}
|
|
|
|
.hf-docs-video-control svg {
|
|
width: 1.2rem;
|
|
height: 1.2rem;
|
|
fill: currentColor;
|
|
}
|
|
|
|
.hf-docs-video-rate {
|
|
min-width: 2.75rem;
|
|
padding: 0 0.45rem;
|
|
font-size: 0.75rem;
|
|
font-weight: 650;
|
|
}
|
|
|
|
.hf-docs-video-time {
|
|
margin-left: 0.2rem;
|
|
color: rgba(255, 255, 255, 0.82);
|
|
font-variant-numeric: tabular-nums;
|
|
font-size: 0.74rem;
|
|
line-height: 1;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.hf-docs-video-time span {
|
|
color: rgba(255, 255, 255, 0.46);
|
|
}
|
|
|
|
.hf-docs-video-spacer {
|
|
flex: 1 1 auto;
|
|
}
|
|
|
|
.hf-docs-video-spinner {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
width: 2rem;
|
|
height: 2rem;
|
|
margin: -1rem 0 0 -1rem;
|
|
border: 2px solid rgba(255, 255, 255, 0.28);
|
|
border-top-color: var(--hf-video-accent);
|
|
border-radius: 50%;
|
|
animation: hf-docs-video-spin 700ms linear infinite;
|
|
z-index: 3;
|
|
}
|
|
|
|
@keyframes hf-docs-video-spin {
|
|
to {
|
|
transform: rotate(360deg);
|
|
}
|
|
}
|
|
|
|
@media (max-width: 520px) {
|
|
.hf-docs-video-hero-play {
|
|
width: 3rem;
|
|
height: 3rem;
|
|
}
|
|
|
|
.hf-docs-video-hero-icon {
|
|
width: 2rem;
|
|
height: 2rem;
|
|
}
|
|
|
|
.hf-docs-video-controls {
|
|
gap: 0.25rem;
|
|
padding: 2.75rem 0.6rem 0.42rem;
|
|
}
|
|
|
|
.hf-docs-video-scrub-preview {
|
|
bottom: 3.2rem;
|
|
}
|
|
|
|
.hf-docs-video-time {
|
|
margin-left: 0;
|
|
font-size: 0.67rem;
|
|
}
|
|
|
|
.hf-docs-video-rate {
|
|
min-width: 2.45rem;
|
|
padding: 0 0.25rem;
|
|
}
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.hf-docs-video-controls {
|
|
transition: none;
|
|
}
|
|
|
|
.hf-docs-video-spinner {
|
|
animation: none;
|
|
}
|
|
|
|
.hf-docs-video-scrub-preview {
|
|
transition: none;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 860px) {
|
|
.hf-texture-preview-panel {
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
}
|
|
}
|
|
|
|
@media (max-width: 520px) {
|
|
.hf-texture-preview-panel {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.hf-texture-preview-word {
|
|
font-size: 40px;
|
|
}
|
|
|
|
.hf-texture-animate-meta {
|
|
align-items: flex-start;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.hf-texture-animate-class {
|
|
max-width: 100%;
|
|
}
|
|
|
|
.hf-texture-animate-word {
|
|
font-size: 56px;
|
|
}
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.hf-texture-animate-word {
|
|
animation: none;
|
|
}
|
|
}
|
|
|
|
/* Hide the section/group label ("Start here", etc.) that the theme renders
|
|
* above every page title. It repeats the sidebar grouping and adds noise on
|
|
* every page; each page leads with its own H1 instead. */
|
|
.eyebrow {
|
|
display: none;
|
|
}
|