{
  "aliases": [
    "rebrand",
    "design-handoff",
    "brand-motion",
    "motion-tokens",
    "whitelabel",
    "white-label-theme",
    "theme-block",
    "external-brand"
  ],
  "code": "// ─────────────────────────────────────────────────────────────────────────\n// A WHOLE BRAND AS A THEME BLOCK.\n//\n// tenantTheme(hex) is for ONE customer colour arriving as data. This is the\n// other job: a design system handed to you (brand book, Figma, an HTML\n// prototype) that has its own palette, radii, shadows, type ramp AND motion\n// timing. All of it is token configuration. If you find yourself forking a\n// component or overriding a .ui-* selector from a page stylesheet, stop —\n// that is the signal to file an issue for the missing knob instead.\n//\n// Worked screens, three different brands: /showcase/acme-website (light/gold),\n// /showcase/futurelastic-web (dark/gold), /showcase/tcgm-website (light,\n// Japanese, and the one with a MOTION spec).\n\n<div data-tenant=\"acme\">              {/* a REGION, never <html> */}\n  <style>{THEME}</style>              {/* in an app this is your theme.css */}\n  <Navbar /> <Hero /> <Footer />\n</div>\n\nconst THEME = `\n[data-tenant=\"acme\"] {\n  /* 1 ── COLOUR. Map the brand's hexes onto the ROLES, as HSL triplets. */\n  --primary: 192 72% 21%; --primary-foreground: 0 0% 100%;\n  --ring: 199 89% 29%;          /* DECLARE IT. --ring is bound at :root and FREEZES;\n                                   many brands ring in a different blue than they fill with. */\n  --background: 210 40% 98%; --foreground: 195 57% 16%;\n  --card: 0 0% 100%; --muted-foreground: 196 17% 39%;\n  /* --border is decorative chrome. --input is the CONTROL BOUNDARY and is held to\n     3:1 (WCAG 2.2 SC 1.4.11) — brands usually ship a second, darker \"border-strong\"\n     for exactly this; use THAT one here, not the hairline. */\n  --border: 196 27% 89%; --input: 197 13% 51%;\n\n  /* 2 ── SHAPE + ELEVATION. Card hover is a TOKEN PAIR, not a hand-written rule:\n     \"lift 2px, shadow sm→md\" == --card-shadow + --card-hover-shadow. */\n  --radius-md: 12px; --radius-xl: 20px;\n  --card-radius: var(--radius-xl); --control-radius: var(--radius-md);\n  --card-shadow: 0 1px 2px rgb(15 76 92 / .06);\n  --card-hover-shadow: 0 12px 32px -14px rgb(15 76 92 / .20);\n\n  /* 3 ── TYPE. --font-size-display is the BASE KNOB of the display ramp: 3xl/4xl/5xl\n     all derive from it, so never restate --font-size-5xl. Use clamp() or the hero\n     paints its desktop size on a phone (measured: 56px at 390 where the brand scale\n     said 34px). */\n  --font-family-body: \"Noto Sans JP\", system-ui, sans-serif;\n  --font-family-sans: var(--font-family-body);\n  --font-family-display: var(--font-family-body);\n  --font-size-display: clamp(2.125rem, 4vw, 3.5rem);\n  /* ❌ --font-family-display: \"Montserrat\", \"Noto Sans JP\", …\n     A Latin-only face FIRST in a CJK product looks fine (the browser falls through\n     per glyph) while silently setting the Latin inside a Japanese headline in a\n     different face than the headline. Keep the CJK face as the display family and\n     opt into the Latin face per element instead: */\n  --brand-latin: \"Montserrat\", system-ui, sans-serif;\n}\n[data-tenant=\"acme\"] .brand-eyebrow,\n[data-tenant=\"acme\"] .brand-price { font-family: var(--brand-latin); }\n\n/* 4 ── MOTION. THIS IS THE PART PEOPLE FORK OVER, AND THEY DO NOT NEED TO.\n   A brand motion table — press 90ms · hover 140 · state 200 · enter 260 ·\n   hero-once 480ms at y8→0 · cubic-bezier(.2,.8,.2,1) — is EIGHT declarations.\n   <Reveal>, Card hoverable, Dialog/Sheet enter and every token-driven transition\n   retime themselves off these. Zero new @keyframes. */\n[data-tenant=\"acme\"] {\n  --duration-fast: 140ms;      /* hover / focus  */\n  --duration-base: 200ms;      /* state change   */\n  --duration-slow: 480ms;      /* hero once      */\n  --ease-standard: cubic-bezier(.2,.8,.2,1);\n  --ease-emphasized: cubic-bezier(.2,.8,.2,1);\n  --ease-decelerate: cubic-bezier(.2,.8,.2,1);\n  --reveal-distance: 8px;      /* the spec's \"y8→0\" */\n  --reveal-stagger-step: 70ms;\n  /* a11y numbers are tokens too — this brand's standard is a 44px target, above\n     the WCAG 2.2 SC 2.5.8 floor of 24px (gh#931): */\n  --carousel-arrow-size: 2.75rem;\n}\n`;\n\n// 5 ── SPEND THE MOTION, THEN STOP. Most brand books cap decorative motion\n// (\"one decorative region per viewport; no parallax, no count-up, no infinite\n// float\"). Read the spec before adding effects — adding more is not proving the\n// framework, it is breaking the brand.\n\n<Reveal on=\"mount\">                 {/* hero: illustration only, if the spec says\n                                       \"text appears immediately\" — then DON'T wrap\n                                       the headline, so it paints on frame 1 */}\n  <img src={heroArt} alt=\"…\" />\n</Reveal>\n\n<Reveal on=\"view\">                  {/* one observer per SECTION */}\n  <SectionHeading />\n</Reveal>\n<ResponsiveGrid columns={{ base: 2, sm: 3, lg: 4 }}>\n  {items.map((it, i) => (\n    <Reveal key={it.id} on=\"view\" delay={Math.min(i, 5) + 1}>   {/* delay caps at 6 */}\n      <Card hoverable>…</Card>\n    </Reveal>\n  ))}\n</ResponsiveGrid>\n\n// ❌ THE ONE THAT BITES: never put on=\"view\" on something a scroller CLIPS.\n//    <Carousel><CarouselItem><Reveal on=\"view\">…  ← 3 of 4 slides stayed at\n//    opacity 0 FOREVER: the observer's root is the viewport and a clipped box has\n//    an empty intersection rect, so a slide parked outside the rail never enters.\n// ✅ Reveal the RAIL:\n<Reveal on=\"view\">\n  <Carousel>\n    <CarouselContent>\n      {news.map((n) => (\n        <CarouselItem key={n.id} className=\"basis-4/5 sm:basis-1/2 lg:basis-1/3\">\n          <Card hoverable>…</Card>          {/* fractional basis, never basis-[82%] */}\n        </CarouselItem>\n      ))}\n    </CarouselContent>\n  </Carousel>\n</Reveal>\n\n// 6 ── VERIFY BY MEASURING, NOT BY SCREENSHOT. A full-page screenshot does not\n// fire viewport observers, so every scroll reveal photographs as invisible and\n// tells you nothing. Scroll the document, then count:\n//   [...document.querySelectorAll('.ui-reveal')]\n//     .filter(e => e.getAttribute('data-reveal-state') === 'out').length   // must be 0\n//   document.documentElement.scrollWidth - document.documentElement.clientWidth // must be 0\n// Check the hero's computed font-size at 390, and remember ResponsiveGrid steps\n// are CONTAINER widths: inside a 1280 shell with 32px gutters, a 768 tablet hands\n// the grid 704px, so `md` (48rem) never fires there — key that column to `sm`.\n",
  "name": "brand-theme-block",
  "tagline": "Wear a WHOLE external brand — its palette, shape, type AND its motion spec — as one scoped theme block of token declarations. No forked components, no page-local overrides of library selectors. Use when you are handed a design system (Figma/HTML prototype/brand book), not a single customer hex.",
  "tags": [
    "theme",
    "brand",
    "rebrand",
    "handoff",
    "motion",
    "animation",
    "reveal",
    "typography",
    "font",
    "cjk",
    "japanese",
    "tokens",
    "design-system",
    "white-label",
    "marketing",
    "landing"
  ]
}
