
Yuv Design System
- 27 installs
- 262 repo stars
- Updated July 11, 2026
- hoodini/ai-agents-skills
Apply Yuval Avidani's YUV.AI brand and design system (Neon, Decks, Warm Editorial modes) with mandated typography, motifs, and brand assets.
About
Encodes the YUV.AI palette modes, typography, Fly High motifs, and brand assets, plus rules for when to apply the brand at all. A developer uses it only when producing YUV.AI-branded output.
- Three palette modes selected by medium with universal Fly High throughline
- Strict apply/do-not-apply guidance to avoid over-triggering the brand
Yuv Design System by the numbers
- 27 all-time installs (skills.sh)
- +5 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #1,334 of 1,880 Design & UI/UX skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/hoodini/ai-agents-skills --skill yuv-design-systemAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 27 |
|---|---|
| repo stars | ★ 262 |
| Last updated | July 11, 2026 |
| Repository | hoodini/ai-agents-skills ↗ |
What it does
Apply Yuval Avidani's YUV.AI brand and design system (Neon, Decks, Warm Editorial modes) with mandated typography, motifs, and brand assets.
Files
yuv-design-system
The canonical personal brand and design system for Yuval Avidani (yuv.ai). This skill encodes how YUV.AI work should look — palette, typography, motifs, assets, links — and when to apply those rules at all.
Source of truth: Public reference repo at https://github.com/hoodini/yuv-design-system (private). The repo holds runnable tokens and components; this skill encodes the rules so Claude can apply them in any context.
Where this skill sits in the YUV.AI pyramid: yuv-design-system is the middle-tier visual-rules skill. Above it is yuv-pilot — the top-of-pyramid orchestrator that routes ambiguous or multi-medium YUV.AI requests to the right combination of downstream skills. Below it (and beside it in the middle tier) are yuv-decks (slide builds) and yuv-viral-video (viral short MP4s). When yuv-pilot or a sibling skill calls into here, they pin a specific palette mode (Neon / Decks / Warm Editorial) — never let the mode drift mid-task. See yuv-pilot/SKILL.md for the full pyramid map.
---
§0. WHEN TO APPLY THIS SKILL — read this before anything else
This skill is the YUV.AI brand identity, not a generic visual-design skill. Apply it conservatively.
✅ Apply when
- The output is explicitly for YUV.AI or Yuval personally — his portfolio, his brand site, his keynote, his deck, his speaker bio, his profile, his social cards, his About page, his linktree, his course landing, an artifact featuring his brand assets or logos.
- Yuval mentions "my brand", "my design system", "my deck", "my site", "my presentation", "for me", "YUV.AI", "Fly High", "Let's Fly High", or anything that signals the output is his brand identity.
- A YUV.AI brand asset (logo, profile photo, watermark) is needed.
❌ Do NOT apply when
- Yuval asks for a generic game, web app, dashboard, landing page, demo, prototype, or visual without YUV.AI context. ("Build me a snake game" → pick whatever palette fits the game, NOT the brand palette.)
- The work is for a different brand, a client, a third party, or anyone other than Yuval personally.
- Yuval explicitly names a different palette or framework.
When unsure
Ask once: "Is this for the YUV.AI brand, or open palette?" — then commit. Don't drift mid-task.
Default behavior across Claude / Copilot / Cursor: if a YUV.AI signal is missing, stay out and let the chosen tool's own design instincts apply. Over-triggering this skill is worse than under-triggering it.
---
How to use this skill (when it applies)
1. Pick the palette mode by medium (see §1):
- Presentation / deck / keynote → Decks (Fly High purple/yellow/grey)
- Web / app / game / dashboard / social / general → Neon (DEFAULT — pink/cyan/white)
- Hope / Marcus / bigcats / course-landing → Warm Editorial (pink/yellow/bone)
2. Apply mandatory typography (Anton/Inter + Rubik/Assistant — see §2). 3. Carry the universal Fly High throughline (motifs across modes — see §1a). 4. Use bundled brand assets when a logo, profile, or watermark fits (see §3). 5. Include the canonical link set + credentials in footers, About blocks, credits, speaker bios (see §4). 6. Deep-dive references — load the file that matches the medium:
| Medium | Read this file |
|---|---|
| YUV.AI web / app / game / dashboard | references/palettes-neon.md + references/web-and-react.md |
| Slide decks / keynotes | references/presentations.md |
| Infographics / charts / data viz | references/visuals-and-charts.md |
| Social images / posters | references/social-images.md |
| Hebrew / bilingual / RTL | references/typography.md § Hebrew |
| Hardened patterns | references/lessons-learned.md |
Load only what's relevant — keeps context small.
---
§1. Palette modes — pick by medium
Three named modes. Pick by the kind of output, not your gut feel. Don't drift.
| Mode | When to use | Palette |
|---|---|---|
| Neon (DEFAULT for YUV.AI brand work) | Websites, web apps, games, dashboards, social images, posters, AI tools, profile/about content, ANY YUV.AI visual that isn't a slide deck | Hot pink + neon cyan + white + black |
| Decks (Fly High) | Presentations, slides, keynotes, hackathon decks, talk recordings, video banners formatted as a deck | Purple + yellow + light-grey |
| Warm Editorial | Hope (cheetah), Marcus (white lion), bigcats.ai, practical.yuv.ai course landing — any project in the editorial-pink-bone brand family | Warm pink + yellow + off-white + bone |
If you're picking a mode mid-task and it isn't obvious, ask once and commit.
§1a. The Universal Fly High throughline (applies to ALL modes)
Regardless of palette, YUV.AI work carries the same brand DNA — these motifs are what makes something feel YUV.AI:
- Flight metaphors in copy: climb / cruise / descent / pre-flight / landed / takeoff / heading / altitude / "let's fly high".
- Progress visualisations: gradient progress bars (purple→yellow on Decks, pink→cyan on Neon, pink→yellow on Warm Editorial), HUD strips, altimeter/compass-style dials, count-up animations on stats.
- Phoenix mark (or the Y-bird) somewhere meaningful — hero, About, watermark.
- "LET'S FLY HIGH · YUV.AI" watermark in the bottom-right corner of any shareable surface (slide, social card, hero crop).
- Anton uppercase display type with
letter-spacing: 0default. - JetBrains Mono for instrument-style readouts and section eyebrows in tracking 0.2em+.
These DNA elements travel across every mode. The palette tells you which brand chapter; the motifs tell you it's still YUV.AI.
§1b. Neon — NEW DEFAULT mode (web, apps, games, general visual work)
The everyday YUV.AI brand. Tavus-inspired: hot pink as the lead, neon cyan as electric counterpoint, white or rich black as the canvas. Best for product surfaces, web apps, games, dashboards, anything browser-rendered or interactive.
:root {
--yuv-pink: #FF1464; /* primary brand — CTAs, accents, the thread color */
--yuv-pink-hot: #FF0080; /* high-energy variant for glow / shadow / hover */
--yuv-cyan: #00E5FF; /* electric secondary — data, highlights, focus rings */
--yuv-cyan-edge: #00FFFF; /* max-neon variant — small surfaces only */
--yuv-white: #FFFFFF; /* canvas (light mode) or text-on-dark */
--yuv-black: #0A0A0A; /* canvas (dark mode) or text-on-light */
--yuv-charcoal: #1A1A1A; /* secondary surface (dark mode), text on light */
--yuv-soft: #F4F4F6; /* alt light surface for layering on white */
}Neon mode rules:
- Two canvas options: white
#FFFFFFor rich black#0A0A0A. Pick ONE per surface. No grey middle ground. - Pink is the thread — primary CTA fill, hero headline accents, brand stripe.
- Cyan is the second voice — secondary CTAs, focus rings, hero data points, link hover, progress fill (cyan against pink). Cyan never floods — accent only.
- Glow allowed.
text-shadow: 0 0 24px var(--yuv-pink),box-shadow: 0 0 32px rgba(0,229,255,0.4). The brand can shimmer; it can't sparkle (no rainbows, no multi-hue gradients). - Gradients: pink → cyan only. Small surfaces (buttons, text underlines, progress bars). Never full-page wash.
- Headlines: Anton uppercase, white or pink on dark / black on white. Tight
line-height: 1.0,letter-spacing: 0default. - Card pattern: rich black
#0A0A0Abackground with1px solid rgba(255,20,100,0.25)border, optional0 0 0 1px #00E5FFinner glow on hover. On light canvas: white card with1px solid rgba(0,0,0,0.08)and4px solid #FF1464left stripe. - Cursors and UI affordances can be cyan-tinted; the heroes are pink.
Full deep dive — composition patterns, glow recipes, neon-themed components → references/palettes-neon.md.
§1c. Decks (Fly High) — presentations only
This is the existing purple/yellow/grey deck system. Use ONLY for slides, keynotes, presentations. Don't apply to a web app or game.
:root {
--yuv-purple: #5E17EB; /* primary deck brand, act-slide backgrounds, vertical accent bars */
--yuv-purple-dark: #3D0DA8; /* decorative depth, diagonal accents */
--yuv-yellow: #FFEC00; /* loud accent only — never a slide background */
--yuv-grey: #F1F2F2; /* content-slide background */
--yuv-white: #FFFFFF; /* cards on grey, headers on purple */
--yuv-black: #000000; /* headers on grey, body */
--yuv-grey-dark: #D4D6D6; /* dividers on grey */
}Full deck rules — two-background rule, FlightHUD, PurpleBar, YellowUnderline, signature video banner pattern — live in references/presentations.md. Decks mode is the only mode where the purple/yellow palette applies.
The two-background rule (Decks mode, non-negotiable)
Every slide lives in one of two states. Never invent a third. Yellow is an accent, never a background.
| Type | Background | Headline color | Use for |
|---|---|---|---|
| Act | Purple #5E17EB | White #FFFFFF | Title, section divider, closer, hero |
| Content | Light grey #F1F2F2 | Black #000000 | Everything else — info, evidence, lists |
§1d. Warm Editorial tokens
:root {
--pink: #FF1464; /* primary brand, CTAs, thread color */
--yellow: #E5FF00; /* accent — when it shows up, it dominates */
--black: #0A0A0A; /* warm near-black, never blue-black */
--off-white: #FAFAF7; /* paper-feel, replaces #FFFFFF entirely */
--bone: #F5EEE4; /* transitional sections, cream */
--charcoal: #1A1A1A; /* primary text on light */
}Full Warm Editorial rules (paper grain, warm shadows, gradient bans, etc.) → references/palettes-warm-editorial.md.
Rules that apply to ALL modes
- No default Tailwind palette. No
slate,zinc,indigo,emerald, etc. Stay in the chosen mode. - Border radius: `0` or `999px` only. The 8–12px middle ground is the single most "AI template" tell.
- Asymmetric over grid-perfect wherever possible. Offset columns. One element breaks the grid.
- Whitespace is generous. Premium comes from restraint.
- Max content width 1440px. Section padding 120–160px desktop, 64–80px mobile.
Mode-specific canvas / white rules
| Mode | Pure #FFFFFF at root? | Where white is allowed |
|---|---|---|
| Neon | YES — #FFFFFF is a valid canvas (one of two — the other is rich black #0A0A0A). | Anywhere, but pick ONE per surface. No grey-white middle. |
| Decks | NO — page/slide root is #5E17EB (act) or #F1F2F2 (content). | Pure white allowed on cards inside content slides. |
| Warm Editorial | NO — #FFFFFF banned everywhere. Use #FAFAF7 off-white. | — |
---
§2. Typography — mandatory
English
- Display / headlines: Anton. Always UPPERCASE. Letter-spacing default `0` (normal). Anton is already condensed — negative tracking makes letters touch, which is what the system used to do wrong. Only at very large hero sizes (≥ 180px) can you go negative, and never tighter than
-0.015em. Line-height1.0–1.05. Never lowercase, never title case. - Body / UI: Inter, 400 / 500 / 600 / 700 / 900. Line-height
1.5for body. - Mono (readouts, citations, eyebrows): JetBrains Mono.
Hebrew
- Display: Rubik (weights up to 900).
- Body: Assistant.
- Hebrew pages:
<html lang="he" dir="rtl">+ logical CSS properties (margin-inline-start, notmargin-left).
Bilingual rule (battle-tested)
Never stack EN + HE side-by-side or in adjacent positions. Anton + Rubik at display sizes overlap visually (descenders invade the next ascender row). Use a language toggle instead — one language visible at a time. Full hardened pattern in references/lessons-learned.md § Bilingual toggle.
One Google Fonts link — always safe to load all four
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Anton&family=Inter:wght@400;500;600;700;900&family=JetBrains+Mono:wght@400;500&family=Rubik:wght@400;500;600;700;800;900&family=Assistant:wght@400;500;600;700&display=swap" rel="stylesheet">Stack the families in one variable so the browser picks per glyph:
:root {
--yuv-font-display: 'Anton', 'Rubik', system-ui, sans-serif;
--yuv-font-body: 'Inter', 'Assistant', system-ui, sans-serif;
--yuv-font-mono: '"JetBrains Mono"', '"SF Mono"', ui-monospace, Menlo, monospace;
}
h1, h2, h3, .display {
font-family: var(--yuv-font-display);
text-transform: uppercase; /* Latin only — Hebrew unaffected */
letter-spacing: 0; /* Anton is condensed — DO NOT add negative tracking */
line-height: 1.0;
}
/* Only at hero size (≥ 180px) and only when the headline visually demands it: */
.display-hero { letter-spacing: -0.01em; } /* max — never tighter */Banned defaults: serif fonts, script fonts, system-ui, the default Tailwind font stack.
---
§3. Brand assets — bundled in assets/
Four canonical files are bundled with this skill. Reach for them when an output deserves a real brand mark instead of a placeholder.
Asset retrieval — fallback chain (works on any machine)
When you need a brand image, follow this chain in order:
1. Look inside the skill bundle. On a properly-installed machine, the assets are always present at one of: ~/.claude/skills/yuv-design-system/assets/, ~/.copilot/skills/yuv-design-system/assets/, or ~/.agents/skills/yuv-design-system/assets/. Use whichever path your runtime exposes.
2. If the skill location isn't known (headless, fresh container), scan common spots for a local clone of the repo: ~/Documents/ai-decks/yuv-design-system/, ~/yuv-design-system/, ~/Code/yuv-design-system/, ~/dev/yuv-design-system/. If found, use its assets/.
3. If nothing is local, clone the canonical repo. Private, but the user's GitHub auth on the machine will let git clone work without prompts:
git clone https://github.com/hoodini/yuv-design-system.git ~/Documents/ai-decks/yuv-design-system
~/Documents/ai-decks/yuv-design-system/scripts/install-skill.shThat populates ~/.claude/skills/yuv-design-system/assets/ plus the Copilot/agent symlinks.
4. Last-resort fallback for sandboxed sessions where step 3 isn't possible (e.g. CI with no git auth): embed via base64 data:image/png;base64,… inline. Acceptable only as a last resort — file references stay small and edit-friendly.
The repo is https://github.com/hoodini/yuv-design-system and is private. Don't attempt raw.githubusercontent.com URLs — they will 404 without a personal access token. Use git clone instead.
The four files
| File | When to use | Notes |
|---|---|---|
assets/logo-square-transparent.png | Logos on light or dark backgrounds — anywhere you need a clean cutout (favicon, header, app icon, social card on a custom bg) | Phoenix mark, transparent background, square |
assets/logo-square-color.png | Hero badges, social profile images, OG images, anywhere the logo needs its own colored backplate | Phoenix mark on the brand backplate, square |
assets/logo-rectangle-wordmark.png | Watermarks (bottom-right corner of slides, videos, screenshots), email signatures, lower-third overlays, certificates | "LET'S FLY HIGH · YUV.AI" wordmark |
assets/profile-yuval-studio.png | About sections, course pages, podcast art, "meet your instructor" blocks, speaker bio cards | Yuval in the studio under the YUV.AI neon |
Quick path — call ensure-assets.sh
If you're running in a shell-capable session and just want a guaranteed-good assets path, run:
ASSETS_DIR=$(/path/to/yuv-design-system/scripts/ensure-assets.sh)
echo "$ASSETS_DIR"The script walks the fallback chain (skill bundle → local clones → git clone) and prints the absolute path to a working assets/ directory. If GitHub auth isn't set up and the clone fails, it exits non-zero with a clear message.
Embedding rules
- Watermark placement: bottom-right corner of any slide / video / shareable image, ~3% of the canvas width margin, ~120–180px wide at 1920×1080. Use
logo-rectangle-wordmark.pngfor watermarks. Never stretch. - Hero logos on dark backgrounds: prefer
logo-square-transparent.pngso the phoenix sits on whatever background you have. - Hero logos on light/grey/bone backgrounds: also prefer the transparent square — the mark has enough internal color to stand on its own.
- Social profile / OG cards:
logo-square-color.png(has its own backplate so it stands out in feeds). - Profile photo for bios / about / contact:
profile-yuval-studio.png. Crop to square or 4:5 portrait depending on the surface.
For web embedding, copy the asset into the project's public/ and reference it; or for HTML artifacts where you can't add files, inline as a data:image/png;base64,… URI (only acceptable as a last resort — file references are preferred). On slides, embed natively.
---
§4. Canonical bio + link set — auto-include
Whenever an output has a footer, contact section, "find me elsewhere" block, profile card, speaker bio, credits panel, or video end-screen, include this set automatically. Don't ask Yuval to provide it.
Credentials (use in bios, about sections, speaker intros, course pages)
- 2× GitHub Star (recognised twice by GitHub for community impact)
- AWS Gen AI Superstar
- AI commentator on Channel 12 News (Israel) — מגיש פינת AI בחדשות 12 / "AI segment host on Channel 12 News" / "Resident AI commentator on Channel 12 News"
- AI Builder & Speaker
- Technical Content Creator
- Founder of YUV.AI — leading Hebrew-speaking AI educator and technical innovator
- Active communities across X, Instagram, TikTok, YouTube, LinkedIn, Facebook, GitHub
Suggested one-liners (drop into bios)
- Yuval Avidani — 2× GitHub Star, AWS Gen AI Superstar, AI commentator on Channel 12 News (Israel). Founder of YUV.AI. Builds, teaches, and ships AI that actually works.
- AI Builder & Speaker. Technical Content Creator. 2× GitHub Star. AWS Gen AI Superstar. The AI guy on Channel 12 News.
- Hebrew: יובל אבידני — מגיש פינת AI בחדשות 12, GitHub Star כפול, AWS Gen AI Superstar, מייסד YUV.AI. בונה, מלמד, ומשגר AI שעובד.
Link set
website: https://yuv.ai
linktree: https://linktr.ee/yuvai
x: https://x.com/yuvalav · @yuvalav
instagram:https://instagram.com/yuval_770 · @yuval_770
tiktok: https://www.tiktok.com/@yuval.ai · @yuval.ai
youtube: https://youtube.com/@yuv-ai · @yuv-ai
github: https://github.com/hoodini · @hoodini
facebook: https://facebook.com/yuval.avidani · @yuval.avidani
linkedin: https://www.linkedin.com/in/yuval-avidani-87081474/Inclusion rules
- Footers: all eight platforms + linktree, each as an icon-only button (Phosphor or Lucide brand glyphs, single-color stroke matching the active palette).
- Speaker bios / about cards: website + linkedin + linktree + one signature social (X or Instagram).
- Video end-screens: website + 3 socials (typically X, Instagram, YouTube).
- Email signatures / certificate footers: website + linkedin + linktree.
- Linktree-as-shortcut: for any "follow me everywhere" CTA, link directly to https://linktr.ee/yuvai instead of listing all eight — punchier and one tap.
Drop-in HTML snippet (Neon mode — DEFAULT for YUV.AI web/apps)
<footer class="yuv-socials" style="display:flex;gap:16px;align-items:center;justify-content:center;padding:48px 0;background:#FFFFFF">
<a href="https://yuv.ai" aria-label="Website" style="color:#FF1464"><i class="ph ph-globe"></i></a>
<a href="https://x.com/yuvalav" aria-label="X" style="color:#FF1464"><i class="ph ph-x-logo"></i></a>
<a href="https://instagram.com/yuval_770" aria-label="Instagram" style="color:#FF1464"><i class="ph ph-instagram-logo"></i></a>
<a href="https://www.tiktok.com/@yuval.ai" aria-label="TikTok" style="color:#FF1464"><i class="ph ph-tiktok-logo"></i></a>
<a href="https://youtube.com/@yuv-ai" aria-label="YouTube" style="color:#FF1464"><i class="ph ph-youtube-logo"></i></a>
<a href="https://github.com/hoodini" aria-label="GitHub" style="color:#FF1464"><i class="ph ph-github-logo"></i></a>
<a href="https://facebook.com/yuval.avidani" aria-label="Facebook" style="color:#FF1464"><i class="ph ph-facebook-logo"></i></a>
<a href="https://www.linkedin.com/in/yuval-avidani-87081474/" aria-label="LinkedIn" style="color:#FF1464"><i class="ph ph-linkedin-logo"></i></a>
<a href="https://linktr.ee/yuvai" aria-label="Linktree" style="color:#00E5FF"><i class="ph ph-tree-structure"></i></a>
</footer>For Decks mode, swap #FF1464 → var(--yuv-purple) and #FFFFFF → var(--yuv-grey). For Warm Editorial, swap #FF1464 → #FF1464 (already pink), #FFFFFF → var(--off-white).
Full machine-readable link list (JSON, handles + URLs): references/social-and-links.md.
---
§5. Visuals, infographics & charts — the "thinking in pictures" default
Yuval prefers visual explanation over prose wherever possible. Charts, infographics, dynamic diagrams, isometric scenes, animated counters, before-and-after splits. When something could be explained as a number, a chart, or a diagram, default to the visual.
Encoding rules in brand
- Neon (default) primary series:
#FF1464pink. Highlight or "hero" data point:#00E5FFcyan. Secondary series: charcoal#1A1A1A, soft grey#F4F4F6. Never blue (other than the brand cyan), never default chart palettes. - Decks (Fly High) primary series:
#5E17EBpurple. Highlight:#FFEC00yellow. Secondary:#3D0DA8/#1A1A1A/#D4D6D6. Charts in decks only. - Warm Editorial primary series:
#FF1464pink. Highlight:#E5FF00yellow. Secondary: charcoal / warm grey. - Always label the hero number. If there's a big stat, render it with
<CounterUp>(component inreferences/components/CounterUp.tsx) — count from 0 withrequestAnimationFrame, ease-out cubic, ~1400ms. - No 3D bar charts. No pie charts with > 4 slices. No rainbow palettes. Pick categorical or sequential, never both.
- Grids and axes: 1px charcoal at 10–15% opacity. No coloured gridlines.
- Type on charts: Anton uppercase for axis labels and section titles, Inter for tick numbers, JetBrains Mono for raw readouts.
Default libraries
| Surface | Library | Notes |
|---|---|---|
| React chart | recharts | Lightweight, easy palette override. |
| Vanilla HTML chart | Chart.js | Drop-in, easy to skin via options.scales + custom colors. |
| Bespoke / interactive infographic | D3 + GSAP | When the visual has personality — sankey, force layout, custom layout. |
| Hand-built SVG infographic | inline <svg> | For static / printable / Hyperframes-capturable visuals. |
| Slide-deck stat slide | <CounterUp> + Anton heading | Pattern in references/components/CounterUp.tsx |
Full chart-skinning guidance, sample Chart.js options object, recharts override pattern, and isometric SVG patterns → references/visuals-and-charts.md.
---
§6. Signature components (Decks mode — Fly High)
These components apply ONLY in Decks mode, where the purple/yellow palette and slide-canvas rules are in play. Don't drop them into a Neon-mode web app or game — that's the wrong language.
These are the recognisable fingerprints. Full source in references/components/.
| Component | Role |
|---|---|
<PurpleBar height={96}> | Vertical purple accent bar to the left of every major content-slide headline. The single most consistent brand mark in the system. Use it. |
<YellowUnderline width={210}> | Hand-drawn SVG underline anchored under one specific word — never decoratively floating. For the punchline word in a subtitle. |
<FlightHUD n={12} cite="…" tone="content"> | Bottom HUD strip: progress bar + flight phase (CLIMB/CRUISE/DESCENT/LANDED) + instrument readouts (ALT / SPD / HDG / FUEL). Establishes the flight-simulator throughline across a deck. |
<CompassDial heading={287}> / <AltimeterDial alt={35000}> | Circular SVG cockpit instruments. Title and section-divider slides only — overuse cheapens them. |
<CounterUp to={95}> | Animated number counter pairs with a big Anton % sign for stat slides. |
<HeroBg src={image} overlay={…}> | Ken-Burns full-bleed image background with mandatory overlay gradient for headline contrast. |
The yellow highlight span (critical pattern)
<h1 style={{ fontSize: 148, lineHeight: 1.05, letterSpacing: 0 /* see warnings */ }}>
The team that<br />
wins this weekend<br />
won't have the<br />
<span style={{
display: 'inline-block',
background: '#FFEC00',
color: '#000',
padding: '0.08em 0.4em', /* em-based so it scales with font size */
letterSpacing: '0.01em', /* slight POSITIVE tracking inside the box */
boxDecorationBreak: 'clone',
WebkitBoxDecorationBreak: 'clone',
}}>
smartest model.
</span>
</h1>Three rules baked into this pattern — all battle-tested gotchas:
1. `letterSpacing: 0` on the headline (never negative). Anton is already condensed; negative tracking makes letters touch. 2. `letterSpacing: '0.01em'` inside the yellow span — slight positive tracking so letters breathe inside the yellow rectangle. Without this, the yellow box looks like a wall of crammed glyphs. 3. `padding: '0.08em 0.4em'` (em-based) instead of pixel padding — scales with the headline size. With pixel padding, the yellow box hugs letters too tight at large sizes.
Plus the always-rules: line-height ≥ 1.0, display: inline-block, boxDecorationBreak: clone. Without these the yellow box eats the previous line's descenders. Full pattern + anti-patterns: references/lessons-learned.md.
One Anton element per slide / section
Stacking multiple Anton blocks in a row (a headline + four big Anton stat tiles, say) makes the surface read like a wall of thick type. Rule: one Anton element per slide. Supporting numbers, labels, and stat values go in Inter 900 or JetBrains Mono at the size you'd otherwise put Anton. The slide stays calm, the hero remains the hero.
---
§7. Animation defaults
Every animation needs a reason. Alive, not restless. Hovers are deliberate. Reveals are scroll-triggered. Numbers count up on viewport entry. Connecting lines draw themselves.
Library preference order: 1. GSAP — primary. Use SplitText for headline reveals, ScrollTrigger for scroll-driven sequences, MotionPath for SVG paths. Prefer GSAP over Framer Motion for non-trivial work — higher-end results, and timelines are seekable (matters for Hyperframes capture). 2. Three.js + React Three Fiber — for 3D when the project justifies it. 3. MediaPipe — gesture / pose interactions when the project has a signature wow moment. 4. Lottie — when the designer has shipped a .json. 5. CSS-only — hover states, focus rings, button press feedback. Don't reach for a library for these.
Hyperframes compatibility (free insurance)
Yuval often captures sites and decks as MP4 for keynotes, reels, and marketing. Hyperframes (https://hyperframes.mintlify.app/llms.txt) renders HTML to deterministic, frame-by-frame video. Default to Hyperframes-friendly patterns even when capture isn't requested:
- Semantic HTML5 (
<section>,<article>,<header>,<nav>,<footer>). Div soup is harder to target. - Mark capture-destined regions:
<!-- HYPERFRAMES_VIDEO_SLOT: hero --> … <!-- /HYPERFRAMES_VIDEO_SLOT -->. - Prefer GSAP timelines (seekable) over CSS
@keyframesfor sequences that might be captured. - No wall-clock dependencies. No
Date.now(), no orphansetInterval. Use scroll position, GSAP timelines, orrequestAnimationFrametied to a deterministic clock.
Signature video banner — mandatory on landing sites
Every landing page or marketing site Yuval ships opens with a cinematic product-demo video banner at the top, embedded as an <iframe> pointing at a self-looping Hyperframes-compatible HTML composition (public/demo/index.html). "Hero with static text and a stock image" is banned — the hero opens with motion. Full 6-scene narrative arc, device mockup spec, cursor choreography rules, and deterministic loop pattern → references/presentations.md § Signature video banner.
---
§8. Bilingual & Hebrew — battle-tested rules
Hebrew work has hard-earned rules. Read these before writing any HE/EN page:
1. Bilingual = toggle, never side-by-side. Anton at 8vw + Rubik 900 at 5–6vw visually collide. Build a language toggle with data-lang="en" / data-lang="he" attributes and a universal show/hide rule. Default EN, button to switch. 2. `document.title` is part of the toggle. Swap the tab title too — otherwise it screams "half-built bilingual" to native readers. 3. Mobile = duplicate the language toggle into the always-visible mobile cluster, not just inside the hamburger. 4. `<html lang="he" dir="rtl">` when Hebrew is the default. Logical CSS (margin-inline-start, padding-inline-end) everywhere. 5. One display headline per section. Never two stacked display fonts.
Full hardened bilingual toggle (HTML + CSS + JS) and mobile-nav pattern → references/lessons-learned.md.
---
§9. What NOT to ship — explicit anti-defaults
- Stock photography. No generic business people, abstract cityscapes, Unsplash textures. Prefer abstract geometric, AI-generated in palette, or Yuval's own imagery (Hope cheetah, Marcus lion, the studio shot).
- Emoji in enterprise / client work. Personal blogs and casual content are fine.
- Soft drop-shadow rounded-corner template cards — the dominant AI slop visual. Flat card, 12px radius,
0 4px 6px rgba(0,0,0,0.1). Never ship. - The default Tailwind palette (
slate,zinc,gray,emerald,cyan,indigo,violet,rose). - Multicolour icon sets. Phosphor / Lucide with single-color stroke only.
- Cookie banners, chat widgets, marketing popups in demo builds. Production concerns. Leave demos clean.
- AI-generated feel — templated hero → features → pricing → footer, lorem ipsum, "Your tagline here." Write real copy.
- Pure `#FFFFFF` as a page/slide root.
- Blue as a default accent unless the project explicitly requires it.
- Yellow as a slide / page background. Accent only.
- A third background colour in Decks mode. Two-background rule is non-negotiable for slides.
- Mixing modes within one surface. A web app in Neon shouldn't have a purple act section. A slide in Decks mode shouldn't have a pink CTA. Pick one mode per project; stay in it.
- Anton at large sizes with `line-height < 1.0` inside a multi-line headline that has a
box-decoration-break: clonespan. Descenders get eaten. - `<YellowUnderline>` floating with no anchor. Always under a specific word.
- WebFetch on JS-rendered SPAs (Teachable, Kajabi, practical.yuv.ai, etc.). Use Claude-in-Chrome.
---
§10. Pre-flight checklist (run before delivering anything)
1. YUV.AI signal confirmed. If the output isn't YUV.AI-branded, this skill shouldn't be applied — back off and let the tool's neutral design instincts run. 2. Mode picked by medium: Neon (default for web/app/game/dashboard/social), Decks (presentations only), Warm Editorial (Hope/bigcats/practical.yuv.ai only). 3. Canvas rules respected:
- Neon: pure
#FFFFFFOR rich#0A0A0Acanvas — pick one per surface. - Decks: root is
#5E17EBact or#F1F2F2content — never pure#FFFFFFat root. - Warm Editorial: pure
#FFFFFFbanned — use#FAFAF7.
4. No default Tailwind color classes (slate-*, zinc-*, indigo-*, emerald-*, …). 5. Decks-only: two-background rule respected — every slide is purple-act or grey-content, no third. 6. Anton is uppercase, letter-spacing: 0 by default (NEVER negative — Anton is already condensed), -0.01em only at hero size (≥ 180px). Line-height ≥ 1.0 whenever a yellow box-decoration-break: clone span sits inside a multi-line headline. Yellow span uses em-based padding (0.08em 0.4em) + slight POSITIVE tracking (0.01em). 6. Hebrew pages have dir="rtl" + logical CSS. Bilingual pages use a toggle, not side-by-side. 7. Border radius is 0 or 999px. Nothing in between. 9. Shadows match the mode — plain low-opacity black for Decks, warm pink/orange undertone for Warm Editorial, pink/cyan-glow allowed in Neon. No blue-black anywhere. 10. Universal Fly High throughline carried. Flight metaphors in copy, a HUD or progress visual where it fits, phoenix mark / "LET'S FLY HIGH" watermark in the corner. The motifs travel; the palette per mode. 9. Social link footer included wherever a footer / credits / about section exists, with the canonical link set. 10. Brand mark used where a logo is appropriate — square transparent on hero, rectangle wordmark in corners, profile photo in about/bio. 12. <PurpleBar> next to content-slide headlines (Decks mode only). 12. <YellowUnderline> anchored to a specific word — never decorative. 13. For any catalog with > 8 GSAP demos: IntersectionObserver pauses offscreen demos. 14. For any frontend output: resize the preview to 375×812 (iPhone size) and verify before declaring done. Mobile-first is a baseline check, not polish.
If any fails — fix before shipping.
---
Where the canonical files live
| File | Purpose |
|---|---|
assets/logo-square-transparent.png | Square phoenix mark, transparent background |
assets/logo-square-color.png | Square phoenix mark on brand backplate |
assets/logo-rectangle-wordmark.png | "LET'S FLY HIGH · YUV.AI" rectangle wordmark (watermarks) |
assets/profile-yuval-studio.png | Yuval in the studio (bios, about, course pages) |
references/tokens/palette.css | Drop-in CSS variables |
references/tokens/palette.json | Design tokens in JSON |
references/tokens/tailwind.config.js | Tailwind preset |
references/components/*.tsx | Signature React components |
references/components/keyframes.css | Shared yuv-* keyframes |
references/web-and-react.md | Web/React deep-dive |
references/presentations.md | Slide deck rules + signature video banner |
references/visuals-and-charts.md | Infographics, charts, data-viz patterns |
references/social-images.md | Social cards, OG images, posters |
references/typography.md | Full type system including Hebrew/RTL |
references/social-and-links.md | Machine-readable link set + footer patterns |
references/lessons-learned.md | Hardened patterns + anti-patterns from production |
references/palettes-warm-editorial.md | Warm Editorial mode deep dive |
External canonical source: https://github.com/hoodini/yuv-design-system (private — clone via git clone https://github.com/hoodini/yuv-design-system.git from any of Yuval's machines).
---
import React from 'react';
/**
* Cockpit altimeter dial. Companion to CompassDial — same visual language.
*
* <AltimeterDial size={220} alt={35400} />
*/
export const AltimeterDial: React.FC<{
size?: number;
alt?: number;
bg?: string;
ring?: string;
needle?: string;
text?: string;
}> = ({
size = 240,
alt = 35000,
bg = 'rgba(0,0,0,0.4)',
ring = '#FFEC00',
needle = '#FFEC00',
text = '#FFFFFF',
}) => {
const rotation = ((alt % 1000) / 1000) * 360;
return (
<svg viewBox="0 0 200 200" width={size} height={size}>
<circle cx="100" cy="100" r="96" fill={bg} stroke={ring} strokeWidth="2" />
{Array.from({ length: 10 }, (_, i) => i * 36).map((a) => (
<line
key={a}
x1="100"
y1="10"
x2="100"
y2="22"
stroke={ring}
strokeWidth="1.5"
transform={`rotate(${a} 100 100)`}
/>
))}
{Array.from({ length: 10 }, (_, i) => i * 36).map((a, i) => {
const rad = (a - 90) * (Math.PI / 180);
return (
<text
key={i}
x={100 + 78 * Math.cos(rad)}
y={100 + 78 * Math.sin(rad) + 5}
fill={text}
fontSize="13"
fontFamily='"JetBrains Mono", monospace'
fontWeight="600"
textAnchor="middle"
>
{i}
</text>
);
})}
<polygon
points="100,28 95,100 100,108 105,100"
fill={needle}
transform={`rotate(${rotation} 100 100)`}
style={{ transition: 'transform 2s ease-out' }}
/>
<circle cx="100" cy="100" r="6" fill={ring} />
<text
x="100"
y="160"
fill={text}
fontSize="13"
fontFamily='"JetBrains Mono", monospace'
letterSpacing="0.18em"
textAnchor="middle"
>
ALT {alt.toLocaleString()}
</text>
</svg>
);
};
import React from 'react';
/**
* Cockpit compass dial. Decorative — use sparingly on hero / divider slides.
*
* <CompassDial size={220} heading={287} />
*/
export const CompassDial: React.FC<{
size?: number;
heading?: number;
bg?: string;
ring?: string;
needle?: string;
text?: string;
}> = ({
size = 240,
heading = 287,
bg = 'rgba(0,0,0,0.4)',
ring = '#FFEC00',
needle = '#FFEC00',
text = '#FFFFFF',
}) => (
<svg viewBox="0 0 200 200" width={size} height={size}>
<circle cx="100" cy="100" r="96" fill={bg} stroke={ring} strokeWidth="2" />
<circle cx="100" cy="100" r="76" fill="none" stroke={ring} strokeWidth="1" opacity="0.5" />
{[0, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330].map((a) => (
<line
key={a}
x1="100"
y1="10"
x2="100"
y2={a % 90 === 0 ? '24' : '18'}
stroke={ring}
strokeWidth={a % 90 === 0 ? 2 : 1}
transform={`rotate(${a} 100 100)`}
/>
))}
{[
['N', 0],
['E', 90],
['S', 180],
['W', 270],
].map(([letter, ang]) => {
const rad = ((ang as number) - 90) * (Math.PI / 180);
return (
<text
key={letter as string}
x={100 + 78 * Math.cos(rad)}
y={100 + 78 * Math.sin(rad) + 5}
fill={text}
fontSize="14"
fontFamily="Inter, sans-serif"
fontWeight="700"
textAnchor="middle"
>
{letter}
</text>
);
})}
<g
style={{
transformOrigin: '100px 100px',
animation: 'yuv-needleSweep 6s ease-in-out infinite',
}}
>
<polygon
points="100,30 92,108 100,100 108,108"
fill={needle}
transform={`rotate(${heading} 100 100)`}
/>
</g>
<circle cx="100" cy="100" r="6" fill={ring} />
<text
x="100"
y="160"
fill={text}
fontSize="13"
fontFamily='"JetBrains Mono", monospace'
letterSpacing="0.2em"
textAnchor="middle"
>
HDG {heading}°
</text>
</svg>
);
import React from 'react';
/**
* Animated number counter. Counts from 0 to `to` with ease-out cubic.
*
* <CounterUp to={95} duration={1600} />
*
* Pairs nicely with a big Anton % sign for stat slides.
*/
export const CounterUp: React.FC<{
to: number;
duration?: number;
prefix?: string;
suffix?: string;
style?: React.CSSProperties;
}> = ({ to, duration = 1400, prefix = '', suffix = '', style }) => {
const [val, setVal] = React.useState(0);
React.useEffect(() => {
let raf = 0;
const t0 = performance.now();
const tick = (t: number) => {
const elapsed = t - t0;
const p = Math.min(1, elapsed / duration);
const eased = 1 - Math.pow(1 - p, 3); // ease-out cubic
setVal(Math.round(to * eased));
if (p < 1) raf = requestAnimationFrame(tick);
};
raf = requestAnimationFrame(tick);
return () => cancelAnimationFrame(raf);
}, [to, duration]);
return (
<span style={style}>
{prefix}
{val}
{suffix}
</span>
);
};
import React from 'react';
/**
* Bottom flight-simulator HUD strip. Sits below every slide as a running
* status bar — progress bar + flight phase + instrument readouts + counter.
*
* <FlightHUD n={12} total={23} cite="The 7 Traps" tone="purple" />
*
* Values progress over the deck via flightStatus() — pre-flight → climb →
* cruise → descent → landed.
*/
export type FlightTone = 'content' | 'purple';
type Phase = {
phase: string;
alt: number;
spd: number;
hdg: number;
fuel: number;
};
/**
* Default journey shape — pre-flight → climb → cruise → descent → landed
* across N steps. Override by passing your own `status` prop.
*/
export function defaultFlightStatus(n: number, total = 23): Phase {
// 23-step default. Adapt for shorter/longer decks by scaling.
const phases: Array<[number, Phase]> = [
[1, { phase: 'PRE-FLIGHT', alt: 0, spd: 0, hdg: 287, fuel: 100 }],
[2, { phase: 'TAXI', alt: 0, spd: 28, hdg: 287, fuel: 99 }],
[3, { phase: 'TAKEOFF', alt: 850, spd: 165, hdg: 290, fuel: 98 }],
[5, { phase: 'CLIMB', alt: 9100, spd: 280, hdg: 294, fuel: 95 }],
[9, { phase: 'CRUISE', alt: 33800, spd: 472, hdg: 302, fuel: 85 }],
[12, { phase: 'CRUISE', alt: 35400, spd: 484, hdg: 308, fuel: 76 }],
[15, { phase: 'CRUISE', alt: 35400, spd: 484, hdg: 308, fuel: 67 }],
[19, { phase: 'CRUISE', alt: 35400, spd: 484, hdg: 308, fuel: 55 }],
[20, { phase: 'DESCENT', alt: 24600, spd: 410, hdg: 312, fuel: 50 }],
[21, { phase: 'DESCENT', alt: 12400, spd: 320, hdg: 314, fuel: 46 }],
[22, { phase: 'FINAL', alt: 2200, spd: 180, hdg: 316, fuel: 43 }],
[23, { phase: 'LANDED', alt: 0, spd: 0, hdg: 0, fuel: 41 }],
];
// Find closest defined phase ≤ n
let last = phases[0][1];
for (const [num, p] of phases) {
if (num <= n) last = p;
else break;
}
return last;
}
export const FlightHUD: React.FC<{
n: number;
total?: number;
cite?: string;
tone?: FlightTone;
status?: Phase;
flightId?: string;
}> = ({ n, total = 23, cite, tone = 'content', status, flightId = 'FH-YUV-AI' }) => {
const onPurple = tone === 'purple';
const s = status ?? defaultFlightStatus(n, total);
const pct = (n / total) * 100;
const txt = onPurple ? '#FFFFFF' : '#000000';
const dim = onPurple ? 'rgba(255,255,255,0.7)' : 'rgba(0,0,0,0.55)';
const accent = onPurple ? '#FFEC00' : '#5E17EB';
const borderColor = onPurple ? 'rgba(255,255,255,0.2)' : 'rgba(0,0,0,0.12)';
const cellBg = onPurple ? 'rgba(255,255,255,0.06)' : '#FFFFFF';
const Cell: React.FC<{ label: string; value: string; unit?: string }> = ({ label, value, unit }) => (
<div
style={{
background: cellBg,
border: `1px solid ${borderColor}`,
padding: '6px 14px',
minWidth: 92,
display: 'flex',
flexDirection: 'column',
}}
>
<span
style={{
fontFamily: '"JetBrains Mono", monospace',
fontSize: 9,
color: dim,
letterSpacing: '0.2em',
fontWeight: 700,
}}
>
{label}
</span>
<span
style={{
fontFamily: '"JetBrains Mono", monospace',
fontSize: 16,
color: txt,
fontWeight: 700,
letterSpacing: '0.04em',
marginTop: 2,
}}
>
{value}
{unit && <span style={{ fontSize: 10, color: dim, marginLeft: 4 }}>{unit}</span>}
</span>
</div>
);
return (
<div
style={{
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
zIndex: 5,
}}
>
{/* Progress bar */}
<div
style={{
position: 'relative',
height: 6,
background: onPurple ? 'rgba(255,255,255,0.10)' : 'rgba(0,0,0,0.06)',
}}
>
<div
style={{
position: 'absolute',
inset: '0 auto 0 0',
width: `${pct}%`,
background: 'linear-gradient(90deg, #5E17EB 0%, #FFEC00 100%)',
}}
/>
</div>
{/* HUD strip */}
<div
style={{
padding: '14px 100px 18px',
background: onPurple ? 'rgba(0,0,0,0.20)' : 'rgba(255,255,255,0.65)',
backdropFilter: 'blur(4px)',
WebkitBackdropFilter: 'blur(4px)',
borderTop: `1px solid ${borderColor}`,
display: 'grid',
gridTemplateColumns: '1fr auto 1fr',
alignItems: 'center',
gap: 24,
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
<span
style={{
fontFamily: '"JetBrains Mono", monospace',
fontSize: 11,
color: accent,
letterSpacing: '0.3em',
fontWeight: 700,
padding: '3px 10px',
border: `1px solid ${accent}`,
}}
>
◉ {s.phase}
</span>
{cite && (
<span
style={{
fontFamily: '"JetBrains Mono", monospace',
fontSize: 11,
color: dim,
letterSpacing: '0.06em',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
maxWidth: 540,
}}
>
{cite}
</span>
)}
</div>
<div style={{ display: 'flex', gap: 8 }}>
<Cell label="ALT" value={s.alt.toLocaleString()} unit="ft" />
<Cell label="SPD" value={String(s.spd)} unit="kt" />
<Cell label="HDG" value={s.hdg ? `${s.hdg}°` : '— —'} />
<Cell label="FUEL" value={`${s.fuel}%`} />
</div>
<div style={{ display: 'flex', justifyContent: 'flex-end', alignItems: 'center', gap: 12 }}>
<span
style={{
fontFamily: '"JetBrains Mono", monospace',
fontSize: 11,
color: dim,
letterSpacing: '0.2em',
fontWeight: 600,
}}
>
FLIGHT {flightId}
</span>
<span
style={{
fontFamily: 'Anton, sans-serif',
fontSize: 22,
color: txt,
letterSpacing: '0.02em',
}}
>
{String(n).padStart(2, '0')}
<span style={{ color: dim }}> / {total}</span>
</span>
</div>
</div>
</div>
);
};
import React from 'react';
/**
* Full-bleed background image with Ken-Burns pan + customisable overlay.
*
* <HeroBg
* src={myImage}
* overlay={`linear-gradient(180deg, rgba(241,242,242,0.6), rgba(241,242,242,0.95))`}
* />
*
* The overlay is mandatory in practice — without it, headlines lose contrast.
* Pair with a Ken-Burns CSS keyframe `yuv-kenBurns` (see keyframes.css).
*/
export const HeroBg: React.FC<{
src: string;
overlay?: string;
reverse?: boolean;
zIndex?: number;
}> = ({ src, overlay, reverse, zIndex = 0 }) => (
<>
<div
aria-hidden
style={{
position: 'absolute',
inset: 0,
zIndex,
overflow: 'hidden',
}}
>
<div
style={{
position: 'absolute',
inset: '-6%',
backgroundImage: `url(${src})`,
backgroundSize: 'cover',
backgroundPosition: 'center',
animation: `${reverse ? 'yuv-kenBurnsR' : 'yuv-kenBurns'} 22s ease-in-out infinite`,
filter: 'saturate(1.05) contrast(1.04)',
}}
/>
</div>
{overlay && (
<div
aria-hidden
style={{
position: 'absolute',
inset: 0,
zIndex: zIndex + 1,
background: overlay,
pointerEvents: 'none',
}}
/>
)}
</>
);
export { PurpleBar } from './PurpleBar';
export { YellowUnderline } from './YellowUnderline';
export { CounterUp } from './CounterUp';
export { CompassDial } from './CompassDial';
export { AltimeterDial } from './AltimeterDial';
export { FlightHUD, defaultFlightStatus } from './FlightHUD';
export type { FlightTone } from './FlightHUD';
export { HeroBg } from './HeroBg';
/* YUV.AI design system — shared keyframe animations.
* Import once at the root of your app or inject per-component.
* All keyframes are namespaced `yuv-*`.
*
* Hyperframes-compatible: all motion is deterministic (no wall-clock state).
*/
@keyframes yuv-fadeUp {
from { opacity: 0; transform: translateY(28px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes yuv-fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes yuv-kenBurns {
0% { transform: scale(1.04) translate(-1%, -0.5%); }
50% { transform: scale(1.08) translate(1%, 0.5%); }
100% { transform: scale(1.04) translate(-1%, -0.5%); }
}
@keyframes yuv-kenBurnsR {
0% { transform: scale(1.06) translate(0.8%, 0.4%); }
50% { transform: scale(1.10) translate(-0.8%, -0.4%); }
100% { transform: scale(1.06) translate(0.8%, 0.4%); }
}
@keyframes yuv-needleSweep {
0% { transform: rotate(0deg); }
50% { transform: rotate(10deg); }
100% { transform: rotate(0deg); }
}
@keyframes yuv-blink {
0%, 49% { opacity: 1; }
50%, 100% { opacity: 0.25; }
}
@keyframes yuv-pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.6; transform: scale(0.96); }
}
@keyframes yuv-glitch {
0%, 100% { transform: translateX(0); }
20% { transform: translateX(-3px); }
40% { transform: translateX(3px); }
60% { transform: translateX(-2px); }
80% { transform: translateX(2px); }
}
@keyframes yuv-cruise {
0%, 100% { transform: translateX(0) translateY(0); }
50% { transform: translateX(8px) translateY(-6px); }
}
@keyframes yuv-takeoff {
0% { transform: translate(-200px, 60px) rotate(-2deg); opacity: 0; }
40% { opacity: 1; }
100% { transform: translate(0px, -40px) rotate(-12deg); opacity: 1; }
}
@keyframes yuv-stampIn {
0% { opacity: 0; transform: scale(1.4) rotate(-3deg); }
60% { opacity: 1; transform: scale(0.95) rotate(-1deg); }
100% { opacity: 1; transform: scale(1) rotate(0deg); }
}
import React from 'react';
/**
* Vertical purple accent bar — sits to the left of content-slide headlines.
* The single most consistent brand mark in the YUV.AI design system.
*
* <div style={{ display: 'flex', alignItems: 'center' }}>
* <PurpleBar height={96} />
* <h2>The flight plan.</h2>
* </div>
*/
export const PurpleBar: React.FC<{
height?: number;
width?: number;
offset?: number;
color?: string;
}> = ({ height = 96, width = 8, offset = 0, color = '#5E17EB' }) => (
<span
aria-hidden
style={{
display: 'inline-block',
width,
height,
background: color,
marginRight: 28,
transform: `translateY(${offset}px)`,
verticalAlign: 'middle',
flexShrink: 0,
}}
/>
);
import React from 'react';
/**
* Hand-drawn SVG yellow underline. Sits under ONE specific word for emphasis.
*
* <span style={{ position: 'relative', display: 'inline-block' }}>
* <span style={{ position: 'relative', zIndex: 2 }}>explode.</span>
* <YellowUnderline width={210}
* style={{ position: 'absolute', left: -4, bottom: -16, zIndex: 1 }} />
* </span>
*
* Sizing: width should match the underlined word's rendered width.
* Don't float decoratively — it should always anchor a specific word.
*/
export const YellowUnderline: React.FC<{
width?: number;
color?: string;
strokeWidth?: number;
style?: React.CSSProperties;
}> = ({ width = 360, color = '#FFEC00', strokeWidth = 10, style }) => (
<svg
viewBox="0 0 360 24"
width={width}
height={(24 * width) / 360}
style={style}
aria-hidden
>
<path
d="M 8 16 C 70 6, 140 22, 200 12 S 320 4, 352 14"
stroke={color}
strokeWidth={strokeWidth}
strokeLinecap="round"
fill="none"
/>
</svg>
);
Lessons learned — hardened patterns
Production-tested patterns and the anti-patterns they replace. Each entry below cost a real iteration in a real session. Read before starting any web or visual output.
---
§1. Bilingual = toggle, never side-by-side
Anti-pattern: Showing English headline + Hebrew headline stacked or in adjacent columns. Anton at 8vw + Rubik 900 at 5–6vw visually collide because line-heights below 1.0 cause descenders to invade the next ascender row. Users see "the fonts are way too thick and override each other."
Pattern: Language toggle. One language visible at a time. data-lang="en" / data-lang="he" attributes on every translatable element + a universal show/hide rule + a state handler.
HTML
<div class="lang-toggle" role="group" aria-label="Language toggle">
<button class="lang-btn active" data-lang-set="en" aria-label="English"><span>🇺🇸</span>EN</button>
<button class="lang-btn" data-lang-set="he" aria-label="עברית"><span>🇮🇱</span>HE</button>
</div>
<h1>
<span data-lang="en">Pick the effect, not the code.</span>
<span data-lang="he">בחר את האפקט, לא את הקוד.</span>
</h1>CSS
html:not([lang="he"]) [data-lang="he"] { display: none !important; }
html[lang="he"] [data-lang="en"] { display: none !important; }
[data-lang="he"] { direction: rtl; unicode-bidi: embed; }
[data-lang="he"]:where(p, span:not(.flag), em, strong) {
font-family: 'Assistant', 'Inter', sans-serif;
}
h1 [data-lang="he"], h2 [data-lang="he"], h3 [data-lang="he"], h4 [data-lang="he"] {
font-family: 'Rubik', sans-serif;
font-weight: 900;
}
.lang-toggle {
display: inline-flex; gap: 0; padding: 2px;
background: rgba(10,10,10,0.05);
border: 1px solid rgba(10,10,10,0.1);
border-radius: 999px;
}
.lang-btn {
font-family: 'Anton', sans-serif;
font-size: 12px; letter-spacing: 0.10em; text-transform: uppercase;
padding: 5px 12px; border-radius: 999px;
background: transparent; border: none; cursor: pointer;
display: inline-flex; align-items: center; gap: 6px;
}
.lang-btn.active { background: var(--yuv-purple, #5E17EB); color: #fff; }JS
const TITLES = {
en: 'Yuval Avidani — Let’s Fly High',
he: 'יובל אבידני — בואו נטוס גבוה',
};
function setLang(lang) {
document.documentElement.lang = lang;
document.title = TITLES[lang] || TITLES.en;
document.querySelectorAll('[data-lang-set]').forEach(btn => {
btn.classList.toggle('active', btn.dataset.langSet === lang);
});
try { localStorage.setItem('yuv-lang', lang); } catch {}
}
document.querySelectorAll('[data-lang-set]').forEach(btn => {
btn.addEventListener('click', () => setLang(btn.dataset.langSet));
});
setLang(localStorage.getItem('yuv-lang') || document.documentElement.lang || 'en');Don't forget `document.title` — leaving the tab title in English is the most common "half-built bilingual" giveaway.
Mobile rule: the toggle must be duplicated into the always-visible mobile cluster, not just inside the hamburger menu. Otherwise mobile users need one extra interaction to switch language.
---
§2. Mobile = hamburger, always, at ≤880px with > 6 nav items
Anti-pattern: A flat horizontal nav with flex-wrap: wrap. On desktop fine; on mobile it eats 50% of the viewport.
Pattern: Hamburger menu for max-width: 880px whenever nav has more than 6 items.
<header class="topbar">
<a class="brand" href="/">YUV.AI</a>
<button class="hamburger" aria-label="Menu" aria-expanded="false">☰</button>
<nav class="nav">
<a href="#about">About</a>
<a href="#work">Work</a>
<a href="#course">Course</a>
<a href="#talks">Talks</a>
<a href="#blog">Blog</a>
<a href="#contact">Contact</a>
<div class="lang-toggle">...</div>
</nav>
</header>.topbar { display: flex; align-items: center; justify-content: space-between; padding: 18px 32px; }
.hamburger { display: none; background: none; border: none; font-size: 24px; cursor: pointer; }
.nav { display: flex; gap: 22px; align-items: center; }
@media (max-width: 880px) {
.hamburger { display: block; }
.nav { position: absolute; top: 64px; left: 0; right: 0; flex-direction: column; align-items: flex-start; gap: 14px; padding: 24px; background: var(--yuv-grey); border-top: 1px solid rgba(0,0,0,0.08); transform: translateY(-110%); transition: transform 220ms ease; }
.nav.open { transform: translateY(0); }
}const hb = document.querySelector('.hamburger');
const nav = document.querySelector('.nav');
hb.addEventListener('click', () => {
const open = nav.classList.toggle('open');
hb.setAttribute('aria-expanded', String(open));
});---
§3. One display headline per section — never two stacked
Stacking two display-weight fonts (e.g. Anton h1 + Rubik h1) with line-height < 1.0 causes overlap. If you need a subhead, make it 50% of the headline size and don't use a display font for it.
---
§4. Never assign HTML strings as element content with variables
Use el.textContent = value for plain text. Use createElement + appendChild for structured content. Reserve direct HTML-string assignment for compile-time literal strings only. Variable HTML assignment is an XSS hole the moment a variable can take user data — and modern browsers' security hooks rightfully block it.
---
§5. Spec-driven content — zero hallucination
When the user says "add a CTA for X" or "embed link to Y":
- WebFetch for HTML-static pages (blogs, docs, GitHub).
- `curl` for API endpoints or raw HTML.
- Claude-in-Chrome for JS-rendered SPAs (Teachable, Kajabi, learn.*, builder.io, practical.yuv.ai, most modern landing-page builders).
If WebFetch returns suspiciously thin output (< 500 chars, only <title> and meta), it's an SPA — re-route to Claude-in-Chrome. Don't bridge gaps with plausible-sounding invention. If a field isn't on the page, write "not on page" and ask.
---
§6. Mobile-first responsive is a baseline check, not polish
Before declaring any frontend work done:
1. Resize the preview to 375×812 (iPhone size). 2. Screenshot. 3. Verify: nav fits, no horizontal text overflow, headlines readable, grid collapses correctly.
If any of those fail, fix and re-screenshot. This is a 30-second check that prevents shipped bugs.
---
§7. IntersectionObserver for catalogs with > 8 GSAP demos
Multiple GSAP timelines running at 30fps simultaneously will cook the CPU. Pause off-screen demos:
const observer = new IntersectionObserver((entries) => {
entries.forEach(e => {
const tl = e.target.__tl;
if (!tl) return;
if (e.isIntersecting) tl.play();
else tl.pause();
});
}, { threshold: 0.15 });
document.querySelectorAll('.stage').forEach(s => observer.observe(s));Each stage stores its timeline at stage.__tl = tl. Standard pattern.
---
§8. e.stopPropagation() on inner clickable elements
A clickable card with a clickable child (e.g. a copy-to-clipboard chip inside a card link) causes both the inner handler AND the parent navigation to fire. Stop propagation on the inner:
chip.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
navigator.clipboard.writeText(value);
// toast...
});---
§9. Effect-ID copy chip
Every browse-and-pick interface (catalog, design system, component library) gets a copyable identifier on every item:
- Pink/yellow/purple chip with monospace font.
- Click to copy. Toast confirmation.
e.stopPropagation()if nested inside a card link.
<button class="chip" data-copy="effect.purple-bar.v1">
<span>effect.purple-bar.v1</span>
<i class="ph ph-copy"></i>
</button>.chip {
font-family: 'JetBrains Mono', monospace;
font-size: 12px;
padding: 4px 10px;
background: var(--yuv-yellow);
color: #000;
border: 1px solid #000;
border-radius: 0;
cursor: pointer;
display: inline-flex; gap: 8px; align-items: center;
}---
§9a. Anton letter-spacing — DEFAULT TO ZERO, never negative
Anti-pattern (real production miss): setting Anton at letter-spacing: -0.03em to -0.04em because "headlines feel tighter that way." Anton is already a condensed display face. Layering negative tracking on top makes letters touch — especially visible inside yellow box-decoration-break: clone spans, where the highlight box reads as a wall of crammed glyphs. Users describe it as "the type feels too thick" without being able to name the cause.
Pattern:
/* default Anton */
.display { font-family: 'Anton', sans-serif; text-transform: uppercase; letter-spacing: 0; line-height: 1.0; }
/* only at hero size (≥ 180px), only when measured */
.display-hero { letter-spacing: -0.01em; } /* max — never tighter */
/* yellow highlight span: ALWAYS positive tracking + em-based padding */
.highlight {
display: inline-block;
background: #FFEC00; color: #000;
padding: 0.08em 0.4em; /* em-based — scales with font size */
letter-spacing: 0.01em; /* slight POSITIVE tracking inside the box */
box-decoration-break: clone;
-webkit-box-decoration-break: clone;
}Detection: any Anton CSS with letter-spacing: -0.0[234]em is wrong. Replace with 0 (default) or -0.01em (hero only). Any .highlight with pixel padding instead of em padding is wrong at hero sizes.
---
§9b. One Anton element per slide / section
Anti-pattern: A slide with a big Anton headline AND a row of big Anton stat tiles (e.g. headline + 1. / ON DEMAND / /SKILLS. / ∞ all in Anton). Five Anton elements stacked = visual brick wall. The user can't name what's wrong; they just say "too thick."
Pattern: Anton appears once per slide — as the hero. Supporting numbers and labels render in:
- Inter 900 at the same large size — holds up at hero scale without condensed-display heaviness.
- JetBrains Mono 700 for instrument-style readouts (HUD cells, big numbers with monospace character).
Detection: count Anton elements on a slide. If > 1, decide which one is the hero and downgrade the rest.
---
§10. Scope creep on catalogs — build comprehensive on first pass
When the user asks for "a catalog of effects" or "a showcase of components", build comprehensive immediately. The proven 18+ section structure for a catalog site:
Captions / Markers / Cards / Devices / Magic / 3D / Themes / Transitions / Text / Charts / SFX / Layouts / Features / GSAP / Prompts / Scenarios / Tech / Skills / Connect / Course
Building < 10 sections first means re-iterating 3 times to get to "comprehensive". Plan for 18+ from minute 1.
---
§11. SSL cert provisioning impatience on GitHub Pages custom domain
If the GH Pages cert state stays at none for > 15 min after DNS propagates correctly, toggle the custom domain off and on to kick the Let's Encrypt validation flow:
gh api -X PUT /repos/{owner}/{repo}/pages -f "cname="
sleep 5
gh api -X PUT /repos/{owner}/{repo}/pages -f "cname=<custom-domain>"Cert lands within 15 min after the toggle. Don't wait the rumored 24 hours.
---
§12. Per-skill install command, not per-repo
If a skills repo has many skills and the user wants one, use sparse-checkout to install just that one:
git clone --depth 1 --filter=blob:none --sparse https://github.com/<owner>/<repo> /tmp/x \
&& cd /tmp/x && git sparse-checkout set skills/<skill-name> \
&& mkdir -p ~/.claude/skills && cp -R skills/<skill-name> ~/.claude/skills/ \
&& rm -rf /tmp/xnpx skills add <repo> shows an interactive checkbox list and forces a manual pick — bad UX for "I want this one specific skill".
---
§13. Hero = real Hyperframes-compatible composition
For any major site, the hero section should use real Hyperframes data-* attributes (data-composition-id, data-start, data-duration, data-width="1920", data-height="1080") on the stage element. The site itself becomes a Hyperframes artifact — hyperframes render on the same code produces an MP4 promo. Two outputs (web + video) from one source.
---
§14. Anton + yellow span = line-height ≥ 1.0
When an Anton headline at a large size contains an inline-block yellow highlight span (display: inline-block; background: #FFEC00; box-decoration-break: clone), the highlight box eats the previous line's descenders if line-height < 1.0. Bump to 1.0 minimum whenever the headline is multi-line.
---
§15. <YellowUnderline> always anchors to a specific word
Never floats decoratively. Width matches the underlined word's rendered width. Position absolute, bottom: -16px, left: -4px of the parent span. If it isn't sized to its word, it looks like a stray underscore.
---
Quick reference
| Symptom | Pattern |
|---|---|
| EN + HE side-by-side | §1 — toggle |
| Mobile nav wrapping | §2 — hamburger ≤880px |
| Two display headlines stacked | §3 — one per section |
| XSS warning from inserting HTML | §4 — textContent / createElement |
| Made-up CTA content | §5 — read the page first |
| Mobile broken on ship | §6 — mobile-first baseline check |
| Page CPU pegged at 100% | §7 — IntersectionObserver |
| Card link navigating on inner button click | §8 — stopPropagation |
| Catalog asked to be "bigger" 3 times | §10 — build comprehensive |
| GH Pages cert stuck | §11 — toggle CNAME |
| User wants 1 of many skills | §12 — sparse-checkout |
| Yellow span eating descenders | §14 — line-height ≥ 1.0 |
| Yellow underline looks random | §15 — anchor to specific word |
Neon mode — the YUV.AI default palette
The everyday YUV.AI brand. Use for web, apps, games, dashboards, social images, posters, AI tools, profile/about pages — basically anything that isn't a slide deck. Hot pink as the lead, neon cyan as the electric counterpoint, white or rich black as the canvas. Tavus-inspired confidence; YUV.AI flight throughline.
This is the brand chapter most YUV.AI surfaces live in. Decks (Fly High purple/yellow/grey) are the exception, not the rule.
---
Tokens
:root {
/* Brand */
--yuv-pink: #FF1464; /* primary thread — CTAs, brand accents, hero spans */
--yuv-pink-hot: #FF0080; /* high-energy variant — glow shadows, hover states */
--yuv-cyan: #00E5FF; /* electric secondary — data points, focus rings, links */
--yuv-cyan-edge: #00FFFF; /* max-neon — small surfaces only, never bodies of text */
/* Surfaces */
--yuv-white: #FFFFFF; /* canvas (light mode) */
--yuv-black: #0A0A0A; /* canvas (dark mode), text-on-light */
--yuv-charcoal: #1A1A1A; /* secondary text on light, secondary surface on dark */
--yuv-soft: #F4F4F6; /* alt light surface for subtle layering on #FFFFFF */
--yuv-line: rgba(255,20,100,0.18); /* divider lines, faint pink */
--yuv-line-cy: rgba(0,229,255,0.22); /* divider lines, faint cyan */
--yuv-shadow: 0 0 24px rgba(255,20,100,0.35); /* pink glow shadow */
--yuv-shadow-cy: 0 0 24px rgba(0,229,255,0.30); /* cyan glow shadow */
}JSON form: references/tokens/palette.json (Decks tokens for now; Neon tokens to be added on next iteration).
Canvas — pick one, never grey
Neon mode allows exactly two canvas options. Don't blend them into a grey middle.
| Canvas | Use for | Pair with |
|---|---|---|
| `#FFFFFF` (light) | Marketing pages, About/profile pages, lighter dashboards, brochure-style sites | Pink for CTAs and accents, cyan for highlights, charcoal text |
| `#0A0A0A` (rich black) | Product UIs, games, dashboards, "premium" surfaces, anything where glow effects matter | Pink hero accents, cyan secondary, white text, pink/cyan glow allowed |
No `#F1F2F2` greys (that's Decks mode), no `#FAFAF7` off-whites (that's Warm Editorial). Pure white or rich black. Pick once per project.
---
Pink, cyan, and the rule of one accent at a time
Pink and cyan are both saturated. If they share equal weight on the same surface, the eye doesn't know where to land. Use them in a lead + counter relationship:
| Surface | Lead | Counter |
|---|---|---|
| Hero CTA + body | Pink (CTA fill, "current" highlight) | Cyan (link hover, secondary CTA outline) |
| Data dashboard | Cyan (chart lines, scale highlights) | Pink (hero stat, status callouts) |
| Game UI | Pink (player accents, health) | Cyan (collectibles, energy meters) |
| Form fields | Cyan (focus ring) | Pink (validation errors, "primary" submit) |
If pink dominates the surface, cyan can only appear on small high-attention elements (a focus ring, a hover state, a single data point). If cyan dominates, pink shows up as the human/CTA layer.
Never have pink AND cyan both at 100% surface area — pick which is lead per screen.
---
Glow effects — allowed, in moderation
This is the mode where YUV.AI feels like a neon sign. Glow is welcome, controlled.
Text glow
.hero {
color: var(--yuv-pink);
text-shadow:
0 0 16px rgba(255,20,100,0.65),
0 0 32px rgba(255,20,100,0.35);
}
.hero-cyan {
color: var(--yuv-cyan);
text-shadow:
0 0 18px rgba(0,229,255,0.6),
0 0 36px rgba(0,229,255,0.3);
}Box glow (cards, CTAs)
.cta-primary {
background: var(--yuv-pink);
color: #FFFFFF;
box-shadow:
0 0 24px rgba(255,20,100,0.5),
0 0 48px rgba(255,20,100,0.25);
}
.cta-primary:hover {
background: var(--yuv-pink-hot);
box-shadow:
0 0 32px rgba(255,0,128,0.7),
0 0 64px rgba(255,0,128,0.35);
}
.card-on-black {
background: #0A0A0A;
border: 1px solid var(--yuv-line);
box-shadow: inset 0 0 0 1px transparent;
transition: box-shadow 240ms ease;
}
.card-on-black:hover {
border-color: rgba(0,229,255,0.4);
box-shadow: 0 0 24px rgba(0,229,255,0.20);
}Don't double-glow. Either text-shadow OR box-shadow on a given element, not both. Glow is dramatic — use sparingly, on the hero, primary CTA, and one or two accents per surface. Glow on every element = strip-mall neon, not premium.
Gradients — pink → cyan ONLY
Only acceptable gradient direction. Small surfaces (text underlines, progress bars, button borders). Never a full-page gradient wash.
.progress { background: linear-gradient(90deg, #FF1464 0%, #00E5FF 100%); }
.underline-grad { background: linear-gradient(90deg, #FF1464, #00E5FF); }---
Card patterns
Rich-black surface card
.card {
background: #0A0A0A;
color: #FFFFFF;
border: 1px solid rgba(255,20,100,0.22);
border-radius: 0; /* always 0 in YUV.AI; pills are the only other option */
padding: 32px 36px;
}
.card.lead { border-left: 4px solid var(--yuv-pink); }
.card.electric { border-left: 4px solid var(--yuv-cyan); }White-surface card
.card-light {
background: #FFFFFF;
color: #0A0A0A;
border: 1px solid rgba(0,0,0,0.08);
border-left: 4px solid var(--yuv-pink);
border-radius: 0;
padding: 32px 36px;
}Same shape across modes — border-radius: 0, 4px-stripe left border for "lead" cards. Color of the stripe changes per mode.
---
CTAs
<!-- Primary, pink-fill pill -->
<a class="cta primary" href="...">START THE CLIMB</a>
<!-- Secondary, cyan-outline pill -->
<a class="cta secondary" href="...">SEE THE DEMO</a>.cta {
display: inline-flex; align-items: center; gap: 10px;
font-family: 'Anton', sans-serif;
text-transform: uppercase;
letter-spacing: 0.04em;
padding: 16px 32px;
border-radius: 999px; /* pill — only allowed radius besides 0 */
text-decoration: none;
font-size: 18px;
transition: transform 180ms ease, box-shadow 240ms ease;
}
.cta.primary {
background: var(--yuv-pink);
color: #FFFFFF;
box-shadow: 0 0 20px rgba(255,20,100,0.45);
}
.cta.primary:hover {
background: var(--yuv-pink-hot);
transform: translateY(-1px);
box-shadow: 0 0 32px rgba(255,0,128,0.6);
}
.cta.secondary {
background: transparent;
color: var(--yuv-cyan);
border: 2px solid var(--yuv-cyan);
}
.cta.secondary:hover { background: rgba(0,229,255,0.08); }---
Universal Fly High throughline in Neon
These motifs travel from Decks mode INTO Neon mode — the palette changes, the brand DNA doesn't:
Neon-flavored HUD strip (for product UIs)
A bottom or top status strip with phase tag + readouts, in the Neon palette:
<div class="hud-neon">
<span class="phase">◉ CRUISE</span>
<div class="cell"><span class="lbl">USERS</span><span class="val">12,840</span></div>
<div class="cell"><span class="lbl">SPD</span><span class="val">486 KT</span></div>
<div class="cell"><span class="lbl">FUEL</span><span class="val">76%</span></div>
<span class="flight">FLIGHT FH-YUV-AI · 02 / 12</span>
</div>.hud-neon {
display: flex; align-items: center; gap: 16px;
padding: 14px 32px;
background: rgba(10,10,10,0.85);
border-top: 1px solid var(--yuv-line);
color: #FFFFFF;
font-family: 'JetBrains Mono', monospace;
}
.hud-neon .phase {
font-size: 11px; letter-spacing: 0.3em; font-weight: 700;
color: var(--yuv-cyan);
border: 1px solid var(--yuv-cyan);
padding: 4px 10px;
}
.hud-neon .cell { display:flex; flex-direction:column; padding: 4px 12px; }
.hud-neon .lbl { font-size: 9px; letter-spacing: 0.22em; opacity: 0.6; }
.hud-neon .val { font-size: 15px; font-weight: 700; color: var(--yuv-pink); margin-top: 2px; }
.hud-neon .flight { font-size: 11px; letter-spacing: 0.18em; opacity: 0.55; margin-inline-start: auto; }Altimeter / progress dial (decoration, sparingly)
Same CompassDial / AltimeterDial components from Decks mode CAN be re-skinned for Neon — swap the purple stroke for cyan, the yellow needle for pink:
<CompassDial heading={287} strokeColor="#00E5FF" needleColor="#FF1464" backgroundColor="#0A0A0A" />Use rarely — the dial is a flourish, not a fixture in Neon mode. Hero / loading screen / "console" surfaces only.
Phoenix mark + watermark
Same brand-asset rules as the other modes — bottom-right corner of any shareable surface, ~120–180px wide on a 1920 canvas. The phoenix art works against both white and rich-black canvas.
---
Typography in Neon
Same system fonts as all other modes — Anton uppercase for display (letter-spacing: 0 default), Inter for body, JetBrains Mono for instrument readouts. The colors change:
| Role | On white canvas | On rich-black canvas |
|---|---|---|
| Hero headline | #0A0A0A charcoal OR #FF1464 pink with glow | #FFFFFF white OR #FF1464 pink with glow |
| Sub-headline | #0A0A0A | #FFFFFF |
| Body | #1A1A1A | rgba(255,255,255,0.85) |
| Caption | rgba(10,10,10,0.55) | rgba(255,255,255,0.55) |
| Mono readout | #FF1464 pink for emphasised values; #00E5FF for secondary | same |
| Link | #FF1464 underline, hover → #00E5FF | same |
The Anton-with-yellow-highlight pattern from Decks mode doesn't translate to Neon. In Neon, the punchline word uses a pink underline or a pink background span (not yellow), with the same letter-spacing: 0.01em + em-based padding rule:
<h1>The future has <span class="hl-pink">arrived.</span></h1>.hl-pink {
display: inline-block;
background: var(--yuv-pink);
color: #FFFFFF;
padding: 0.08em 0.4em;
letter-spacing: 0.01em;
-webkit-box-decoration-break: clone;
box-decoration-break: clone;
}---
Anti-patterns specific to Neon mode
- Pink + cyan + yellow all at once. That's three accents on one surface — the brand chapter mixes. Pick lead/counter, leave yellow for Decks mode.
- Multi-hue gradients. Pink → cyan only. No rainbow, no purple, no blue→green.
- Soft drop-shadow rounded-corner template cards. Still banned (it's the AI-slop tell across every mode).
- Default Tailwind classes. Same rule. No
slate-*,zinc-*,indigo-*,cyan-500(use the custom--yuv-cyan). - Border radius 8–12px. Same rule.
0or999px. Nothing in between. - Glow on every element. Strip-mall neon — too much. Keep glow on hero / primary CTA / one or two accents per surface.
- `box-shadow: 0 4px 6px rgba(0,0,0,0.1)` defaults. Replace with pink or cyan glow, OR omit entirely.
---
When NOT to use Neon (route to a different mode)
- A slide deck. Use Decks (Fly High) —
references/presentations.md. - A Hope / Marcus / bigcats / practical.yuv.ai surface. Use Warm Editorial —
references/palettes-warm-editorial.md. - Yuval explicitly said "purple", "Fly High palette", "yellow accent", etc. Honor the explicit override.
- A non-YUV.AI project. This whole skill shouldn't be applying. Step back.
---
Self-check before shipping Neon work
1. Canvas is #FFFFFF OR #0A0A0A — never a grey middle. 2. Pink is the lead OR cyan is the lead — not both at equal weight. 3. Anton hero with letter-spacing: 0 default, max -0.01em at ≥180px. 4. Glow on hero / primary CTA only — not on every element. 5. Gradients are pink → cyan, small surfaces only. 6. Border-radius 0 or 999px. No 8–12px corners. 7. Brand watermark in the bottom-right of any shareable surface. 8. Canonical socials + credentials in the footer / About / credits. 9. At least one Fly High throughline motif (HUD, dial, progress, phoenix, "let's fly high" copy) is present.
Warm Editorial — alternate palette mode
The pink / yellow / bone variant. Use only for projects in this brand family:
- Hope — the cheetah video series and related campaign content
- Marcus — the white lion content
- bigcats.ai
- practical.yuv.ai — the course landing page
- Anywhere a "warm, confrontational, paper-feel, editorial" mood fits better than the Fly High "bold, electric, optimistic" mood
For everything else — keynotes, dashboards, technical product pages, hackathon decks — default to Fly High.
---
Tokens
:root {
--pink: #FF1464; /* primary brand, CTAs, thread color */
--yellow: #E5FF00; /* accent — when it shows up, it dominates */
--black: #0A0A0A; /* warm near-black, never blue-black */
--off-white: #FAFAF7; /* paper-feel, replaces #FFFFFF entirely */
--bone: #F5EEE4; /* transitional sections, cream */
--charcoal: #1A1A1A; /* primary text on light */
--warm-gray: #8B8680; /* secondary text on light */
--light-gray: #A8A39D; /* secondary text on dark */
}---
Strict rules
- Pure white `#FFFFFF` is banned. Use
--off-whitefor every surface a white would normally occupy. Page background, cards, text on dark. - Pink is the thread. Appears in: logo, primary CTA, section accents.
- Yellow dominates when it appears. Full background block, full-width divider, large pull-quote field. Never a timid swatch.
- Gradients only pink → yellow, only on small surfaces (buttons, text underlines, icon fills). Never a full-page gradient wash.
- Warm-toned shadows with pink/orange undertone:
box-shadow: 0 8px 32px rgba(255, 20, 100, 0.08); /* card */
box-shadow: 0 20px 60px rgba(255, 20, 100, 0.12); /* card-hover */Never blue-black shadows.
- No `<PurpleBar>` — that's Fly High. In Warm Editorial, content sections lead with a small bone-colored eyebrow + Anton headline. No vertical bar.
---
Grain / paper texture (signature)
1–2% opacity SVG noise on light backgrounds for paper feel:
body { background-color: var(--off-white); position: relative; }
body::before {
content: '';
position: fixed; inset: 0; pointer-events: none; z-index: 1; opacity: 0.02;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3' /%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}Lift opacity to 0.04 for more pronounced grain; drop to 0.01 for subtle. Never above 0.05 — that's a visual noise, not a paper feel.
---
Tailwind preset
module.exports = {
theme: {
extend: {
colors: {
pink: '#FF1464', yellow: '#E5FF00', black: '#0A0A0A',
'off-white': '#FAFAF7', bone: '#F5EEE4',
charcoal: '#1A1A1A', 'warm-gray': '#8B8680', 'light-gray': '#A8A39D',
},
fontFamily: {
display: ['Anton', 'Rubik', 'sans-serif'],
body: ['Inter', 'Assistant', 'sans-serif'],
},
boxShadow: {
warm: '0 8px 32px rgba(255, 20, 100, 0.08)',
'warm-lg':'0 20px 60px rgba(255, 20, 100, 0.12)',
},
maxWidth: { content: '1440px' },
},
},
};---
Differences from Fly High at a glance
| Aspect | Fly High | Warm Editorial |
|---|---|---|
| Primary brand | #5E17EB purple | #FF1464 pink |
| Page background | #F1F2F2 grey | #FAFAF7 off-white |
Pure #FFFFFF | Allowed on cards only | Banned everywhere |
| Yellow | #FFEC00 | #E5FF00 |
| Shadows | Plain low-opacity black | Warm pink/orange undertone |
| Grain | None — flat grey | 1–2% opacity SVG noise |
| Cards | White bg + 1px border + 4px purple left border | Off-white bg + warm shadow, no left border |
| Vertical accent | <PurpleBar> mandatory | Not used |
| Two-background rule | Yes (act / content) | No — single off-white canvas + occasional yellow section |
| Diagonal decoration | Rotated purple-dark in corners of act slides | Not typically used |
Presentations — slide decks, keynotes, hackathon talks
Yuval's slide system is the original source of the Fly High palette. These rules govern decks, keynote captures, conference talks, hackathon presentations, demo screen-recordings, and any slide-based output.
---
Canvas
- 1920×1080 at 16:9. Always.
- Padding:
100pxhorizontal,120pxvertical. Generous whitespace is part of the premium feel. - No grid — asymmetric layouts. One element breaks the implied grid in every content slide.
---
The two-background rule (non-negotiable)
Every slide is either an act slide or a content slide. No third option.
Act slide
- Background:
#5E17EBpurple - Headline:
#FFFFFFwhite in Anton - Use for: title slide, section dividers ("ACT II"), closer, hero / cinematic moments
- Optional decoration: diagonal
--purple-darkblock rotated ~45° in a corner at ~30–40% opacity
Content slide
- Background:
#F1F2F2light grey - Headline:
#000black in Anton, flanked left by a<PurpleBar> - Use for: everything else (info, evidence, lists, dashboards, architecture, stat, story)
- Cards on content slides:
#FFFFFFbackground,1px solid rgba(0,0,0,0.12)border,4px solid #5E17EBleft border
Yellow is an accent on either background — never floor a slide in yellow.
---
The FlightHUD strip (every slide)
A horizontal HUD strip sits at the bottom of every slide:
- Progress bar (slide N / total)
- Flight phase tag (PRE-FLIGHT · TAKEOFF · CLIMB · CRUISE · DESCENT · FINAL · LANDED)
- Four readout cells: ALT (ft) · SPD (kt) · HDG (°) · FUEL (%)
- Citation slot (left-aligned, JetBrains Mono, dim)
- Flight ID (right-aligned, e.g. "FLIGHT FH-YUV-AI")
- Slide number (right-aligned, Anton, 22px, zero-padded)
Component lives at references/components/FlightHUD.tsx. The default flight-status function (defaultFlightStatus(n, total)) maps slide index to phase across a 23-slide journey. Override status for custom decks.
The HUD tone (content vs purple) auto-adapts to the slide's background.
---
Slide types
Title / opening (act)
- Massive Anton wordmark or talk title, 280–360px, white on purple.
- Small JetBrains Mono eyebrow above (e.g.
PRE-FLIGHT · MAY 12 2026 · TLV). - One supporting line in Inter 400 below the headline.
- Optional
<CompassDial>or<AltimeterDial>decoration in a corner. - FlightHUD at bottom, tone="purple".
Section divider (act)
- Anton headline ~200px ("ACT II / EVIDENCE").
- A single Anton subline in yellow OR a yellow highlight span.
- Diagonal
--purple-darkdecoration in a corner.
Content (info, body)
<PurpleBar>to the left of every H1 (96–140px).- H1 in Anton uppercase black.
- Body in Inter 400, 22–28px, max 60ch wide.
- Cards laid out in a 2- or 3-column asymmetric grid.
Stat (big number)
- One enormous Anton number (200–280px),
<CounterUp>animated. - Above: JetBrains Mono eyebrow with tracking 0.3em (e.g. "AVERAGE WIN RATE · MAY 2026").
- Below: Inter 400 caption with the supporting context (one line max).
Quote / pull-quote (content)
- Anton uppercase headline in
clamp(80px, 8vw, 140px). - One word highlighted with yellow
box-decoration-break: clonespan. - Attribution in Inter 500, 18px, color
rgba(0,0,0,0.55).
Closing / CTA (act)
- Single line of Anton white on purple.
- Pill CTA — black background, white Anton text,
border-radius: 999px, padding1.25rem 2.5rem. - Linktree URL OR a single QR code centered.
- Brand watermark (
logo-rectangle-wordmark.png) bottom-right.
---
Signature video banner — mandatory on landing sites
Every landing page or marketing site opens with a cinematic product-demo video banner. Not optional. "Hero with static text and a stock image" is banned.
What the video is
- A standalone Hyperframes-compatible HTML composition at
public/demo/index.html. - Embedded as a full-width 16:9
<iframe>at the top of the page, above the traditional hero section. - Self-looping in the browser via
onComplete → rebuild → play(0)— neverrepeat: -1(breaks Hyperframes capture). - Renderable to MP4 later via
npx hyperframes render.
6-scene narrative arc (~12–18 seconds total)
1. Logo slam (~1s) — giant Anton wordmark + triangle mark slams in on off-white. Establishes brand. 2. Headline hold (~2s) — massive black scene with yellow/pink Anton headline stating the core promise. Subtitle in white Anton below. 3. Device interaction (~4s) — iPhone or MacBook mockup with a LIVE product dashboard inside it. A pink cursor enters, hovers over multiple interactive elements with row highlights, then clicks. The product transitions to a detail view. Cursor continues interacting. This is the signature moment. 4. Evidence cascade (~3s) — full-screen cascade of 3–4 evidence/feature cards entering with staggered gsap.from({ opacity: 0, y: 40, scale: 0.96 }) at 150ms intervals. 5. Climactic payoff (~2.5s) — massive headline reveal with the core stat, accompanied by 2–3 counters animating up from 0 (onUpdate callbacks, power2.out ease). 6. Pink/purple flood CTA (~2s) — full brand-color background, enormous Anton headline ("Stop missing what matters" / "Start flying"), solid black pill CTA. Closes the narrative.
Device mockup specifics
- iPhone for consumer / vertical product stories: pure CSS frame at 540×1100px container, 72px border-radius, 18px dark padding, inner screen at 54px radius, notch as absolute-positioned black pill.
- MacBook for enterprise / dashboard products: CSS frame with laptop base and screen bezel.
- Inside the device: real product UI rendered in HTML, not screenshots. The cursor actually interacts with elements.
Cursor choreography
The cursor is the emotional vehicle. Treat it like a character.
- Pink (or purple, in Fly High) teardrop cursor rotated -45°, off-white inner dot, 38px.
- Entry from bottom-right at 0.5 scale, scaling to 1.0 as it settles.
- Multiple interactions before the main click — hover over 2–3 other elements first to establish the scan. Each hover triggers a subtle row highlight.
- Click cue:
scale: 0.85press on cursor, syncedclick-ringelement (80pxcircle, brand border) expandingscale 0.5 → 2.2, opacity 1 → 0over 0.6s. - Cursor continues after the click. Don't cut to black — show the cursor navigating the result.
Deterministic loop pattern
let tl = buildTimeline();
tl.eventCallback('onComplete', () => {
tl.kill();
tl = buildTimeline();
tl.play(0);
});
window.__timelines = window.__timelines || {};
window.__timelines['<composition-id>'] = tl;Scaling to viewport
Author at fixed dimensions (1920×1080 horizontal, 1080×1920 vertical). A fitToViewport() function computes scale = min(innerWidth/1920, innerHeight/1080) and applies via CSS transform with top-left origin, centered with margins. Re-runs on resize.
React embed
export function DemoVideo() {
return (
<section aria-label="Product demo" style={{ width: '100%', background: 'var(--yuv-grey)' }}>
<div style={{ position: 'relative', width: '100%', aspectRatio: '16 / 9' }}>
<iframe src="/demo/index.html" title="Product demo" loading="eager"
style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', border: 'none' }} />
</div>
</section>
);
}Mount as the first child of <main>, above the traditional hero section.
Why HTML over MP4
- Editable in seconds — no re-render cycle.
- Crisp at any resolution.
- Renders to MP4 on demand via
npx hyperframes render. - Text is live — A/B test copy without re-rendering.
- Lives with the code. No "which folder was that MP4 in?"
---
Watermarking
Every deck has the brand watermark (logo-rectangle-wordmark.png) in the bottom-right corner. Rules:
- Width ~120–180px on a 1920px canvas.
- Margin ~3% of canvas (~60px from right and bottom edges).
- Never stretch — preserve aspect ratio.
- Opacity 100% on grey content slides. 80% on purple act slides (the colored wordmark has enough internal contrast).
- Position:
position: absolute; bottom: 32px; right: 32px;.
---
Self-check for slide decks
1. Every slide is either act (purple) or content (grey) — no third background. 2. Yellow appears only as accent (highlight span, underline, instrument tag) — never as a slide background. 3. <PurpleBar> next to every content-slide H1. 4. <FlightHUD> at the bottom of every slide. 5. Watermark in bottom-right. 6. Anton headlines are UPPERCASE with `0 default, -0.01em only at hero size (≥ 180px) letter-spacing. 7. Line-height ≥ 1.0 on any multi-line Anton headline with a yellow highlight span. 8. <YellowUnderline> anchored under a specific word, never decorative. 9. Cards on grey: white bg + 1px border + 4px purple left border. 10. Final / closing slide has Linktree URL (or QR) and watermark.
Bio, credentials & links — canonical reference
Yuval's full set of credentials, public handles, and URLs, plus drop-in HTML/JSX patterns for footers, profile cards, speaker bios, and "find me elsewhere" sections.
Auto-include rule: Whenever an output has a footer, contact section, profile block, credits panel, video end-screen, speaker bio, course page, or any "find me elsewhere" surface, include this set without asking. Don't request URLs — they're below.
---
Credentials (canonical list — use in any bio surface)
| Credential | Phrasing variants |
|---|---|
| 2× GitHub Star | "2× GitHub Star", "two-time GitHub Star", "GitHub Star (twice)" |
| AWS Gen AI Superstar | "AWS Gen AI Superstar", "AWS GenAI Superstar" |
| AI commentator on Channel 12 News (Israel) | EN: "AI commentator on Channel 12 News", "Resident AI commentator on Channel 12 News (Israel)", "The AI guy on Channel 12 News" · HE: "מגיש פינת AI בחדשות 12" |
| AI Builder & Speaker | "AI Builder & Speaker", "AI Builder and Speaker", "Builder · Speaker · AI" |
| Technical Content Creator | "Technical Content Creator", "AI Educator", "Hebrew AI Educator" |
| Founder of YUV.AI | "Founder of YUV.AI", "Founder, YUV.AI" |
| Active communities across socials | "Active communities across X, Instagram, TikTok, YouTube, LinkedIn, Facebook, GitHub" |
Drop-in one-liner bios
Long (about page / course landing):
Yuval Avidani is a 2× GitHub Star, AWS Gen AI Superstar, and the resident AI commentator on Channel 12 News (Israel). Founder of YUV.AI, AI Builder & Speaker, and technical content creator with active communities across every major social platform. Builds, teaches, and ships AI that actually works.
Medium (speaker intro / podcast guest blurb):
AI Builder & Speaker · 2× GitHub Star · AWS Gen AI Superstar · AI commentator on Channel 12 News · Founder of YUV.AI.
Short (Twitter bio / video lower-third / 280-char):
2× GitHub Star · AWS Gen AI Superstar · The AI guy on @news12israel · Founder @yuv_ai
Hebrew (long):
יובל אבידני — מגיש פינת ה-AI בחדשות 12, GitHub Star פעמיים, AWS Gen AI Superstar, מייסד YUV.AI. בונה, מלמד, ומשגר AI שעובד. קהילות פעילות בכל הרשתות החברתיות.
Hebrew (short):
מגיש פינת AI בחדשות 12 · GitHub Star כפול · AWS Gen AI Superstar · מייסד YUV.AI
Credential badges block (visual surfaces)
For about sections, speaker pages, podcast art — render the credentials as a row of mono-font chips:
<div class="credentials" style="display:flex;flex-wrap:wrap;gap:10px;margin:24px 0;font-family:'JetBrains Mono',monospace">
<span style="background:#FFEC00;color:#000;padding:6px 14px;font-size:11px;letter-spacing:0.2em;font-weight:700">2× GITHUB STAR</span>
<span style="background:#5E17EB;color:#fff;padding:6px 14px;font-size:11px;letter-spacing:0.2em;font-weight:700">AWS GEN AI SUPERSTAR</span>
<span style="border:1px solid #000;color:#000;padding:6px 14px;font-size:11px;letter-spacing:0.2em;font-weight:700">CHANNEL 12 · AI COMMENTATOR</span>
<span style="border:1px solid #000;color:#000;padding:6px 14px;font-size:11px;letter-spacing:0.2em;font-weight:700">FOUNDER · YUV.AI</span>
</div>---
---
Machine-readable link set
{
"primary": {
"website": "https://yuv.ai",
"linktree": "https://linktr.ee/yuvai"
},
"social": [
{ "id": "x", "handle": "@yuvalav", "url": "https://x.com/yuvalav", "label": "X", "icon": "ph-x-logo" },
{ "id": "instagram", "handle": "@yuval_770", "url": "https://instagram.com/yuval_770", "label": "Instagram", "icon": "ph-instagram-logo" },
{ "id": "tiktok", "handle": "@yuval.ai", "url": "https://www.tiktok.com/@yuval.ai", "label": "TikTok", "icon": "ph-tiktok-logo" },
{ "id": "youtube", "handle": "@yuv-ai", "url": "https://youtube.com/@yuv-ai", "label": "YouTube", "icon": "ph-youtube-logo" },
{ "id": "github", "handle": "@hoodini", "url": "https://github.com/hoodini", "label": "GitHub", "icon": "ph-github-logo" },
{ "id": "facebook", "handle": "@yuval.avidani", "url": "https://facebook.com/yuval.avidani", "label": "Facebook", "icon": "ph-facebook-logo" },
{ "id": "linkedin", "handle": "yuval-avidani", "url": "https://www.linkedin.com/in/yuval-avidani-87081474/", "label": "LinkedIn", "icon": "ph-linkedin-logo" }
]
}For most use, just inline these URLs. For programmatic generation (e.g. building a Linktree-style page from data), use the JSON above.
---
Surface-by-surface inclusion rules
| Surface | What to include |
|---|---|
| Full website footer | All 7 socials + website + linktree, as icon-only buttons in the brand color. Order: X · Instagram · TikTok · YouTube · GitHub · Facebook · LinkedIn · Linktree · Website. |
| Speaker bio / about card | Website + LinkedIn + Linktree + one signature social (X for technical audience, Instagram for general). |
| Video end-screen | Website + 3 socials (X · Instagram · YouTube). |
| Email signature / certificate footer | Website + LinkedIn + Linktree. Plain text or compact icons. |
| Slide deck closing slide | Linktree-as-shortcut (single QR or single URL). Don't list all 8 on a slide — too noisy. |
| Social card "find me elsewhere" graphic | Same as full website footer order. |
| LinkedIn post / Twitter bio CTA | Linktree URL — one tap, everything. |
---
Drop-in HTML — Fly High mode
<footer class="yuv-socials" style="
display:flex; gap:18px; align-items:center; justify-content:center;
padding:48px 0; background:var(--yuv-grey); font-family:'Inter',sans-serif;
">
<a href="https://yuv.ai" aria-label="Website" style="color:#5E17EB;font-size:22px;text-decoration:none"><i class="ph ph-globe"></i></a>
<a href="https://x.com/yuvalav" aria-label="X" style="color:#5E17EB;font-size:22px;text-decoration:none"><i class="ph ph-x-logo"></i></a>
<a href="https://instagram.com/yuval_770" aria-label="Instagram" style="color:#5E17EB;font-size:22px;text-decoration:none"><i class="ph ph-instagram-logo"></i></a>
<a href="https://www.tiktok.com/@yuval.ai" aria-label="TikTok" style="color:#5E17EB;font-size:22px;text-decoration:none"><i class="ph ph-tiktok-logo"></i></a>
<a href="https://youtube.com/@yuv-ai" aria-label="YouTube" style="color:#5E17EB;font-size:22px;text-decoration:none"><i class="ph ph-youtube-logo"></i></a>
<a href="https://github.com/hoodini" aria-label="GitHub" style="color:#5E17EB;font-size:22px;text-decoration:none"><i class="ph ph-github-logo"></i></a>
<a href="https://facebook.com/yuval.avidani" aria-label="Facebook" style="color:#5E17EB;font-size:22px;text-decoration:none"><i class="ph ph-facebook-logo"></i></a>
<a href="https://www.linkedin.com/in/yuval-avidani-87081474/" aria-label="LinkedIn" style="color:#5E17EB;font-size:22px;text-decoration:none"><i class="ph ph-linkedin-logo"></i></a>
<a href="https://linktr.ee/yuvai" aria-label="Linktree" style="color:#5E17EB;font-size:22px;text-decoration:none"><i class="ph ph-tree-structure"></i></a>
</footer>
<!-- Phosphor icons: <script src="https://unpkg.com/@phosphor-icons/web"></script> -->For Warm Editorial mode, swap color:#5E17EB → color:#FF1464 and background:var(--yuv-grey) → background:var(--off-white).
Drop-in JSX — React + Phosphor
const SOCIALS = [
{ url: 'https://yuv.ai', label: 'Website', Icon: Globe },
{ url: 'https://x.com/yuvalav', label: 'X', Icon: XLogo },
{ url: 'https://instagram.com/yuval_770', label: 'Instagram', Icon: InstagramLogo },
{ url: 'https://www.tiktok.com/@yuval.ai', label: 'TikTok', Icon: TiktokLogo },
{ url: 'https://youtube.com/@yuv-ai', label: 'YouTube', Icon: YoutubeLogo },
{ url: 'https://github.com/hoodini', label: 'GitHub', Icon: GithubLogo },
{ url: 'https://facebook.com/yuval.avidani', label: 'Facebook', Icon: FacebookLogo },
{ url: 'https://www.linkedin.com/in/yuval-avidani-87081474/', label: 'LinkedIn', Icon: LinkedinLogo },
{ url: 'https://linktr.ee/yuvai', label: 'Linktree', Icon: TreeStructure },
];
export function SocialsFooter() {
return (
<footer className="flex items-center justify-center gap-5 py-12 bg-grey font-body">
{SOCIALS.map(({ url, label, Icon }) => (
<a key={url} href={url} aria-label={label} className="text-purple text-xl no-underline">
<Icon weight="regular" />
</a>
))}
</footer>
);
}---
"Meet your instructor" / speaker bio block (Fly High)
<section class="bio" style="display:grid;grid-template-columns:280px 1fr;gap:48px;align-items:center;padding:80px 100px;background:var(--yuv-grey)">
<img src="/assets/profile-yuval-studio.png" alt="Yuval Avidani"
style="width:280px;height:280px;object-fit:cover;border:8px solid var(--yuv-purple);border-radius:0">
<div>
<span style="font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:0.3em;color:var(--yuv-purple);font-weight:700">YOUR INSTRUCTOR</span>
<h2 style="font-family:'Anton',sans-serif;font-size:88px;line-height:1;letter-spacing:0;text-transform:uppercase;margin:12px 0 16px">Yuval Avidani</h2>
<p style="font-family:'Inter',sans-serif;font-size:18px;line-height:1.5;color:#000;max-width:60ch">
Founder of YUV.AI — Israel's leading Hebrew AI educator and technical innovator.
Builds, teaches, and ships AI that actually works.
</p>
<div style="margin-top:24px;display:flex;gap:14px">
<a href="https://yuv.ai" style="font-family:'Anton',sans-serif;text-transform:uppercase;padding:12px 24px;background:var(--yuv-purple);color:#fff;text-decoration:none;border-radius:999px;letter-spacing:0.04em">yuv.ai</a>
<a href="https://linktr.ee/yuvai" style="font-family:'Anton',sans-serif;text-transform:uppercase;padding:12px 24px;border:2px solid var(--yuv-black);color:var(--yuv-black);text-decoration:none;border-radius:999px;letter-spacing:0.04em">linktree</a>
</div>
</div>
</section>---
Linktree-as-shortcut pattern
For closing slides, QR-code-friendly surfaces, and "where to follow next" CTAs, default to a single linktree URL instead of listing all 8 platforms. One link, one tap, everything. Pair with a QR rendered from the URL for live keynotes.
<a href="https://linktr.ee/yuvai" style="font-family:'Anton',sans-serif;font-size:48px;text-transform:uppercase;color:var(--yuv-purple);text-decoration:none;letter-spacing:0">
linktr.ee/yuvai
</a>---
Domain / handle inventory (for copy-paste)
- Email:
info@yuv.ai - Brand name: YUV.AI / yuv.ai
- Tagline: Let's Fly High (also the wordmark on
logo-rectangle-wordmark.png) - Primary handle:
@yuvalav(X),@hoodini(GitHub) - Brand handle variants:
@yuval_770(IG),@yuval.ai(TikTok),@yuv-ai(YouTube),@yuval.avidani(FB) - One-link shortcut: https://linktr.ee/yuvai
Social images, posters & OG cards
Patterns for static visual assets shared on social, in DMs, or embedded as preview cards.
---
Canvas sizes
| Surface | Canvas | Aspect |
|---|---|---|
| Instagram feed (single) | 1080 × 1350 | 4:5 portrait |
| Instagram square | 1080 × 1080 | 1:1 |
| Instagram story / Reels cover | 1080 × 1920 | 9:16 |
| X / Twitter image | 1600 × 900 | 16:9 |
| LinkedIn feed | 1200 × 1200 (or 1200 × 627) | 1:1 or ~1.91:1 |
| Facebook / OG card | 1200 × 630 | ~1.91:1 |
| YouTube thumbnail | 1280 × 720 | 16:9 |
| TikTok cover | 1080 × 1920 | 9:16 |
| Podcast art (squared) | 3000 × 3000 (export as 1400 min) | 1:1 |
Design at the maximum useful size and downsample for delivery. Anton holds up at any scale.
---
The four basic layouts
1. Big stat card
For an announcement, milestone, or single-number flex. Brand watermark bottom-right. Linktree URL bottom-left.
- Background: grey (Fly High) or off-white (Warm Editorial).
- Big Anton stat at 240–360px, color
#000. The%or unit in brand color. - JetBrains Mono eyebrow above (tracking 0.3em), in brand color.
- Inter 500 caption below explaining what the number means.
- Brand watermark
logo-rectangle-wordmark.pngbottom-right at ~140px wide.
2. Headline-only social card
For a punchline, quote, or hot take. Watermark bottom-right.
- Background: brand color (purple in Fly High act mode, pink in Warm Editorial).
- 4–6 lines of Anton headline. One word highlighted with the yellow
box-decoration-break: clonespan pattern. - Attribution in Inter 500, 16–22px, color
rgba(255,255,255,0.7)(on purple) orrgba(0,0,0,0.55)(on grey). - Watermark bottom-right.
3. "Find me elsewhere" card
For directing followers to other platforms. Use the canonical link set.
- Background: grey.
logo-square-color.pngtop-center at ~360px.- Anton "LET'S CONNECT" headline at ~96px.
- Row of social icons (Phosphor) at 48px each, in brand color, with handles in JetBrains Mono below each icon.
- Linktree URL at bottom in Anton, ~64px.
- Watermark bottom-right.
4. "About me" / speaker bio card
For LinkedIn / Twitter pinned post / speaker page.
- 50/50 split. Left:
profile-yuval-studio.pngcropped square, with 8px brand-color border, radius 0. - Right: eyebrow ("YOUR HOST" / "FOUNDER · YUV.AI") in JetBrains Mono. Anton "Yuval Avidani" headline. Inter body with credentials (see
social-and-links.md§ Credentials). Pill CTA to website.
---
Mandatory watermark
Every social image gets the brand watermark in the bottom-right corner.
- Source:
assets/logo-rectangle-wordmark.png - Width: 12–15% of the canvas width (e.g. 160–180px on a 1080-wide image).
- Margin: 3% of canvas from right and bottom (≈32px on a 1080 canvas).
- Opacity: 100% on light backgrounds, 90% on dark.
- Never stretch. Preserve aspect.
---
Type sizes on social images (1080-wide canvas)
| Role | Size |
|---|---|
| Hero stat | 240–320px Anton |
| Headline | 88–140px Anton |
| Section eyebrow | 18–22px JetBrains Mono, tracking 0.3em |
| Body / supporting copy | 28–36px Inter 400 |
| Caption / footer | 20–24px Inter 500 |
---
Image generation prompts (Nano Banana 2 / Veo)
When generating imagery for social cards, use the brand palette explicitly in the prompt. Examples:
- "Hero shot of a phoenix taking off, electric purple and yellow palette, on a clean light-grey background, cinematic studio lighting, no text, square 1:1" (for Fly High)
- "Editorial portrait, warm pink and bone palette, paper-grain texture, asymmetric composition, no text, 4:5 portrait" (for Warm Editorial)
Avoid generic "AI art" prompts — always specify the palette explicitly and "no text" (Nano Banana 2 + most diffusion models render text poorly).
---
Self-check before posting a social image
1. Canvas size matches the target platform exactly. 2. Watermark is in the bottom-right at the correct size. 3. All type is in the system fonts (Anton / Inter / JetBrains Mono / Rubik / Assistant). 4. Palette is brand-only (no stray default colors). 5. If text-heavy: the headline answers "what is this?" in under 2 seconds. 6. If number-heavy: there's exactly one hero stat, sized to dominate. 7. No emoji on enterprise / business surfaces. Casual / personal posts can use 1–2 sparingly. 8. Linktree URL or website URL visible if the image is meant to drive traffic.
/* YUV.AI Design System — "Fly High" palette
* https://github.com/hoodini/yuv-design-system
*
* Drop this into your project and reference vars below.
* For Tailwind, use tokens/tailwind.config.js instead.
*/
:root {
/* Brand */
--yuv-purple: #5E17EB;
--yuv-purple-dark: #3D0DA8;
--yuv-purple-soft: rgba(94, 23, 235, 0.10);
--yuv-yellow: #FFEC00;
/* Surfaces */
--yuv-grey: #F1F2F2; /* content slide / page background */
--yuv-white: #FFFFFF; /* cards on grey, headers on purple */
--yuv-black: #000000; /* headers on grey, body copy */
--yuv-grey-dark: #D4D6D6; /* dividers, borders on grey */
/* Text tones */
--yuv-dim: rgba(0, 0, 0, 0.55);
--yuv-dim-on-purple: rgba(255, 255, 255, 0.70);
--yuv-faint: rgba(0, 0, 0, 0.18);
--yuv-border: rgba(0, 0, 0, 0.12);
--yuv-border-on-purple: rgba(255, 255, 255, 0.20);
/* Type */
--yuv-font-display: "Anton", "Rubik", system-ui, sans-serif;
--yuv-font-body: "Inter", "Assistant", system-ui, sans-serif;
--yuv-font-mono: "JetBrains Mono", "SF Mono", ui-monospace, Menlo, monospace;
/* Geometry */
--yuv-radius: 0; /* cards / rectangles */
--yuv-radius-pill: 999px; /* CTAs only */
/* Spacing scale (slide canvas — 1920×1080) */
--yuv-pad-slide: 100px;
--yuv-pad-section: 120px;
}
/* Reset white to off-white where the system expects */
.yuv-root {
background: var(--yuv-grey);
color: var(--yuv-black);
font-family: var(--yuv-font-body);
}
/* Act mode — for hero / section / closer */
.yuv-act {
background: var(--yuv-purple);
color: var(--yuv-white);
}
/* Display headline default — letter-spacing 0, NOT negative.
* Anton is already condensed; negative tracking makes letters touch. */
.yuv-display {
font-family: var(--yuv-font-display);
text-transform: uppercase;
letter-spacing: 0;
font-weight: 400;
line-height: 1.0;
}
/* Hero-size only override (≥ 180px). Never tighter than -0.01em. */
.yuv-display-hero { letter-spacing: -0.01em; }
/* Yellow highlight span — punchline word.
* Em-based padding (scales with font size) + slight POSITIVE tracking
* so letters breathe inside the yellow rectangle. */
.yuv-highlight {
display: inline-block;
background: var(--yuv-yellow);
color: var(--yuv-black);
padding: 0.08em 0.4em;
letter-spacing: 0.01em;
-webkit-box-decoration-break: clone;
box-decoration-break: clone;
}
/* Card on grey */
.yuv-card {
background: var(--yuv-white);
border: 1px solid var(--yuv-border);
border-left: 4px solid var(--yuv-purple);
padding: 16px 24px;
}
/* Card on purple */
.yuv-card-on-purple {
background: rgba(255, 255, 255, 0.06);
border: 1px solid var(--yuv-border-on-purple);
border-left: 4px solid var(--yuv-yellow);
padding: 16px 24px;
color: var(--yuv-white);
}
{
"$schema": "https://design-tokens.github.io/community-group/format/",
"yuv": {
"color": {
"purple": { "value": "#5E17EB", "description": "Primary brand. Act-slide backgrounds. Vertical accent bars on content slides." },
"purpleDark": { "value": "#3D0DA8", "description": "Decorative depth, diagonal accents." },
"purpleSoft": { "value": "rgba(94, 23, 235, 0.10)", "description": "Soft purple wash for callout backgrounds on grey." },
"yellow": { "value": "#FFEC00", "description": "Loud accent only. Never a slide background. Use for punchline highlights." },
"grey": { "value": "#F1F2F2", "description": "Content-slide / page background. Never pure white at root." },
"white": { "value": "#FFFFFF", "description": "Cards on grey. Headers on purple." },
"black": { "value": "#000000", "description": "Headers on grey. Body copy." },
"greyDark": { "value": "#D4D6D6", "description": "Dividers and quiet borders on grey." },
"dim": { "value": "rgba(0,0,0,0.55)" },
"dimOnPurple": { "value": "rgba(255,255,255,0.70)" },
"border": { "value": "rgba(0,0,0,0.12)" },
"borderOnPurple": { "value": "rgba(255,255,255,0.20)" }
},
"font": {
"display": { "value": "Anton, Rubik, system-ui, sans-serif", "description": "Headlines. Always UPPERCASE." },
"body": { "value": "Inter, Assistant, system-ui, sans-serif", "description": "Body, UI." },
"mono": { "value": "JetBrains Mono, SF Mono, ui-monospace, Menlo, monospace", "description": "Readouts, eyebrows, citations." }
},
"radius": {
"default": { "value": "0", "description": "Cards and rectangles." },
"pill": { "value": "999px", "description": "CTAs only." }
},
"slide": {
"canvas": { "value": "1920x1080" },
"paddingX": { "value": "100px" },
"paddingY": { "value": "120px" }
}
}
}
/** YUV.AI "Fly High" design system — Tailwind preset
*
* // tailwind.config.js
* module.exports = {
* presets: [require('./tokens/tailwind.config.js')],
* content: ['./src/**\/*.{ts,tsx,html}'],
* };
*/
module.exports = {
theme: {
extend: {
colors: {
// Brand
purple: '#5E17EB',
'purple-dark':'#3D0DA8',
yellow: '#FFEC00',
// Surfaces
grey: '#F1F2F2',
'grey-dark': '#D4D6D6',
bone: '#FAFAF7',
// Keep black/white explicit so we never default to Tailwind's slate/zinc
black: '#000000',
white: '#FFFFFF',
},
fontFamily: {
display: ['Anton', 'Rubik', 'sans-serif'],
body: ['Inter', 'Assistant', 'sans-serif'],
mono: ['"JetBrains Mono"', '"SF Mono"', 'ui-monospace', 'Menlo', 'monospace'],
},
letterSpacing: {
tightest: '-0.04em',
tighter: '-0.03em',
wide: '0.04em',
wider: '0.20em',
widest: '0.32em',
},
borderRadius: {
// Yuval system uses 0 or 999px — nothing in between
none: '0',
pill: '9999px',
},
boxShadow: {
card: '0 8px 24px rgba(0,0,0,0.05)',
'card-hover': '0 16px 40px rgba(0,0,0,0.08)',
// No coloured shadows. No blue-black.
},
maxWidth: {
content: '1440px',
},
},
},
plugins: [],
};
Typography — full system
Mandatory type rules for English, Hebrew, and bilingual surfaces.
---
English stack
| Role | Family | Where |
|---|---|---|
| Display / headlines | Anton | h1, h2, h3, section titles, pull quotes, big stats, CTA labels, wordmarks |
| Body / UI | Inter | paragraphs, lists, form fields, buttons (non-display) |
| Mono / readouts | JetBrains Mono | eyebrows, citations, instrument readouts, chip labels, code |
Anton rules
- Always UPPERCASE (use
text-transform: uppercasein CSS). Anton renders title case poorly because it's a condensed display face. - Letter-spacing default `0` (normal). Anton is already condensed — negative tracking pushes letters into each other, especially inside yellow
box-decoration-break: clonespans, where it reads as a wall of crammed glyphs. Never tighter than `0` by default. Only at extreme hero sizes (≥ 180px) and only when measured, drop to-0.01emmax. The earlier negative-tracking rule was wrong; this is the correction. - Inside yellow highlight spans: add slight POSITIVE tracking —
letter-spacing: 0.01emto0.015em— and use em-based padding (padding: 0.08em 0.4em) so the box has breathing room around each letter and scales with the font size. - Line-height `1.0`–`1.05`. Never below
1.0when a highlight span sits inside (descenders get eaten). - Single weight — Anton ships only one weight. Don't try to bold it via
font-weight. - Size range — never below 16px in body. Anton at small sizes is illegible. For section eyebrows, use JetBrains Mono instead.
- One Anton element per slide / section. Two or more big Anton elements stacked (e.g. a headline plus a row of stat tiles all in Anton) make the surface read like a wall of thick type. Use Anton for the hero only; supporting numbers and labels go in Inter 900 or JetBrains Mono 700.
Inter rules
- Weights 400 / 500 / 600 / 700 / 900. Use 400 for body, 500–600 for UI labels, 700 for emphasis, 900 only as a fallback display.
- Line-height
1.5for body,1.3for tight UI lists,1.2for small headlines. - Letter-spacing default (
normal) for body. Don't apply negative tracking to Inter — that's Anton's territory.
JetBrains Mono rules
- Use for: section eyebrows ("PRE-FLIGHT", "EVIDENCE", "CHAPTER 3"), data readouts, citations under HUD, chips, inline code.
- Always tracking
0.2em–0.3emand weight 700 when used as a label. - Body text mono is banned — leave it for atmosphere/data only.
---
Hebrew stack
| Role | Family |
|---|---|
| Display / headlines | Rubik (weights up to 900) |
| Body / UI | Assistant (400 / 500 / 600 / 700) |
Hebrew page setup
<!DOCTYPE html>
<html lang="he" dir="rtl">
<head>
<meta charset="UTF-8">
<!-- fonts -->
</head>
<body>...</body>
</html>Logical CSS — mandatory on Hebrew pages
Use logical properties instead of physical ones:
| Physical (avoid) | Logical (use) |
|---|---|
margin-left | margin-inline-start |
margin-right | margin-inline-end |
padding-left | padding-inline-start |
padding-right | padding-inline-end |
border-left | border-inline-start |
border-right | border-inline-end |
text-align: left | text-align: start |
text-align: right | text-align: end |
With logical properties, the same CSS works in LTR and RTL — the "start" side flips automatically.
Rubik rules
- Weight 900 for major display. 700 for section heads. 500–600 for UI.
- Letter-spacing default — Hebrew doesn't tolerate the negative tracking Anton uses.
- Line-height
1.05–1.15for headlines. Hebrew glyphs have less vertical reach, so they can run tighter than Anton.
Assistant rules
- Same weights / use as Inter. Line-height
1.5for body.
---
Bilingual stack
Stack both fonts in the variable. The browser picks per glyph automatically.
:root {
--yuv-font-display: 'Anton', 'Rubik', system-ui, sans-serif;
--yuv-font-body: 'Inter', 'Assistant', system-ui, sans-serif;
--yuv-font-mono: '"JetBrains Mono"', '"SF Mono"', ui-monospace, Menlo, monospace;
}
h1, h2, h3, .display {
font-family: var(--yuv-font-display);
text-transform: uppercase; /* Latin only — Hebrew unaffected */
letter-spacing: 0; /* Anton is condensed — never negative by default */
line-height: 1.0;
}Anton ignores font-weight. Rubik respects it. So if your headline mixes Latin and Hebrew, set font-weight: 900 — Latin Anton uses its single weight, Hebrew Rubik picks the 900 face. Both look display-correct.
Bilingual content rule (battle-tested)
Never stack EN + HE side-by-side or in adjacent positions. Always use the toggle pattern in lessons-learned.md § Bilingual toggle.
---
Google Fonts — one link to rule them all
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Anton&family=Inter:wght@400;500;600;700;900&family=JetBrains+Mono:wght@400;500;700&family=Rubik:wght@400;500;600;700;800;900&family=Assistant:wght@400;500;600;700&display=swap" rel="stylesheet">For English-only projects, drop Rubik + Assistant. For Hebrew-only, drop Anton + Inter. When in doubt, load all five — total uncached weight ~50KB, trivial.
---
Type-scale reference
Slide deck canvas (1920×1080)
| Role | Size | Family |
|---|---|---|
| Act-slide hero | 280–360px | Anton |
| Content-slide H1 | 96–140px | Anton |
| Content-slide H2 | 56–72px | Anton |
| Eyebrow above H1 | 13–14px tracking 0.3em | JetBrains Mono |
| Body | 22–28px | Inter |
| Caption / cite | 13–15px | Inter or JetBrains Mono |
| Big stat | 200–280px | Anton |
| HUD readout | 16px | JetBrains Mono |
Web (1440px max-width)
| Role | Size | Family |
|---|---|---|
| Hero H1 | clamp(4rem, 9vw, 10rem) | Anton |
| Section H2 | clamp(2.5rem, 5vw, 5rem) | Anton |
| Card title | clamp(1.5rem, 2.5vw, 2.5rem) | Anton |
| Body | 18px | Inter |
| Small / caption | 14px | Inter |
| Eyebrow | 11–13px tracking 0.2–0.3em | JetBrains Mono |
---
Banned
- Serif fonts as defaults (Garamond, Times, Playfair, Lora).
- Script / handwritten fonts.
system-ui/ OS default stack.- Comic Sans (obvious but worth saying).
- Inter as a display font (don't bump it up to 900 to substitute Anton — load Anton).
- Anton in body (illegible at small sizes — use Inter).
- Two display headlines stacked (see
lessons-learned.md§ 3).
Web & React — frontend deep dive
Patterns specific to browser-rendered output: sites, landing pages, React components, dashboards, web artifacts.
---
Starter HTML — Fly High landing hero (English)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Let's Fly High — YUV.AI</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Anton&family=Inter:wght@400;500;600;700;900&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
<style>
:root {
--yuv-purple: #5E17EB; --yuv-purple-dark: #3D0DA8;
--yuv-yellow: #FFEC00; --yuv-grey: #F1F2F2;
--yuv-white: #FFFFFF; --yuv-black: #000000;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { background: var(--yuv-grey); color: var(--yuv-black); font-family: 'Inter', sans-serif; line-height: 1.5; }
.hero {
min-height: 100vh; display: grid; grid-template-columns: 1.2fr 1fr;
align-items: center; gap: 64px; padding: 120px 80px;
max-width: 1440px; margin-inline: auto;
}
.eyebrow {
font-family: 'JetBrains Mono', monospace; font-size: 13px;
letter-spacing: 0.3em; color: var(--yuv-purple); font-weight: 700;
margin-bottom: 24px;
}
.hero h1 {
font-family: 'Anton', sans-serif; font-size: clamp(4rem, 9vw, 10rem);
text-transform: uppercase; letter-spacing: -0.01em; line-height: 1.0;
}
.hero h1 .highlight {
display: inline-block; background: var(--yuv-yellow); color: var(--yuv-black);
padding: 0 18px; -webkit-box-decoration-break: clone; box-decoration-break: clone;
}
.cta {
display: inline-block; margin-top: 32px;
padding: 18px 36px; background: var(--yuv-purple); color: var(--yuv-white);
font-family: 'Anton', sans-serif; text-transform: uppercase; letter-spacing: 0.04em;
border-radius: 999px; text-decoration: none; font-size: 18px;
}
@media (max-width: 880px) {
.hero { grid-template-columns: 1fr; padding: 80px 24px; }
}
</style>
</head>
<body>
<!-- HYPERFRAMES_VIDEO_SLOT: hero -->
<section class="hero">
<div>
<span class="eyebrow">YUV.AI · LET'S FLY HIGH</span>
<h1>Build AI<br>that <span class="highlight">actually</span><br>works.</h1>
<a href="#start" class="cta">Start the climb</a>
</div>
<div><!-- right column for visual / 3D / live demo --></div>
</section>
<!-- /HYPERFRAMES_VIDEO_SLOT -->
</body>
</html>---
React + Tailwind preset
// tailwind.config.js
const yuv = require('./references/tokens/tailwind.config.js');
module.exports = {
presets: [yuv],
content: ['./src/**/*.{ts,tsx,html}'],
};import { PurpleBar, YellowUnderline, CounterUp } from './yuv';
export default function HeroSection() {
return (
<section className="grid grid-cols-1 md:grid-cols-[1.2fr_1fr] gap-16 min-h-screen items-center px-[5%] max-w-content mx-auto bg-grey">
<div>
<span className="font-mono text-[13px] tracking-[0.3em] text-purple font-bold mb-6 block">
YUV.AI · LET'S FLY HIGH
</span>
<h1 className="font-display uppercase tracking-tightest leading-none text-[clamp(4rem,9vw,10rem)]">
Build AI<br />
that <span className="inline-block bg-yellow text-black px-[18px]" style={{ boxDecorationBreak: 'clone' }}>actually</span><br />
works.
</h1>
<a href="#start" className="inline-block mt-8 px-9 py-4 bg-purple text-white font-display uppercase tracking-wide rounded-pill no-underline text-lg">
Start the climb
</a>
</div>
<div>{/* right column */}</div>
</section>
);
}---
Page architecture defaults
| Region | Defaults |
|---|---|
| Topbar / nav | Sticky on desktop, hamburger on mobile (≤ 880px) when > 6 items. Background var(--yuv-grey) with 1px solid rgba(0,0,0,0.08) bottom border. |
| Hero | First child of <main>, full-viewport, ALWAYS the signature video banner (<iframe src="/demo/index.html">) above the static hero. |
| Sections | padding-block: 120px desktop, 64px mobile. Alternate purple/grey only when the section is meaningfully different — never decoratively. |
| Cards | border-radius: 0. Borders 1px solid rgba(0,0,0,0.12). Optional 4px solid var(--yuv-purple) left border on info cards. |
| CTAs | Primary: bg-purple (or bg-pink in Warm Editorial), white text, Anton uppercase, border-radius: 999px. Secondary: bordered 2px solid var(--yuv-black), transparent fill. |
| Footer | The canonical social links + brand watermark. See social-and-links.md. |
---
Performance defaults
- IntersectionObserver pauses any GSAP timeline whose stage is offscreen (essential for catalogs with > 8 demos).
- Preconnect Google Fonts in
<head>. Don't@importfrom CSS — blocks render. - `loading="eager"` for the hero video banner iframe,
loading="lazy"for any other below-the-fold media. - No `localStorage` access during render (only on user action or
useEffect). - `will-change` sparingly — only on elements that actually animate. Overuse cooks the GPU.
---
Accessibility defaults
- Every
<a>and<button>has a visible focus ring —:focus-visible { outline: 3px solid var(--yuv-yellow); outline-offset: 4px; }. - Body text contrast: AA minimum. Anton at large sizes always passes; small Inter on
--yuv-greyneeds#000not#1A1A1Ato hit AAA. - All icons get an
aria-label. All decorative SVGs getaria-hidden. - Language toggle has
role="group"+aria-label. - Buttons that toggle state expose
aria-expanded.
---
Common patterns
Counter-up on viewport entry
import { useEffect, useRef, useState } from 'react';
export function CounterUp({ to, duration = 1400 }: { to: number; duration?: number }) {
const ref = useRef<HTMLSpanElement>(null);
const [n, setN] = useState(0);
useEffect(() => {
const io = new IntersectionObserver(([e]) => {
if (!e.isIntersecting) return;
const t0 = performance.now();
const tick = (now: number) => {
const t = Math.min(1, (now - t0) / duration);
const eased = 1 - Math.pow(1 - t, 3); // ease-out cubic
setN(Math.round(to * eased));
if (t < 1) requestAnimationFrame(tick);
};
requestAnimationFrame(tick);
io.disconnect();
}, { threshold: 0.3 });
if (ref.current) io.observe(ref.current);
return () => io.disconnect();
}, [to, duration]);
return <span ref={ref}>{n}</span>;
}Sticky purple side accent
.section::before {
content: '';
position: absolute;
inset: 0 auto 0 0; /* full height, left side */
inset-inline-start: 0; /* RTL-safe */
width: 8px;
background: var(--yuv-purple);
}---
Mobile baseline check (mandatory)
Before shipping any frontend output:
1. Open browser DevTools → device toolbar → iPhone 13 (390×844). 2. Screenshot. 3. Verify nav fits, no horizontal scroll, headlines readable, grid collapses to 1 column, footer fits. 4. Tap the hamburger — does the menu actually open? Does the language toggle work on mobile?
If any check fails, fix and re-verify. Mobile is not polish; it's the baseline.