{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "styles",
  "title": "Base styles",
  "description": "Base layer: colour scheme, selection, focus, reduced-motion cut, Lenis and reveal/marquee/skeleton rules. Import after Tailwind and the token sheets.",
  "dependencies": [],
  "registryDependencies": [],
  "files": [
    {
      "path": "src/styles.css",
      "content": "/*\n * @hjconsult/ui base layer.\n * Import AFTER Tailwind and the token sheets in the app's global stylesheet:\n *\n *   @import \"tailwindcss\";\n *   @import \"@hjconsult/tokens/css\";\n *   @import \"@hjconsult/tokens/theme.css\";\n *   @import \"@hjconsult/tokens/fluid.css\";\n *   @import \"@hjconsult/ui/styles.css\";\n *\n * No Tailwind import here on purpose — the app owns Tailwind. Every value below comes from a\n * `--hj-*` token or a Tailwind theme variable; the few `var(--x, fallback)` fallbacks only apply\n * when the token sheet is missing (Storybook-less previews, tests).\n */\n\n@layer base {\n  :root {\n    color-scheme: dark;\n  }\n\n  [data-scheme=\"dark\"] {\n    color-scheme: dark;\n  }\n\n  [data-scheme=\"light\"] {\n    color-scheme: light;\n  }\n\n  html {\n    text-rendering: optimizeLegibility;\n    -webkit-font-smoothing: antialiased;\n    -moz-osx-font-smoothing: grayscale;\n    -webkit-text-size-adjust: 100%;\n    text-size-adjust: 100%;\n    scroll-behavior: smooth;\n  }\n\n  body {\n    background-color: var(--color-canvas);\n    color: var(--color-fg);\n    font-family: var(--font-body);\n    overflow-x: clip;\n  }\n\n  ::selection {\n    background-color: var(--color-accent-soft);\n    color: var(--color-fg);\n  }\n\n  :focus-visible {\n    outline: 2px solid var(--color-focus);\n    outline-offset: 2px;\n  }\n\n  /* Text-wrap fallbacks for engines without `balance`/`pretty` support. */\n  .text-balance {\n    text-wrap: balance;\n  }\n\n  .text-pretty {\n    text-wrap: pretty;\n  }\n\n  /* Lenis (smooth scroll) recommended styles. */\n  html.lenis,\n  html.lenis body {\n    height: auto;\n  }\n\n  .lenis.lenis-smooth {\n    scroll-behavior: auto;\n  }\n\n  .lenis.lenis-smooth [data-lenis-prevent] {\n    overscroll-behavior: contain;\n  }\n\n  .lenis.lenis-stopped {\n    overflow: clip;\n  }\n\n  .lenis.lenis-smooth iframe {\n    pointer-events: none;\n  }\n\n  /*\n   * Reveal initial state. Only while motion is allowed AND the MotionProvider has marked the\n   * document as motion-ready (`html[data-motion=\"on\"]`) — so crawlers and JS-off visitors always\n   * see the content, and there is no flash on pages that never mount the provider.\n   */\n  @media (prefers-reduced-motion: no-preference) {\n    html[data-motion=\"on\"] [data-reveal]:not([data-reveal=\"done\"]) {\n      opacity: 0;\n    }\n  }\n\n  /*\n   * The reduced-motion cut. Two ways in, because the visitor's own choice outranks the OS:\n   *   1. the OS asks for less motion and the visitor has not opted in here\n   *   2. the visitor opted out here, whatever the OS says\n   * `html[data-motion-pref]` is written before first paint, so this holds with or without React;\n   * with JS disabled the attribute is never set and the media query alone decides, which is the\n   * conservative answer.\n   */\n  /* biome-ignore-start lint/complexity/noImportantStyles: the reduced-motion cut must beat inline styles set by animation libraries */\n  @media (prefers-reduced-motion: reduce) {\n    html:not([data-motion-pref=\"full\"]) {\n      scroll-behavior: auto;\n    }\n\n    html:not([data-motion-pref=\"full\"]) :is(*, *::before, *::after) {\n      animation-duration: 0.01ms !important;\n      animation-iteration-count: 1 !important;\n      transition-duration: 0.01ms !important;\n      scroll-behavior: auto !important;\n    }\n  }\n\n  html[data-motion-pref=\"reduced\"] {\n    scroll-behavior: auto;\n  }\n\n  html[data-motion-pref=\"reduced\"] :is(*, *::before, *::after) {\n    animation-duration: 0.01ms !important;\n    animation-iteration-count: 1 !important;\n    transition-duration: 0.01ms !important;\n    scroll-behavior: auto !important;\n  }\n  /* biome-ignore-end lint/complexity/noImportantStyles: end of reduced-motion cut */\n}\n\n@layer components {\n  /* Marquee — cycle length is a token; `speed` divides it. */\n  .hj-marquee {\n    --hj-marquee-duration: calc(\n      var(--hj-motion-duration-marquee, 40s) /\n      var(--hj-marquee-speed, 1)\n    );\n  }\n\n  .hj-marquee-track {\n    animation: hj-marquee var(--hj-marquee-duration) linear infinite;\n  }\n\n  html[data-motion-pref=\"reduced\"] .hj-marquee-track {\n    animation: none;\n    translate: none;\n  }\n\n  .hj-marquee[data-direction=\"right\"] .hj-marquee-track {\n    animation-direction: reverse;\n  }\n\n  .hj-marquee:focus-within .hj-marquee-track,\n  .hj-marquee[data-pause-on-hover]:hover .hj-marquee-track {\n    animation-play-state: paused;\n  }\n\n  @media (prefers-reduced-motion: reduce) {\n    html:not([data-motion-pref=\"full\"]) .hj-marquee-track {\n      animation: none;\n      translate: none;\n    }\n  }\n\n  /*\n   * Under reduced motion the strip is a row the reader scrolls, and its edges fade where there is\n   * more to see: the right edge until the row reaches its end, the left once it has left its\n   * start. The row's own scroll position drives it (a scroll-driven animation), so a fade never\n   * dims the first or the last name while that name is the one in view. Where an engine has no\n   * scroll-driven animations there is no fade at all, rather than one that hides the last name for\n   * good; a name cut off at the edge is the cue there. The reduced-motion cut's 0.01ms duration\n   * does not stop it: on a scroll timeline a time is a share of the range, and one iteration is\n   * all of it.\n   */\n  @supports (animation-timeline: scroll()) {\n    html[data-motion-pref=\"reduced\"] .hj-marquee[data-fade] {\n      mask-image: linear-gradient(\n        to right,\n        transparent,\n        black var(--hj-marquee-fade-start),\n        black calc(100% - var(--hj-marquee-fade-end)),\n        transparent\n      );\n      animation: hj-marquee-edges linear both;\n      animation-timeline: scroll(self inline);\n    }\n\n    @media (prefers-reduced-motion: reduce) {\n      html:not([data-motion-pref=\"full\"]) .hj-marquee[data-fade] {\n        mask-image: linear-gradient(\n          to right,\n          transparent,\n          black var(--hj-marquee-fade-start),\n          black calc(100% - var(--hj-marquee-fade-end)),\n          transparent\n        );\n        animation: hj-marquee-edges linear both;\n        animation-timeline: scroll(self inline);\n      }\n    }\n  }\n\n  /* Skeleton shimmer — sweeps at twice the hero duration. */\n  .hj-skeleton {\n    --hj-skeleton-duration: calc(var(--hj-motion-duration-hero, 900ms) * 2);\n  }\n}\n\n@keyframes hj-marquee {\n  to {\n    translate: calc(-100% - var(--hj-marquee-gap, 0px)) 0;\n  }\n}\n\n/* The widths of the reduced-motion row's two edge fades, registered so they interpolate. */\n@property --hj-marquee-fade-start {\n  syntax: \"<length-percentage>\";\n  inherits: false;\n  initial-value: 0%;\n}\n\n@property --hj-marquee-fade-end {\n  syntax: \"<length-percentage>\";\n  inherits: false;\n  initial-value: 0%;\n}\n\n/* The same 12% the moving strip fades by, arriving on the left and leaving on the right. */\n@keyframes hj-marquee-edges {\n  from {\n    --hj-marquee-fade-start: 0%;\n    --hj-marquee-fade-end: 12%;\n  }\n  4% {\n    --hj-marquee-fade-start: 12%;\n  }\n  96% {\n    --hj-marquee-fade-end: 12%;\n  }\n  to {\n    --hj-marquee-fade-start: 12%;\n    --hj-marquee-fade-end: 0%;\n  }\n}\n\n@keyframes hj-shimmer {\n  to {\n    translate: 100% 0;\n  }\n}\n",
      "type": "registry:file",
      "target": "hj/styles.css"
    }
  ],
  "cssVars": {},
  "type": "registry:file"
}