Skip to content

Latest commit

 

History

History
236 lines (236 loc) · 11.6 KB

figma.md

File metadata and controls

236 lines (236 loc) · 11.6 KB

const TOKENS = { colors: { red: { base: { r: 0.76, g: 0.11, b: 0.11 }, dark: { r: 0.55, g: 0.09, b: 0.09 } }, // ~#C3001D / #8C1717 gray: { 900: { r: 0.12, g: 0.12, b: 0.12 }, // #1F1F1F 700: { r: 0.25, g: 0.25, b: 0.25 }, // #404040 400: { r: 0.60, g: 0.60, b: 0.60 }, // #999 100: { r: 0.97, g: 0.97, b: 0.98 }, // paper 50: { r: 1.00, g: 1.00, b: 1.00 }, // white }, outline: { r: 0.90, g: 0.90, b: 0.92 }, }, radii: { xs: 6, sm: 12, md: 16, lg: 24, pill: 999, }, spacing: { xs: 8, sm: 12, md: 16, lg: 24, xl: 32, xxl: 48, xxxl: 64 }, width: 1440, }; // --------------------------- // Utilities // --------------------------- async function loadFonts() { await figma.loadFontAsync({ family: 'Inter', style: 'Regular' }); await figma.loadFontAsync({ family: 'Inter', style: 'Medium' }); await figma.loadFontAsync({ family: 'Inter', style: 'Semi Bold' }).catch(async () => { await figma.loadFontAsync({ family: 'Inter', style: 'Bold' }); }); await figma.loadFontAsync({ family: 'Inter', style: 'Bold' }); await figma.loadFontAsync({ family: 'Inter', style: 'Extra Bold' }).catch(async () => { await figma.loadFontAsync({ family: 'Inter', style: 'Black' }).catch(() => {}); }); } function setAutoLayout(frame: FrameNode, mode: 'HORIZONTAL' | 'VERTICAL', opts: Partial = {}) { frame.layoutMode = mode; frame.counterAxisSizingMode = 'AUTO'; frame.primaryAxisSizingMode = 'AUTO'; frame.paddingLeft = opts.paddingLeft ?? TOKENS.spacing.xl; frame.paddingRight = opts.paddingRight ?? TOKENS.spacing.xl; frame.paddingTop = opts.paddingTop ?? TOKENS.spacing.lg; frame.paddingBottom = opts.paddingBottom ?? TOKENS.spacing.lg; frame.itemSpacing = opts.itemSpacing ?? TOKENS.spacing.lg; } function createFrame(name: string, fill = TOKENS.colors.gray[50]): FrameNode { const f = figma.createFrame(); f.name = name; f.fills = [{ type: 'SOLID', color: fill }]; f.clipsContent = false; f.cornerRadius = 0; return f; } function createText(text: string, size = 16, color = TOKENS.colors.gray[900], weight: 'Regular'|'Medium'|'Bold'|'Extra Bold' = 'Regular') { const t = figma.createText(); t.characters = text; t.fontName = { family: 'Inter', style: weight }; t.fontSize = size; t.fills = [{ type: 'SOLID', color }]; t.lineHeight = { unit: 'AUTO' }; return t; } function createButton(label: string, variant: 'PRIMARY'|'SECONDARY' = 'PRIMARY'): ComponentNode { const component = figma.createComponent(); component.name = Button/${variant}; setAutoLayout(component, 'HORIZONTAL', { itemSpacing: TOKENS.spacing.sm, paddingLeft: TOKENS.spacing.lg, paddingRight: TOKENS.spacing.lg, paddingTop: 12, paddingBottom: 12 }); component.cornerRadius = TOKENS.radii.sm; const text = createText(label, 16, variant === 'PRIMARY' ? TOKENS.colors.gray[50] : TOKENS.colors.gray[900], 'Medium'); if (variant === 'PRIMARY') { component.fills = [{ type: 'SOLID', color: TOKENS.colors.red.base }]; component.strokes = []; } else { component.fills = [{ type: 'SOLID', color: TOKENS.colors.gray[50] }]; component.strokes = [{ type: 'SOLID', color: TOKENS.colors.outline }]; component.strokeWeight = 1; } component.appendChild(text); return component; } function createNavItem(label: string, active = false, admin = false): ComponentNode { const c = figma.createComponent(); c.name = NavItem/${label}${admin ? '/ADMIN' : ''}${active ? '/Active' : ''}; setAutoLayout(c, 'HORIZONTAL', { paddingLeft: 12, paddingRight: 12, paddingTop: 8, paddingBottom: 8 }); c.cornerRadius = TOKENS.radii.pill; c.fills = [{ type: 'SOLID', color: TOKENS.colors.gray[50] }]; const color = admin ? TOKENS.colors.red.base : (active ? TOKENS.colors.gray[900] : TOKENS.colors.gray[700]); const t = createText(label, 14, color, active ? 'Bold' : 'Medium'); c.appendChild(t); if (active && !admin) { c.strokes = [{ type: 'SOLID', color: TOKENS.colors.gray[900] }]; c.strokeWeight = 1; } if (admin) { c.strokes = [{ type: 'SOLID', color: TOKENS.colors.red.base }]; c.strokeWeight = 1; } return c; } function createCarouselCard(): ComponentNode { const c = figma.createComponent(); c.name = 'Carousel/Card'; c.resize(360, 220); c.cornerRadius = TOKENS.radii.md; c.fills = [{ type: 'SOLID', color: TOKENS.colors.gray[100] }]; c.strokes = [{ type: 'SOLID', color: TOKENS.colors.outline }]; c.strokeWeight = 1; const label = createText('Image', 14, TOKENS.colors.gray[400], 'Medium'); label.x = 16; label.y = 16; c.appendChild(label); return c; } function createDots(count = 7, activeIndex = 2): FrameNode { const wrap = createFrame('Carousel/Dots', TOKENS.colors.gray[50]); setAutoLayout(wrap, 'HORIZONTAL', { itemSpacing: 8, paddingLeft: 0, paddingRight: 0, paddingTop: 0, paddingBottom: 0 }); for (let i = 0; i < count; i++) { const dot = figma.createEllipse(); dot.resize(8, 8); dot.fills = [{ type: 'SOLID', color: i === activeIndex ? TOKENS.colors.red.base : TOKENS.colors.outline }]; wrap.appendChild(dot); } return wrap; } async function main() { await loadFonts(); const page = figma.createPage(); page.name = 'APFF – Home'; figma.currentPage = page; const canvas = createFrame('Desktop 1440', TOKENS.colors.gray[50]); canvas.resize(TOKENS.width, 2200); setAutoLayout(canvas, 'VERTICAL', { itemSpacing: TOKENS.spacing.xxl, paddingLeft: TOKENS.spacing.xxxl, paddingRight: TOKENS.spacing.xxxl, paddingTop: TOKENS.spacing.xxl, paddingBottom: TOKENS.spacing.xxl }); page.appendChild(canvas); // ---------------- Nav ---------------- const nav = createFrame('Top/Nav', TOKENS.colors.gray[50]); setAutoLayout(nav, 'HORIZONTAL', { itemSpacing: TOKENS.spacing.xl, paddingLeft: 0, paddingRight: 0, paddingTop: 0, paddingBottom: 0 }); nav.counterAxisAlignItems = 'CENTER'; const brand = createFrame('Brand', TOKENS.colors.gray[50]); setAutoLayout(brand, 'HORIZONTAL', { itemSpacing: TOKENS.spacing.sm, paddingLeft: 0, paddingRight: 0, paddingTop: 0, paddingBottom: 0 }); brand.counterAxisAlignItems = 'CENTER'; const logo = figma.createEllipse(); logo.resize(40, 40); logo.fills = [{ type: 'SOLID', color: TOKENS.colors.red.base }]; logo.strokes = [{ type: 'SOLID', color: TOKENS.colors.gray[50] }]; logo.strokeWeight = 4; const brandText = createText('APFF', 20, TOKENS.colors.gray[900], 'Bold'); brand.appendChild(logo); brand.appendChild(brandText); const navRight = createFrame('Nav Items', TOKENS.colors.gray[50]); setAutoLayout(navRight, 'HORIZONTAL', { itemSpacing: TOKENS.spacing.md, paddingLeft: 0, paddingRight: 0, paddingTop: 0, paddingBottom: 0 }); const niHome = createNavItem('Home', true); const niMap = createNavItem('Map'); const niConnect = createNavItem('Connect'); const niAdmin = createNavItem('Admin', false, true); navRight.appendChild(niHome.createInstance()); navRight.appendChild(niMap.createInstance()); navRight.appendChild(niConnect.createInstance()); navRight.appendChild(niAdmin.createInstance()); nav.appendChild(brand); nav.appendChild(navRight); canvas.appendChild(nav); // --------------- Hero / Carousel --------------- const hero = createFrame('Hero', TOKENS.colors.gray[50]); setAutoLayout(hero, 'VERTICAL', { itemSpacing: TOKENS.spacing.lg, paddingLeft: 0, paddingRight: 0, paddingTop: 0, paddingBottom: 0 }); const carouselRow = createFrame('Carousel', TOKENS.colors.gray[50]); setAutoLayout(carouselRow, 'HORIZONTAL', { itemSpacing: TOKENS.spacing.lg, paddingLeft: 0, paddingRight: 0, paddingTop: 0, paddingBottom: 0 }); const cardComp = createCarouselCard(); const c1 = cardComp.createInstance(); const c2 = cardComp.createInstance(); const c3 = cardComp.createInstance(); c1.resize(520, 300); c2.resize(520, 300); c3.resize(520, 300); carouselRow.appendChild(c1); carouselRow.appendChild(c2); carouselRow.appendChild(c3); const dots = createDots(9, 3); hero.appendChild(carouselRow); hero.appendChild(dots); canvas.appendChild(hero); // --------------- Title + Info / CTA --------------- const title = createText('Atlanta Professional Fire Foundation', 44, TOKENS.colors.gray[900], 'Extra Bold'); canvas.appendChild(title); const info = createFrame('Info Section', TOKENS.colors.gray[50]); setAutoLayout(info, 'HORIZONTAL', { itemSpacing: TOKENS.spacing.xxxl, paddingLeft: 0, paddingRight: 0, paddingTop: 0, paddingBottom: 0 }); info.counterAxisAlignItems = 'MIN'; const leftCol = createFrame('About', TOKENS.colors.gray[50]); setAutoLayout(leftCol, 'VERTICAL', { itemSpacing: TOKENS.spacing.md, paddingLeft: 0, paddingRight: 0, paddingTop: 0, paddingBottom: 0 }); leftCol.resize(760, 1); const aboutH = createText('Our Mission', 22, TOKENS.colors.gray[900], 'Bold'); const aboutP = createText('We support Atlanta’s firefighters, their families, and the communities they serve through training, wellness, and emergency relief. Your support helps us equip stations, fund scholarships, and respond when it matters most.', 16, TOKENS.colors.gray[700], 'Regular'); aboutP.lineHeight = { unit: 'PIXELS', value: 26 }; leftCol.appendChild(aboutH); leftCol.appendChild(aboutP); const rightCol = createFrame('CTAs', TOKENS.colors.gray[50]); setAutoLayout(rightCol, 'VERTICAL', { itemSpacing: TOKENS.spacing.md, paddingLeft: 0, paddingRight: 0, paddingTop: 0, paddingBottom: 0 }); const btnPrimary = createButton('Donate', 'PRIMARY'); const btnSecondary = createButton('Learn More', 'SECONDARY'); rightCol.appendChild(btnPrimary.createInstance()); rightCol.appendChild(btnSecondary.createInstance()); info.appendChild(leftCol); info.appendChild(rightCol); canvas.appendChild(info); //map page const finder = createFrame('Find Your Station', TOKENS.colors.gray[100]); setAutoLayout(finder, 'HORIZONTAL', { itemSpacing: TOKENS.spacing.xl, paddingLeft: TOKENS.spacing.xxl, paddingRight: TOKENS.spacing.xxl, paddingTop: TOKENS.spacing.xl, paddingBottom: TOKENS.spacing.xl }); finder.cornerRadius = TOKENS.radii.lg; finder.strokes = [{ type: 'SOLID', color: TOKENS.colors.outline }]; finder.strokeWeight = 1; const finderTextWrap = createFrame('Copy', TOKENS.colors.gray[100]); setAutoLayout(finderTextWrap, 'VERTICAL', { itemSpacing: 6, paddingLeft: 0, paddingRight: 0, paddingTop: 0, paddingBottom: 0 }); const finderH = createText('Find Your Station', 22, TOKENS.colors.gray[900], 'Bold'); const finderP = createText('Search stations, contacts, and resources on our interactive map.', 16, TOKENS.colors.gray[700]); finderTextWrap.appendChild(finderH); finderTextWrap.appendChild(finderP); const goMap = createButton('Open Map', 'PRIMARY').createInstance(); finder.appendChild(finderTextWrap); finder.appendChild(goMap); canvas.appendChild(finder); // ---------------- Footer ---------------- const footer = createFrame('Footer', TOKENS.colors.gray[50]); setAutoLayout(footer, 'VERTICAL', { itemSpacing: 8, paddingLeft: 0, paddingRight: 0, paddingTop: TOKENS.spacing.xl, paddingBottom: TOKENS.spacing.xl }); const footLine = figma.createRectangle(); footLine.resize(TOKENS.width - TOKENS.spacing.xxxl * 2, 1); footLine.fills = [{ type: 'SOLID', color: TOKENS.colors.outline }]; const footText = createText('© ' + new Date().getFullYear() + ' Atlanta Professional Fire Foundation. All rights reserved.', 12, TOKENS.colors.gray[400]); footer.appendChild(footLine); footer.appendChild(footText); canvas.appendChild(footer); // Center viewport figma.viewport.scrollAndZoomIntoView([canvas]); figma.notify('APFF Home scaffold created. You can replace carousel placeholders with real photos and refine styles.'); figma.closePlugin(); } main().catch((e) => { console.error(e); figma.notify('Plugin error: ' + e.message); figma.closePlugin(); });