Menu

Change language

GitHub repo

Press Kit Brand Assets

blockmarketing

Comprehensive brand guidelines and press kit hub with official logo downloads in vector SVG/4K PNG/WebP, OKLCH brand palette tokens, executive leadership headshots (300 DPI), and press contact resources.

Installation

$npx uipkge-ng@latest add press-kit-brand-assets
Manifest:https://uipkge.dev/r/angular/press-kit-brand-assets.jsonInstalls to:src/app/components/blocks/press-kit-brand-assets/

Variants

Loading interactive previews…

Schema

Type aliases exported from this item's source. Use these to shape the data you pass in.

LogoAsset
interface LogoAsset {
  id: string
  title: string
  category: string
  description: string
  dimensions: string
  bestFor: string
  theme: 'light' | 'dark' | 'adaptive'
  formats: { label: string; ext: string; size: string }[]
}
BrandColor
interface BrandColor {
  id: string
  name: string
  hex: string
  oklch: string
  rgb: string
  role: string
  contrast: string
  usage: string
  bgClass: string
  textDark?: boolean
}
Executive
interface Executive {
  id: string
  name: string
  role: string
  bio: string
  avatarUrl?: string
  initials: string
  fileSize: string
  dimensions: string
  topics: string[]
  socials: { platform: 'x' | 'linkedin' | 'github'; url: string }[]
}
BrandGuidelineRule
interface BrandGuidelineRule {
  type: 'do' | 'dont'
  text: string
}

Theming

CSS custom properties referenced in this item. Override any of them in your :root or per-element to retheme.

--border

Files installed (2)

  • src/app/components/blocks/press-kit-brand-assets/press-kit-brand-assets.component.ts44.3 kB
    // Brand guidelines and press kit hub: official logo downloads (SVG/4K PNG/WebP),
    // OKLCH brand palette tokens, executive leadership headshots (300 DPI), and press
    // contact resources. Port of the React block 1:1.
    import { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core'
    import {
      Check,
      CheckCircle2,
      Copy,
      Download,
      ExternalLink,
      FileArchive,
      FileCode,
      FileImage,
      FileText,
      Globe,
      Info,
      Layers,
      LucideAngularModule,
      Mail,
      Palette,
      ShieldCheck,
      User,
      XCircle,
    } from 'lucide-angular'
    import { cn } from '@/lib/utils'
    import { UiAvatarComponent, UiAvatarFallbackComponent, UiAvatarImageComponent } from '@/ui/avatar/avatar.component'
    import { UiBadgeComponent } from '@/ui/badge/badge.component'
    import { UiButtonComponent } from '@/ui/button/button.component'
    import {
      UiCardComponent,
      UiCardContentComponent,
      UiCardDescriptionComponent,
      UiCardFooterComponent,
      UiCardHeaderComponent,
      UiCardTitleComponent,
    } from '@/ui/card/card.component'
    import { UiSeparatorComponent } from '@/ui/separator/separator.component'
    
    export interface LogoAsset {
      id: string
      title: string
      category: string
      description: string
      dimensions: string
      bestFor: string
      theme: 'light' | 'dark' | 'adaptive'
      formats: { label: string; ext: string; size: string }[]
    }
    
    export interface BrandColor {
      id: string
      name: string
      hex: string
      oklch: string
      rgb: string
      role: string
      contrast: string
      usage: string
      bgClass: string
      textDark?: boolean
    }
    
    export interface Executive {
      id: string
      name: string
      role: string
      bio: string
      avatarUrl?: string
      initials: string
      fileSize: string
      dimensions: string
      topics: string[]
      socials: { platform: 'x' | 'linkedin' | 'github'; url: string }[]
    }
    
    export interface BrandGuidelineRule {
      type: 'do' | 'dont'
      text: string
    }
    
    const DEFAULT_LOGOS: LogoAsset[] = [
      {
        id: 'horizontal-primary',
        title: 'Primary Horizontal Logo',
        category: 'Full Wordmark',
        description: 'The standard lockup combining the faceted block logomark with horizontal typography and .dev badge.',
        dimensions: '3840 × 1080 px',
        bestFor: 'Light surfaces, navigation bars, marketing hero headers, and pitch decks.',
        theme: 'light',
        formats: [
          { label: 'Vector SVG', ext: 'svg', size: '14 KB' },
          { label: '4K PNG (Alpha)', ext: 'png', size: '280 KB' },
          { label: 'Modern WebP', ext: 'webp', size: '94 KB' },
        ],
      },
      {
        id: 'app-icon-mark',
        title: 'Square App Icon Mark',
        category: 'Logomark Symbol',
        description: 'Standalone geometric prism icon optimized for square aspect ratios and high-density icons.',
        dimensions: '1024 × 1024 px',
        bestFor: 'Favicons, desktop dock icons, app store assets, and square social avatars.',
        theme: 'adaptive',
        formats: [
          { label: 'Vector SVG', ext: 'svg', size: '8 KB' },
          { label: 'High-Res PNG', ext: 'png', size: '165 KB' },
          { label: 'Icon WebP', ext: 'webp', size: '62 KB' },
        ],
      },
      {
        id: 'monochrome-dark',
        title: 'Dark Mode Monochromatic Logo',
        category: 'Dark Backgrounds',
        description: 'Single-tone pure-white silhouette engineered for deep slate surfaces and obsidian backgrounds.',
        dimensions: '3840 × 1080 px',
        bestFor: 'Developer terminals, dark theme dashboards, midnight wallpapers, and video overlays.',
        theme: 'dark',
        formats: [
          { label: 'Vector SVG', ext: 'svg', size: '12 KB' },
          { label: '4K PNG (White)', ext: 'png', size: '240 KB' },
          { label: 'Dark WebP', ext: 'webp', size: '88 KB' },
        ],
      },
      {
        id: 'monochrome-light',
        title: 'Light Mode Inverted Logo',
        category: 'Print & Documents',
        description: 'High-density obsidian monochrome lockup for single-ink printing and high-contrast editorial media.',
        dimensions: '3840 × 1080 px',
        bestFor: 'Whitepapers, physical print collateral, invoices, PDF documents, and newsprint.',
        theme: 'light',
        formats: [
          { label: 'Vector SVG', ext: 'svg', size: '12 KB' },
          { label: '4K PNG (Black)', ext: 'png', size: '235 KB' },
          { label: 'Print WebP', ext: 'webp', size: '85 KB' },
        ],
      },
    ]
    
    const DEFAULT_COLORS: BrandColor[] = [
      {
        id: 'primary-emerald',
        name: 'Primary Emerald',
        hex: '#10B981',
        oklch: 'oklch(0.696 0.170 162.48)',
        rgb: 'rgb(16, 185, 129)',
        role: 'Primary Interactive Brand Color',
        contrast: 'WCAG AAA 7.4:1',
        usage: 'Primary brand color for CTAs, active pills, key highlights, and success indicators.',
        bgClass: 'bg-[#10B981]',
        textDark: true,
      },
      {
        id: 'midnight-slate',
        name: 'Midnight Slate',
        hex: '#0F172A',
        oklch: 'oklch(0.208 0.042 265.75)',
        rgb: 'rgb(15, 23, 42)',
        role: 'Dark Neutral Surface Base',
        contrast: 'WCAG AAA 16.2:1',
        usage: 'Core neutral background and high-contrast dark surface foundations across dark layouts.',
        bgClass: 'bg-[#0F172A]',
        textDark: false,
      },
      {
        id: 'accent-blue',
        name: 'Accent Blue',
        hex: '#3B82F6',
        oklch: 'oklch(0.623 0.214 259.80)',
        rgb: 'rgb(59, 130, 246)',
        role: 'Secondary Interactive Accent',
        contrast: 'WCAG AA 4.8:1',
        usage: 'Secondary accent for active links, information badges, focus rings, and secondary metrics.',
        bgClass: 'bg-[#3B82F6]',
        textDark: true,
      },
      {
        id: 'background-base',
        name: 'Background Base',
        hex: '#F8FAFC',
        oklch: 'oklch(0.985 0.002 247.80)',
        rgb: 'rgb(248, 250, 252)',
        role: 'Light Neutral Surface Fills',
        contrast: 'WCAG AAA 19.5:1',
        usage: 'Clean ambient light surface foundation, card backgrounds, and elevated container fills.',
        bgClass: 'bg-[#F8FAFC]',
        textDark: true,
      },
      {
        id: 'amber-highlight',
        name: 'Amber Highlight',
        hex: '#F59E0B',
        oklch: 'oklch(0.769 0.188 70.08)',
        rgb: 'rgb(245, 158, 11)',
        role: 'Warning & Spotlight Accent',
        contrast: 'WCAG AAA 8.1:1',
        usage: 'Warm highlight for warnings, pro tier tags, system notices, and featured sponsor badges.',
        bgClass: 'bg-[#F59E0B]',
        textDark: true,
      },
    ]
    
    const DEFAULT_EXECUTIVES: Executive[] = [
      {
        id: 'elena-rostova',
        name: 'Elena Rostova',
        role: 'Chief Executive Officer & Co-Founder',
        bio: 'Pioneered component registry architecture and headless design systems. Former principal architect at NextGen UI. Leading UIPKGE’s mission to make design-engineering frictionless for global software teams.',
        avatarUrl: 'https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?q=80&w=400&auto=format&fit=crop',
        initials: 'ER',
        fileSize: '12.4 MB',
        dimensions: '4000 × 5000 px · 300 DPI',
        topics: ['Component Architecture', 'Design Systems', 'Open-Source Sustainability', 'Developer Experience'],
        socials: [
          { platform: 'x', url: 'https://x.com/elenarostova' },
          { platform: 'linkedin', url: 'https://linkedin.com/in/elenarostova' },
          { platform: 'github', url: 'https://github.com/elenarostova' },
        ],
      },
      {
        id: 'marcus-vance',
        name: 'Marcus Vance',
        role: 'Chief Technology Officer & Co-Founder',
        bio: 'Specializes in multi-framework compiler tooling, zero-runtime tokens, and high-performance WebAssembly render pipelines. Previously led frontend infrastructure and UI performance engineering at CloudScale.',
        avatarUrl: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?q=80&w=400&auto=format&fit=crop',
        initials: 'MV',
        fileSize: '11.8 MB',
        dimensions: '4000 × 5000 px · 300 DPI',
        topics: ['Web Performance', 'AST Tooling', 'Tailwind v4 Engine', 'Reka UI Headless Core'],
        socials: [
          { platform: 'x', url: 'https://x.com/marcusvance' },
          { platform: 'linkedin', url: 'https://linkedin.com/in/marcusvance' },
          { platform: 'github', url: 'https://github.com/marcusvance' },
        ],
      },
    ]
    
    const DEFAULT_GUIDELINES: BrandGuidelineRule[] = [
      { type: 'do', text: 'Maintain minimum clear space equal to 50% of the logomark height around all sides.' },
      { type: 'do', text: 'Use the official OKLCH and Hex values when reproducing brand swatches in digital media.' },
      { type: 'do', text: 'Use high-contrast monochromatic variants for single-color print, laser etching, or stamps.' },
      { type: 'dont', text: 'Do not stretch, distort, rotate, or alter the aspect ratio of official logo assets.' },
      { type: 'dont', text: 'Do not add drop shadows, outer glows, strokes, or unapproved gradients to the mark.' },
      { type: 'dont', text: 'Do not place the full-color primary mark on low-contrast or visually busy backgrounds.' },
    ]
    
    type PressKitTab = 'all' | 'logos' | 'colors' | 'leadership' | 'press'
    
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-press-kit-brand-assets, [ui-press-kit-brand-assets]',
      standalone: true,
      // React renders the root <div> itself: the host stays out of layout and `class` goes to the root.
      host: { '[attr.class]': '"contents"' },
      imports: [
        LucideAngularModule,
        UiAvatarComponent,
        UiAvatarFallbackComponent,
        UiAvatarImageComponent,
        UiBadgeComponent,
        UiButtonComponent,
        UiCardComponent,
        UiCardContentComponent,
        UiCardDescriptionComponent,
        UiCardFooterComponent,
        UiCardHeaderComponent,
        UiCardTitleComponent,
        UiSeparatorComponent,
      ],
      template: `
        <div data-slot="press-kit-brand-assets" [class]="rootClass">
          <div class="mx-auto max-w-7xl px-4 py-12 sm:px-6 lg:px-8 lg:py-16">
            <!-- Header Hero Section -->
            <div class="border-border bg-card relative overflow-hidden rounded-2xl border p-6 shadow-xs sm:p-10">
              <div class="relative z-10 flex flex-col gap-6 lg:flex-row lg:items-center lg:justify-between">
                <div class="max-w-3xl space-y-4">
                  <div class="flex flex-wrap items-center gap-2">
                    <span
                      ui-badge
                      variant="outline"
                      class="border-primary/30 bg-primary/10 text-primary gap-1.5 font-medium"
                    >
                      <lucide-icon [img]="Palette" class="size-3.5 shrink-0" />
                      <span>Media & Brand Hub</span>
                    </span>
                    <span ui-badge variant="secondary" class="font-mono text-xs">
                      {{ version }}
                    </span>
                    <span class="text-muted-foreground text-xs">Updated {{ lastUpdated }}</span>
                  </div>
    
                  <h1 class="text-3xl font-bold tracking-tight sm:text-4xl lg:text-5xl">
                    {{ title }}
                  </h1>
    
                  <p class="text-muted-foreground max-w-2xl text-base leading-relaxed sm:text-lg">
                    {{ subtitle }}
                  </p>
    
                  <div class="text-muted-foreground flex flex-wrap items-center gap-4 pt-1 text-xs">
                    <div class="flex items-center gap-1.5">
                      <lucide-icon [img]="ShieldCheck" class="text-primary size-4 shrink-0" />
                      <span>Editorial Use License (CC BY-NC 4.0)</span>
                    </div>
                    <ui-separator orientation="vertical" class="hidden h-3 sm:block" />
                    <div class="flex items-center gap-1.5">
                      <lucide-icon [img]="FileArchive" class="text-muted-foreground size-4 shrink-0" />
                      <span>24 Verified Brand Assets</span>
                    </div>
                    <ui-separator orientation="vertical" class="hidden h-3 sm:block" />
                    <div class="flex items-center gap-1.5">
                      <lucide-icon [img]="Globe" class="text-muted-foreground size-4 shrink-0" />
                      <span>Vector & 300 DPI Print Ready</span>
                    </div>
                  </div>
                </div>
    
                <div class="flex flex-col gap-3 sm:min-w-[280px] lg:flex-col">
                  <button
                    ui-button
                    aria-label="Download complete press kit"
                    size="lg"
                    class="focus-visible:ring-ring h-auto min-h-10 w-full gap-2 py-2.5 text-center font-semibold whitespace-normal shadow-sm focus-visible:ring-2"
                    (click)="handleDownload('all-zip')"
                  >
                    @if (downloadedKey() === 'all-zip') {
                      <lucide-icon [img]="Check" class="size-4 shrink-0" />
                    } @else {
                      <lucide-icon [img]="Download" class="size-4 shrink-0" />
                    }
                    <span>
                      {{
                        downloadedKey() === 'all-zip'
                          ? 'Press Kit Downloaded'
                          : 'Download Complete Press Kit (.ZIP · ' + pressKitZipSize + ')'
                      }}
                    </span>
                  </button>
    
                  <div class="grid grid-cols-2 gap-2">
                    <button
                      ui-button
                      variant="outline"
                      size="sm"
                      class="h-auto min-h-8 w-full gap-1.5 text-center text-xs whitespace-normal"
                      (click)="copyToClipboard('email-header', pressEmail)"
                    >
                      @if (copiedKey() === 'email-header') {
                        <lucide-icon [img]="Check" class="text-primary size-3.5 shrink-0" />
                      } @else {
                        <lucide-icon [img]="Mail" class="size-3.5 shrink-0" />
                      }
                      <span>{{ copiedKey() === 'email-header' ? 'Copied' : 'Press Desk' }}</span>
                    </button>
    
                    <button
                      ui-button
                      variant="outline"
                      size="sm"
                      class="h-auto min-h-8 w-full gap-1.5 text-center text-xs whitespace-normal"
                      (click)="copyToClipboard('boilerplate-header', boilerplateText)"
                    >
                      @if (copiedKey() === 'boilerplate-header') {
                        <lucide-icon [img]="Check" class="text-primary size-3.5 shrink-0" />
                      } @else {
                        <lucide-icon [img]="Copy" class="size-3.5 shrink-0" />
                      }
                      <span>{{ copiedKey() === 'boilerplate-header' ? 'Copied' : 'Boilerplate' }}</span>
                    </button>
                  </div>
                </div>
              </div>
            </div>
    
            <!-- Quick Section Anchor Tabs (Default Variant) -->
            @if (variant === 'default') {
              <div class="mt-8 flex items-center gap-2 overflow-x-auto pb-2">
                <button
                  ui-button
                  [variant]="activeTab() === 'all' ? 'default' : 'ghost'"
                  size="sm"
                  class="h-auto min-h-8 gap-1.5 text-center text-xs whitespace-normal"
                  (click)="activeTab.set('all')"
                >
                  <lucide-icon [img]="Layers" class="size-3.5 shrink-0" />
                  <span>All Assets</span>
                </button>
                <button
                  ui-button
                  [variant]="activeTab() === 'logos' ? 'default' : 'ghost'"
                  size="sm"
                  class="h-auto min-h-8 gap-1.5 text-center text-xs whitespace-normal"
                  (click)="activeTab.set('logos')"
                >
                  <lucide-icon [img]="FileImage" class="size-3.5 shrink-0" />
                  <span>Logos & Marks ({{ activeLogos().length }})</span>
                </button>
                <button
                  ui-button
                  [variant]="activeTab() === 'colors' ? 'default' : 'ghost'"
                  size="sm"
                  class="h-auto min-h-8 gap-1.5 text-center text-xs whitespace-normal"
                  (click)="activeTab.set('colors')"
                >
                  <lucide-icon [img]="Palette" class="size-3.5 shrink-0" />
                  <span>Color Palette ({{ activeColors().length }})</span>
                </button>
                <button
                  ui-button
                  [variant]="activeTab() === 'leadership' ? 'default' : 'ghost'"
                  size="sm"
                  class="h-auto min-h-8 gap-1.5 text-center text-xs whitespace-normal"
                  (click)="activeTab.set('leadership')"
                >
                  <lucide-icon [img]="User" class="size-3.5 shrink-0" />
                  <span>Leadership ({{ activeExecutives().length }})</span>
                </button>
                <button
                  ui-button
                  [variant]="activeTab() === 'press' ? 'default' : 'ghost'"
                  size="sm"
                  class="h-auto min-h-8 gap-1.5 text-center text-xs whitespace-normal"
                  (click)="activeTab.set('press')"
                >
                  <lucide-icon [img]="Mail" class="size-3.5 shrink-0" />
                  <span>Press Contact</span>
                </button>
              </div>
            }
    
            <!-- SECTION 1: Official Logo Downloads -->
            @if (activeTab() === 'all' || activeTab() === 'logos') {
              <div class="mt-12 space-y-6">
                <div class="flex flex-col justify-between gap-4 sm:flex-row sm:items-end">
                  <div>
                    <div class="text-primary flex items-center gap-2 text-xs font-semibold tracking-wider uppercase">
                      <lucide-icon [img]="FileImage" class="size-4 shrink-0" />
                      <span>Official Vectors & Assets</span>
                    </div>
                    <h2 class="mt-1 text-2xl font-bold tracking-tight sm:text-3xl">Official Logo Downloads</h2>
                    <p class="text-muted-foreground mt-1 max-w-2xl text-sm">
                      High-resolution vector assets for print and digital publishing. Please preserve minimum clear space.
                    </p>
                  </div>
                  <span ui-badge variant="outline" class="gap-1 self-start font-mono text-xs sm:self-auto">
                    <span>4 Approved Variants</span>
                  </span>
                </div>
    
                <div class="grid gap-6 md:grid-cols-2">
                  @for (logo of activeLogos(); track logo.id) {
                    <div
                      ui-card
                      class="border-border hover:border-border/80 flex flex-col justify-between overflow-hidden border shadow-xs transition-colors"
                    >
                      <div>
                        <div [class]="logoPreviewClass(logo)">
                          {{ logoPreviewLabel(logo) }}
                          <div class="absolute right-2.5 bottom-2.5">
                            <span
                              class="bg-background/80 text-foreground border-border/50 rounded border px-2 py-0.5 font-mono text-xs backdrop-blur-sm"
                            >
                              {{ logo.dimensions }}
                            </span>
                          </div>
                        </div>
    
                        <div ui-card-header class="p-5 pb-3">
                          <div class="flex flex-wrap items-center justify-between gap-2">
                            <h3 ui-card-title class="text-base font-semibold">{{ logo.title }}</h3>
                            <span ui-badge variant="secondary" class="text-xs">{{ logo.category }}</span>
                          </div>
                          <p ui-card-description class="mt-1 text-xs leading-relaxed">
                            {{ logo.description }}
                          </p>
                          <div class="text-muted-foreground flex items-center gap-1.5 pt-1 text-xs">
                            <lucide-icon [img]="Info" class="text-primary size-3.5 shrink-0" />
                            <span class="line-clamp-1"
                              ><strong class="text-foreground font-medium">Best for:</strong> {{ logo.bestFor }}</span
                            >
                          </div>
                        </div>
                      </div>
    
                      <div ui-card-footer class="p-5 pt-0">
                        <div class="w-full space-y-2">
                          <div class="text-muted-foreground flex items-center justify-between text-xs font-medium">
                            <span>Available Formats</span>
                            <span class="font-mono text-xs">Vector & Bitmaps</span>
                          </div>
                          <div class="grid grid-cols-3 gap-2">
                            @for (fmt of logo.formats; track fmt.ext) {
                              <button
                                ui-button
                                aria-label="Download logo file"
                                variant="outline"
                                size="sm"
                                class="focus-visible:ring-ring h-auto min-h-8 w-full flex-col gap-0.5 px-1.5 py-2 text-center text-xs whitespace-normal focus-visible:ring-2"
                                (click)="handleDownload(logo.id + '-' + fmt.ext)"
                              >
                                <div class="flex items-center gap-1 font-medium">
                                  @if (downloadedKey() === logo.id + '-' + fmt.ext) {
                                    <lucide-icon [img]="Check" class="text-primary size-3 shrink-0" />
                                  } @else {
                                    <lucide-icon [img]="Download" class="text-muted-foreground size-3 shrink-0" />
                                  }
                                  <span class="truncate">{{ fmt.label.split(' ')[0] }}</span>
                                </div>
                                <span class="text-muted-foreground font-mono text-xs">{{ fmt.size }}</span>
                              </button>
                            }
                          </div>
                        </div>
                      </div>
                    </div>
                  }
                </div>
              </div>
            }
    
            <!-- SECTION 2: Official Brand Palette & OKLCH Color Swatches -->
            @if (activeTab() === 'all' || activeTab() === 'colors') {
              <div class="mt-16 space-y-6">
                <div class="flex flex-col justify-between gap-4 sm:flex-row sm:items-end">
                  <div>
                    <div class="text-primary flex items-center gap-2 text-xs font-semibold tracking-wider uppercase">
                      <lucide-icon [img]="Palette" class="size-4 shrink-0" />
                      <span>Token Palette & Swatches</span>
                    </div>
                    <h2 class="mt-1 text-2xl font-bold tracking-tight sm:text-3xl">Official Brand Palette & OKLCH</h2>
                    <p class="text-muted-foreground mt-1 max-w-2xl text-sm">
                      Perceptually uniform color tokens with high WCAG contrast ratings for both dark and light UI surfaces.
                    </p>
                  </div>
                  <span ui-badge variant="outline" class="gap-1 self-start font-mono text-xs sm:self-auto">
                    <span>5 Core Swatches</span>
                  </span>
                </div>
    
                <div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-5">
                  @for (color of activeColors(); track color.id) {
                    <div ui-card class="border-border flex flex-col justify-between overflow-hidden border shadow-xs">
                      <div>
                        <div [class]="swatchClass(color)">
                          <div class="flex items-center justify-between">
                            <span [class]="contrastPillClass(color)">
                              {{ color.contrast }}
                            </span>
                          </div>
                          <div class="flex items-center justify-between">
                            <span
                              [class]="
                                color.textDark
                                  ? 'font-mono text-xs font-bold text-black'
                                  : 'font-mono text-xs font-bold text-white'
                              "
                            >
                              {{ color.hex }}
                            </span>
                          </div>
                        </div>
    
                        <div class="space-y-3 p-4">
                          <div>
                            <h3 class="text-foreground text-sm font-semibold">{{ color.name }}</h3>
                            <p class="text-primary mt-0.5 text-xs font-medium">{{ color.role }}</p>
                          </div>
    
                          <p class="text-muted-foreground text-xs leading-relaxed">
                            {{ color.usage }}
                          </p>
    
                          <div class="space-y-1.5 pt-1">
                            <div
                              class="bg-muted/60 border-border/40 flex items-center justify-between rounded-md border px-2.5 py-1.5 text-xs"
                            >
                              <span class="text-muted-foreground truncate font-mono text-xs">{{ color.hex }}</span>
                              <button
                                type="button"
                                class="text-foreground hover:text-primary focus-visible:ring-ring flex min-h-6 items-center gap-1 rounded px-1 text-xs transition-colors focus-visible:ring-2"
                                [attr.aria-label]="'Copy ' + color.name + ' HEX'"
                                (click)="copyToClipboard('hex-' + color.id, color.hex)"
                              >
                                @if (copiedKey() === 'hex-' + color.id) {
                                  <lucide-icon [img]="Check" class="text-primary size-3 shrink-0" />
                                } @else {
                                  <lucide-icon [img]="Copy" class="size-3 shrink-0" />
                                }
                                <span class="text-xs">{{ copiedKey() === 'hex-' + color.id ? 'Copied' : 'HEX' }}</span>
                              </button>
                            </div>
    
                            <div
                              class="bg-muted/60 border-border/40 flex items-center justify-between rounded-md border px-2.5 py-1.5 text-xs"
                            >
                              <span class="text-muted-foreground truncate font-mono text-xs" [attr.title]="color.oklch">
                                {{ oklchInner(color.oklch) }}
                              </span>
                              <button
                                type="button"
                                class="text-foreground hover:text-primary focus-visible:ring-ring flex min-h-6 items-center gap-1 rounded px-1 text-xs transition-colors focus-visible:ring-2"
                                [attr.aria-label]="'Copy ' + color.name + ' OKLCH'"
                                (click)="copyToClipboard('oklch-' + color.id, color.oklch)"
                              >
                                @if (copiedKey() === 'oklch-' + color.id) {
                                  <lucide-icon [img]="Check" class="text-primary size-3 shrink-0" />
                                } @else {
                                  <lucide-icon [img]="Copy" class="size-3 shrink-0" />
                                }
                                <span class="text-xs">{{ copiedKey() === 'oklch-' + color.id ? 'Copied' : 'OKLCH' }}</span>
                              </button>
                            </div>
                          </div>
                        </div>
                      </div>
                    </div>
                  }
                </div>
              </div>
            }
    
            <!-- SECTION 3: Executive Leadership Headshots & Bios -->
            @if (activeTab() === 'all' || activeTab() === 'leadership') {
              <div class="mt-16 space-y-6">
                <div class="flex flex-col justify-between gap-4 sm:flex-row sm:items-end">
                  <div>
                    <div class="text-primary flex items-center gap-2 text-xs font-semibold tracking-wider uppercase">
                      <lucide-icon [img]="User" class="size-4 shrink-0" />
                      <span>Media Spokespersons</span>
                    </div>
                    <h2 class="mt-1 text-2xl font-bold tracking-tight sm:text-3xl">Executive Leadership & Bios</h2>
                    <p class="text-muted-foreground mt-1 max-w-2xl text-sm">
                      Official executive headshots and media blurbs for press releases, podcasts, and event keynotes.
                    </p>
                  </div>
                  <span ui-badge variant="outline" class="gap-1 self-start font-mono text-xs sm:self-auto">
                    <span>300 DPI Studio Photography</span>
                  </span>
                </div>
    
                <div class="grid gap-6 md:grid-cols-2">
                  @for (exec of activeExecutives(); track exec.id) {
                    <div ui-card class="border-border flex flex-col justify-between overflow-hidden border shadow-xs">
                      <div class="space-y-5 p-6">
                        <div class="flex flex-col items-start gap-4 sm:flex-row sm:items-center">
                          <ui-avatar class="border-border size-20 shrink-0 rounded-2xl border-2 shadow-sm">
                            <ui-avatar-image [src]="exec.avatarUrl" [alt]="exec.name" class="object-cover" />
                            <ui-avatar-fallback class="bg-primary/10 text-primary rounded-2xl text-lg font-bold">
                              {{ exec.initials }}
                            </ui-avatar-fallback>
                          </ui-avatar>
    
                          <div class="space-y-1">
                            <div class="flex flex-wrap items-center gap-2">
                              <h3 class="text-foreground text-lg font-bold">{{ exec.name }}</h3>
                              <span ui-badge variant="secondary" class="text-xs font-normal">Executive</span>
                            </div>
                            <p class="text-primary text-xs font-medium">{{ exec.role }}</p>
                            <p class="text-muted-foreground font-mono text-xs">{{ exec.dimensions }}</p>
                          </div>
                        </div>
    
                        <p class="text-muted-foreground text-xs leading-relaxed">
                          {{ exec.bio }}
                        </p>
    
                        <div class="space-y-1.5">
                          <span class="text-foreground text-xs font-medium">Speaking Topics & Focus Areas:</span>
                          <div class="flex flex-wrap gap-1.5">
                            @for (topic of exec.topics; track topic) {
                              <span ui-badge variant="outline" class="bg-muted/40 text-xs font-normal">
                                {{ topic }}
                              </span>
                            }
                          </div>
                        </div>
                      </div>
    
                      <div
                        ui-card-footer
                        class="border-border/40 bg-muted/20 mt-2 flex flex-col items-stretch justify-between gap-3 border-t p-6 pt-0 sm:flex-row sm:items-center"
                      >
                        <div class="text-muted-foreground flex items-center gap-1 text-xs">
                          <lucide-icon [img]="FileImage" class="text-primary size-3.5 shrink-0" />
                          <span>{{ exec.fileSize }} RAW TIFF & JPG</span>
                        </div>
    
                        <button
                          ui-button
                          aria-label="Download executive headshot"
                          size="sm"
                          variant="default"
                          class="focus-visible:ring-ring h-auto min-h-8 gap-1.5 text-center text-xs whitespace-normal focus-visible:ring-2"
                          (click)="handleDownload('headshot-' + exec.id)"
                        >
                          @if (downloadedKey() === 'headshot-' + exec.id) {
                            <lucide-icon [img]="Check" class="size-3.5 shrink-0" />
                          } @else {
                            <lucide-icon [img]="Download" class="size-3.5 shrink-0" />
                          }
                          <span>{{
                            downloadedKey() === 'headshot-' + exec.id
                              ? 'Headshot Downloaded'
                              : 'Download Headshot (300 DPI)'
                          }}</span>
                        </button>
                      </div>
                    </div>
                  }
                </div>
              </div>
            }
    
            <!-- SECTION 4: Press Hub & Media Contact + Brand Guidelines -->
            @if (activeTab() === 'all' || activeTab() === 'press') {
              <div class="mt-16 grid gap-6 lg:grid-cols-2">
                <div ui-card class="border-border flex flex-col justify-between border shadow-xs">
                  <div ui-card-header class="p-6 pb-4">
                    <div class="flex flex-wrap items-center justify-between gap-2">
                      <div class="text-primary flex items-center gap-2 text-xs font-semibold tracking-wider uppercase">
                        <lucide-icon [img]="Mail" class="size-4 shrink-0" />
                        <span>Press & Media Inquiries</span>
                      </div>
                      <span ui-badge variant="outline" class="font-mono text-xs">{{ version }}</span>
                    </div>
                    <h3 ui-card-title class="mt-1 text-xl font-bold">Get in Touch with Media Relations</h3>
                    <p ui-card-description class="text-xs leading-relaxed">
                      Direct access for accredited tech journalists, conference organizers, analysts, and podcast hosts.
                    </p>
                  </div>
    
                  <div ui-card-content class="space-y-4 p-6 pt-0">
                    <div
                      class="border-border bg-muted/40 flex flex-wrap items-center justify-between gap-3 rounded-lg border p-3.5"
                    >
                      <div class="flex min-w-0 items-center gap-3">
                        <div class="bg-primary/10 text-primary flex size-9 shrink-0 items-center justify-center rounded-md">
                          <lucide-icon [img]="Mail" class="size-4 shrink-0" />
                        </div>
                        <div>
                          <p class="text-foreground text-xs font-semibold">{{ pressEmail }}</p>
                          <p class="text-muted-foreground text-xs">Official Media & Inquiries Desk</p>
                        </div>
                      </div>
                      <button
                        ui-button
                        variant="outline"
                        size="sm"
                        class="h-auto min-h-8 gap-1.5 text-center text-xs whitespace-normal"
                        (click)="copyToClipboard('press-email', pressEmail)"
                      >
                        @if (copiedKey() === 'press-email') {
                          <lucide-icon [img]="Check" class="text-primary size-3.5 shrink-0" />
                        } @else {
                          <lucide-icon [img]="Copy" class="size-3.5 shrink-0" />
                        }
                        <span>{{ copiedKey() === 'press-email' ? 'Copied' : 'Copy' }}</span>
                      </button>
                    </div>
    
                    <div class="grid grid-cols-2 gap-3 pt-1">
                      <div class="border-border/60 bg-card space-y-1 rounded-lg border p-3">
                        <div class="text-foreground flex items-center gap-1.5 text-xs font-semibold">
                          <lucide-icon [img]="ShieldCheck" class="text-primary size-3.5 shrink-0" />
                          <span>&lt; 24h Response</span>
                        </div>
                        <p class="text-muted-foreground text-xs">
                          Prompt turnaround on verified interview and quote requests.
                        </p>
                      </div>
    
                      <div class="border-border/60 bg-card space-y-1 rounded-lg border p-3">
                        <div class="text-foreground flex items-center gap-1.5 text-xs font-semibold">
                          <lucide-icon [img]="Globe" class="text-primary size-3.5 shrink-0" />
                          <span>Embargo Previews</span>
                        </div>
                        <p class="text-muted-foreground text-xs">
                          Advance beta access and documentation under NDA/embargo.
                        </p>
                      </div>
                    </div>
                  </div>
    
                  <div ui-card-footer class="border-border/40 bg-muted/10 mt-2 border-t p-6 pt-0">
                    <div class="flex w-full flex-col items-stretch justify-between gap-3 pt-3 sm:flex-row sm:items-center">
                      <span class="text-muted-foreground text-xs">Need company numbers & metrics?</span>
                      <button
                        ui-button
                        aria-label="Download company fact sheet"
                        variant="outline"
                        size="sm"
                        class="h-auto min-h-8 gap-1.5 text-center text-xs whitespace-normal"
                        (click)="handleDownload('factsheet-pdf')"
                      >
                        @if (downloadedKey() === 'factsheet-pdf') {
                          <lucide-icon [img]="Check" class="text-primary size-3.5 shrink-0" />
                        } @else {
                          <lucide-icon [img]="FileText" class="size-3.5 shrink-0" />
                        }
                        <span>{{
                          downloadedKey() === 'factsheet-pdf'
                            ? 'Downloaded'
                            : 'Company Fact Sheet (.PDF · ' + factSheetPdfSize + ')'
                        }}</span>
                      </button>
                    </div>
                  </div>
                </div>
    
                <div ui-card class="border-border flex flex-col justify-between border shadow-xs">
                  <div ui-card-header class="p-6 pb-4">
                    <div class="flex flex-wrap items-center justify-between gap-2">
                      <div class="text-primary flex items-center gap-2 text-xs font-semibold tracking-wider uppercase">
                        <lucide-icon [img]="FileCode" class="size-4 shrink-0" />
                        <span>Brand Rules & Boilerplate</span>
                      </div>
                      <button
                        ui-button
                        variant="ghost"
                        size="xs"
                        class="text-primary h-auto min-h-8 gap-1 text-center text-xs whitespace-normal"
                        (click)="copyToClipboard('boilerplate-text', boilerplateText)"
                      >
                        @if (copiedKey() === 'boilerplate-text') {
                          <lucide-icon [img]="Check" class="size-3 shrink-0" />
                        } @else {
                          <lucide-icon [img]="Copy" class="size-3 shrink-0" />
                        }
                        <span>{{ copiedKey() === 'boilerplate-text' ? 'Copied Boilerplate' : 'Copy Boilerplate' }}</span>
                      </button>
                    </div>
                    <h3 ui-card-title class="mt-1 text-xl font-bold">Official Company Boilerplate</h3>
                    <p ui-card-description class="text-xs leading-relaxed">
                      Approved copy for standard "About the Company" sections in press releases and event booklets.
                    </p>
                  </div>
    
                  <div ui-card-content class="space-y-4 p-6 pt-0">
                    <div
                      class="border-border bg-muted/40 text-muted-foreground rounded-lg border p-3.5 text-xs leading-relaxed italic"
                    >
                      "{{ boilerplateText }}"
                    </div>
    
                    <div class="space-y-2">
                      <span class="text-foreground text-xs font-semibold">Brand Usage Rules:</span>
                      <div class="grid gap-2">
                        @for (rule of visibleGuidelines(); track rule.text) {
                          <div class="text-muted-foreground flex items-start gap-2 text-xs">
                            @if (rule.type === 'do') {
                              <lucide-icon [img]="CheckCircle2" class="text-success mt-0.5 size-3.5 shrink-0" />
                            } @else {
                              <lucide-icon [img]="XCircle" class="text-warning mt-0.5 size-3.5 shrink-0" />
                            }
                            <span class="leading-normal">{{ rule.text }}</span>
                          </div>
                        }
                      </div>
                    </div>
                  </div>
    
                  <div ui-card-footer class="border-border/40 bg-muted/10 mt-2 border-t p-6 pt-0">
                    <div class="text-muted-foreground flex w-full items-center justify-between pt-3 text-xs">
                      <span>Questions regarding brand trademark usage?</span>
                      <a
                        [href]="'mailto:' + pressEmail + '?subject=Brand%20Guideline%20Inquiry'"
                        class="text-primary inline-flex items-center gap-1 font-medium hover:underline"
                      >
                        Contact Legal Desk
                        <lucide-icon [img]="ExternalLink" class="size-3 shrink-0" />
                      </a>
                    </div>
                  </div>
                </div>
              </div>
            }
          </div>
        </div>
      `,
    })
    export class UiPressKitBrandAssetsComponent {
      protected readonly Check = Check
      protected readonly CheckCircle2 = CheckCircle2
      protected readonly Copy = Copy
      protected readonly Download = Download
      protected readonly ExternalLink = ExternalLink
      protected readonly FileArchive = FileArchive
      protected readonly FileCode = FileCode
      protected readonly FileImage = FileImage
      protected readonly FileText = FileText
      protected readonly Globe = Globe
      protected readonly Info = Info
      protected readonly Layers = Layers
      protected readonly Mail = Mail
      protected readonly Palette = Palette
      protected readonly ShieldCheck = ShieldCheck
      protected readonly User = User
      protected readonly XCircle = XCircle
    
      @Input() companyName = 'UIPKGE'
      @Input() title = 'Brand Guidelines & Press Kit'
      @Input() subtitle = 'Official logos, brand colors, typography rules, and executive media assets.'
      @Input() version = 'v2.4'
      @Input() lastUpdated = 'August 2026'
      @Input() pressEmail = '[email protected]'
      @Input() pressKitZipSize = '48 MB'
      @Input() pressKitZipUrl = '#'
      @Input() factSheetPdfSize = '2.4 MB'
      @Input()
      boilerplateText =
        'UIPKGE is an open-source multi-framework UI registry providing accessible standalone components for Vue, React, and Angular with 100% code ownership. Headquartered in San Francisco with a distributed core maintainer team.'
      @Input() variant: 'default' | 'compact' = 'default'
      @Input('class') className?: string
    
      private readonly logosInput = signal<LogoAsset[] | undefined>(undefined)
      private readonly colorsInput = signal<BrandColor[] | undefined>(undefined)
      private readonly executivesInput = signal<Executive[] | undefined>(undefined)
      private readonly guidelinesInput = signal<BrandGuidelineRule[] | undefined>(undefined)
    
      @Input() set logos(v: LogoAsset[] | undefined) {
        this.logosInput.set(v)
      }
      @Input() set colors(v: BrandColor[] | undefined) {
        this.colorsInput.set(v)
      }
      @Input() set executives(v: Executive[] | undefined) {
        this.executivesInput.set(v)
      }
      @Input() set guidelines(v: BrandGuidelineRule[] | undefined) {
        this.guidelinesInput.set(v)
      }
    
      readonly activeLogos = computed(() => this.logosInput() ?? DEFAULT_LOGOS)
      readonly activeColors = computed(() => this.colorsInput() ?? DEFAULT_COLORS)
      readonly activeExecutives = computed(() => this.executivesInput() ?? DEFAULT_EXECUTIVES)
      readonly activeGuidelines = computed(() => this.guidelinesInput() ?? DEFAULT_GUIDELINES)
      readonly visibleGuidelines = computed(() => this.activeGuidelines().slice(0, 4))
    
      readonly activeTab = signal<PressKitTab>('all')
      readonly copiedKey = signal<string | null>(null)
      readonly downloadedKey = signal<string | null>(null)
    
      private copyTimer: ReturnType<typeof setTimeout> | undefined
      private downloadTimer: ReturnType<typeof setTimeout> | undefined
    
      get rootClass(): string {
        return cn('bg-background text-foreground w-full transition-colors', this.className)
      }
    
      logoPreviewClass(logo: LogoAsset): string {
        return cn(
          'relative flex min-h-[190px] items-center justify-center overflow-hidden p-4 text-center transition-colors sm:p-8',
          logo.theme === 'dark'
            ? 'bg-zinc-950 text-white'
            : logo.theme === 'light' && logo.id === 'monochrome-light'
              ? 'bg-zinc-100 text-zinc-950 dark:bg-zinc-200'
              : 'bg-muted/40 text-foreground bg-[radial-gradient(var(--border)_1px,transparent_1px)] [background-size:12px_12px]',
        )
      }
    
      /** Short wordmark preview label (the Vue block renders inline SVGs per logo id). */
      logoPreviewLabel(logo: LogoAsset): string {
        if (logo.id === 'app-icon-mark') return '◈'
        return `${this.companyName} .dev`
      }
    
      swatchClass(color: BrandColor): string {
        return cn('border-border/40 relative flex h-24 w-full flex-col justify-between border-b p-3', color.bgClass)
      }
    
      contrastPillClass(color: BrandColor): string {
        return cn(
          'rounded-full px-2 py-0.5 text-xs font-medium backdrop-blur-md',
          color.textDark ? 'bg-black/10 text-black' : 'bg-white/20 text-white',
        )
      }
    
      oklchInner(oklch: string): string {
        const open = oklch.indexOf('(')
        const close = oklch.lastIndexOf(')')
        if (open >= 0 && close > open) return oklch.slice(open + 1, close)
        return oklch
      }
    
      async copyToClipboard(key: string, text: string): Promise<void> {
        try {
          await navigator.clipboard.writeText(text)
        } catch {
          // Clipboard unavailable (jsdom) — still show feedback.
        }
        this.copiedKey.set(key)
        clearTimeout(this.copyTimer)
        this.copyTimer = setTimeout(() => {
          if (this.copiedKey() === key) this.copiedKey.set(null)
        }, 1800)
      }
    
      handleDownload(key: string): void {
        this.downloadedKey.set(key)
        clearTimeout(this.downloadTimer)
        this.downloadTimer = setTimeout(() => {
          if (this.downloadedKey() === key) this.downloadedKey.set(null)
        }, 2000)
      }
    }
    
  • src/app/components/blocks/press-kit-brand-assets/index.ts0.2 kB

Raw manifest:https://uipkge.dev/r/angular/press-kit-brand-assets.json