mirror of
https://github.com/jackyzha0/quartz.git
synced 2025-12-19 10:54:06 -06:00
feat: Plugin for dynamically generated opengraph images (#108)
This commit is contained in:
parent
23255696cd
commit
a30c365ad1
149
.cursor/rules/component-development.mdc
Normal file
149
.cursor/rules/component-development.mdc
Normal file
@ -0,0 +1,149 @@
|
||||
---
|
||||
globs: *.ts,*.tsx
|
||||
description: Development guidelines for React/TypeScript components in the Quartz framework
|
||||
---
|
||||
|
||||
# Component Development Guidelines
|
||||
|
||||
## Quartz Component Architecture
|
||||
Quartz uses Preact (React-compatible) for components. All UI components live in [quartz/components/](mdc:quartz/components/).
|
||||
|
||||
### Component Types
|
||||
1. **Page Components** (`quartz/components/pages/`) - Full page layouts
|
||||
2. **UI Components** (`quartz/components/`) - Reusable interface elements
|
||||
3. **Script Components** (`quartz/components/scripts/`) - Client-side functionality
|
||||
|
||||
### Component Structure
|
||||
Every Quartz component should follow this pattern:
|
||||
|
||||
```typescript
|
||||
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||
|
||||
const ComponentName: QuartzComponent = ({
|
||||
fileData,
|
||||
cfg,
|
||||
tree,
|
||||
displayClass
|
||||
}: QuartzComponentProps) => {
|
||||
// Component logic here
|
||||
return <div class={displayClass}>
|
||||
{/* JSX content */}
|
||||
</div>
|
||||
}
|
||||
|
||||
// Optional: Add CSS if needed
|
||||
ComponentName.css = "component-name.scss"
|
||||
|
||||
// Export as constructor
|
||||
export default (() => ComponentName) satisfies QuartzComponentConstructor
|
||||
```
|
||||
|
||||
## TypeScript Conventions
|
||||
|
||||
### Type Safety
|
||||
- Always use proper TypeScript types, never `any`
|
||||
- Import types from [quartz/components/types.ts](mdc:quartz/components/types.ts)
|
||||
- Use `QuartzComponentProps` for component props
|
||||
- For utility functions, import from [quartz/util/](mdc:quartz/util/)
|
||||
|
||||
### Required Imports
|
||||
```typescript
|
||||
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||
```
|
||||
|
||||
### File Data Access
|
||||
Components receive file metadata through `fileData`:
|
||||
```typescript
|
||||
const title = fileData.frontmatter?.title
|
||||
const slug = fileData.slug
|
||||
const content = fileData.description
|
||||
```
|
||||
|
||||
## Component Patterns
|
||||
|
||||
### Conditional Rendering
|
||||
```typescript
|
||||
if (!fileData.frontmatter?.title) {
|
||||
return null
|
||||
}
|
||||
```
|
||||
|
||||
### CSS Classes
|
||||
- Use `displayClass` prop for responsive visibility
|
||||
- Add component-specific classes with BEM methodology
|
||||
- SCSS files should be named to match component
|
||||
|
||||
### Client-Side Scripts
|
||||
For interactive components, create accompanying scripts in `quartz/components/scripts/`:
|
||||
```typescript
|
||||
// In script file
|
||||
document.addEventListener("nav", () => {
|
||||
// Re-initialize after page navigation
|
||||
})
|
||||
```
|
||||
|
||||
## Component Registration
|
||||
|
||||
### In quartz.layout.ts
|
||||
Add new components to the appropriate layout section:
|
||||
```typescript
|
||||
export const defaultContentPageLayout: PageLayout = {
|
||||
beforeBody: [
|
||||
Component.Breadcrumbs(),
|
||||
Component.ArticleTitle(),
|
||||
Component.ContentMeta(),
|
||||
Component.TagList(),
|
||||
],
|
||||
left: [
|
||||
Component.PageTitle(),
|
||||
Component.MobileOnly(Component.Spacer()),
|
||||
Component.Search(),
|
||||
Component.Darkmode(),
|
||||
Component.DesktopOnly(Component.Explorer()),
|
||||
],
|
||||
right: [
|
||||
Component.Graph(),
|
||||
Component.DesktopOnly(Component.TableOfContents()),
|
||||
Component.Backlinks(),
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
### Component Exports
|
||||
All components must be exported in [quartz/components/index.ts](mdc:quartz/components/index.ts):
|
||||
```typescript
|
||||
export { default as YourComponent } from "./YourComponent"
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Performance
|
||||
- Use functional components (avoid class components)
|
||||
- Minimize DOM manipulation; prefer declarative JSX
|
||||
- For heavy computations, consider memoization
|
||||
|
||||
### Accessibility
|
||||
- Use semantic HTML elements
|
||||
- Include proper ARIA labels where needed
|
||||
- Ensure keyboard navigation works
|
||||
|
||||
### Responsive Design
|
||||
- Use `displayClass` for responsive hiding/showing
|
||||
- Test components at different screen sizes
|
||||
- Consider mobile-first design approach
|
||||
|
||||
### Error Handling
|
||||
- Handle missing data gracefully
|
||||
- Provide fallbacks for optional frontmatter fields
|
||||
- Use optional chaining: `fileData.frontmatter?.field`
|
||||
|
||||
## Debugging
|
||||
- Use `console.log` sparingly; prefer TypeScript for catching errors
|
||||
- Test component isolation by temporarily removing from layout
|
||||
- Check browser dev tools for client-side script errors
|
||||
|
||||
## Integration with Plugins
|
||||
Components often work with Quartz plugins:
|
||||
- Transformers modify content before components receive it
|
||||
- Emitters use components to generate final HTML
|
||||
- Check [quartz/plugins/](mdc:quartz/plugins/) for relevant plugin APIs
|
||||
79
.cursor/rules/content-authoring.mdc
Normal file
79
.cursor/rules/content-authoring.mdc
Normal file
@ -0,0 +1,79 @@
|
||||
---
|
||||
globs: *.md
|
||||
description: Guidelines for authoring blog posts, research articles, and other markdown content
|
||||
---
|
||||
|
||||
# Content Authoring Guidelines
|
||||
|
||||
## Frontmatter Requirements
|
||||
All markdown files must include frontmatter with at minimum:
|
||||
```yaml
|
||||
---
|
||||
title: "Your Title Here"
|
||||
---
|
||||
```
|
||||
|
||||
### Optional Frontmatter Fields
|
||||
- `description: "Brief description for SEO and previews"`
|
||||
- `tags: ["tag1", "tag2", "tag3"]` - For categorization
|
||||
- `draft: true` - To exclude from build (removed by RemoveDrafts plugin)
|
||||
- `aliases: ["alternative-url"]` - For URL redirects
|
||||
|
||||
## Content Structure Best Practices
|
||||
|
||||
### File Organization
|
||||
- **Blog posts**: Place in `content/blog/` with descriptive filenames
|
||||
- **Research**: Use `content/research/` for academic/technical papers
|
||||
- **Release notes**: Follow pattern in `content/releases/` with date-based naming
|
||||
- **Quick notes**: Use `content/notes/` for shorter thoughts and observations
|
||||
|
||||
### Asset References
|
||||
- Store images and media in `content/assets/`
|
||||
- Reference assets relatively: ``
|
||||
- Use descriptive filenames for assets (avoid spaces, use hyphens)
|
||||
- Optimize images for web (prefer .webp for photos, .png for diagrams)
|
||||
|
||||
### Writing Style
|
||||
- Use clear, descriptive headings (## and ###)
|
||||
- Include code blocks with proper language specification:
|
||||
```typescript
|
||||
// Your code here
|
||||
```
|
||||
- For mathematical content, use LaTeX syntax with $ for inline math and $$ for blocks
|
||||
- Link to other content using wikilink syntax: `[[Other Page Title]]`
|
||||
|
||||
### Content Types
|
||||
|
||||
#### Blog Posts
|
||||
- Should tell a complete story or explain a concept thoroughly
|
||||
- Include relevant diagrams or code examples
|
||||
- Consider adding a brief description for better discoverability
|
||||
|
||||
#### Research Articles
|
||||
- Follow academic formatting conventions
|
||||
- Include methodology, results, and conclusions sections
|
||||
- Reference external papers and sources appropriately
|
||||
|
||||
#### Release Notes
|
||||
- Follow existing date-based naming convention (e.g., "Release Notes MM.DD.YY.md")
|
||||
- Include version numbers and brief feature summaries
|
||||
- Categorize changes (New Features, Bug Fixes, Improvements)
|
||||
|
||||
#### Notes
|
||||
- Can be brief thoughts, observations, or work-in-progress ideas
|
||||
- Don't need to be complete articles
|
||||
- Good for capturing insights that might become full posts later
|
||||
|
||||
## Markdown Extensions
|
||||
The site supports:
|
||||
- GitHub Flavored Markdown (tables, task lists, strikethrough)
|
||||
- Obsidian-style wikilinks `[[Page Name]]`
|
||||
- Math via KaTeX (`$inline$` and `$$block$$`)
|
||||
- Syntax highlighting for code blocks
|
||||
- Callouts (follow Obsidian syntax)
|
||||
|
||||
## SEO Considerations
|
||||
- Use descriptive, unique titles
|
||||
- Include meta descriptions when appropriate
|
||||
- Use proper heading hierarchy (h1 → h2 → h3)
|
||||
- Internal linking helps with site navigation and SEO
|
||||
50
.cursor/rules/project-structure.mdc
Normal file
50
.cursor/rules/project-structure.mdc
Normal file
@ -0,0 +1,50 @@
|
||||
---
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Plastic Labs Blog - Project Structure Guide
|
||||
|
||||
This is a Quartz-based blog for Plastic Labs. Understanding the project structure is essential for effective development.
|
||||
|
||||
## Core Configuration
|
||||
- Main entry point: [quartz.config.ts](mdc:quartz.config.ts) - Quartz configuration including plugins, theme, and site settings
|
||||
- Layout configuration: [quartz.layout.ts](mdc:quartz.layout.ts) - Defines the visual layout and component arrangement
|
||||
- TypeScript config: [tsconfig.json](mdc:tsconfig.json) - TypeScript compilation settings
|
||||
- Package dependencies: [package.json](mdc:package.json) - Node.js dependencies and build scripts
|
||||
|
||||
## Content Organization
|
||||
- **Main content directory**: [content/](mdc:content/) - All markdown content lives here
|
||||
- `content/blog/` - Blog posts and articles
|
||||
- `content/research/` - Research papers and findings
|
||||
- `content/releases/` - Release notes and announcements
|
||||
- `content/notes/` - Quick notes and thoughts
|
||||
- `content/careers/` - Job postings and career-related content
|
||||
- `content/extrusions/` - Special content type (monthly updates)
|
||||
- `content/assets/` - Images, diagrams, and media assets
|
||||
- `content/templates/` - Content templates (ignored in build)
|
||||
|
||||
## Quartz Framework Structure
|
||||
- **Core framework**: [quartz/](mdc:quartz/) - Contains all Quartz framework code
|
||||
- `quartz/components/` - React/Preact UI components (.tsx files)
|
||||
- `quartz/plugins/` - Quartz plugins for content processing
|
||||
- `quartz/styles/` - Global SCSS stylesheets
|
||||
- `quartz/util/` - Utility functions and helpers
|
||||
- `quartz/build.ts` - Main build system entry point
|
||||
|
||||
## Build System
|
||||
- Built content outputs to `public/` directory
|
||||
- Static assets in `static/` are copied to root of public output via custom CopyStatic plugin
|
||||
- Uses esbuild for bundling and TypeScript compilation
|
||||
- Supports hot reloading during development
|
||||
|
||||
## Key Conventions
|
||||
- All content files use markdown (.md) with frontmatter
|
||||
- Assets should be placed in `content/assets/` and referenced relatively
|
||||
- Custom components go in `quartz/components/`
|
||||
- Global styles in `quartz/styles/`, component-specific styles as .scss files in component directories
|
||||
|
||||
## Theme Customization
|
||||
The site uses a custom dark/light theme with:
|
||||
- Monospace fonts (Departure Mono for headers, Roboto Mono for body)
|
||||
- Custom color scheme defined in quartz.config.ts
|
||||
- SCSS variables in [quartz/styles/variables.scss](mdc:quartz/styles/variables.scss)
|
||||
185
.cursor/rules/quartz-configuration.mdc
Normal file
185
.cursor/rules/quartz-configuration.mdc
Normal file
@ -0,0 +1,185 @@
|
||||
---
|
||||
globs: quartz.config.ts,quartz.layout.ts
|
||||
description: Guidelines for configuring Quartz framework settings and layouts
|
||||
---
|
||||
|
||||
# Quartz Configuration Guidelines
|
||||
|
||||
## Core Configuration Files
|
||||
|
||||
### quartz.config.ts
|
||||
Main configuration file containing:
|
||||
- Site metadata (title, baseUrl, analytics)
|
||||
- Theme settings (fonts, colors)
|
||||
- Plugin pipeline (transformers, filters, emitters)
|
||||
- Build settings and ignore patterns
|
||||
|
||||
### quartz.layout.ts
|
||||
Defines visual layout and component arrangement for different page types.
|
||||
|
||||
## Configuration Structure
|
||||
|
||||
### Site Settings
|
||||
```typescript
|
||||
configuration: {
|
||||
pageTitle: "🥽 Plastic Labs", // Site title in header
|
||||
enableSPA: true, // Single-page app routing
|
||||
enablePopovers: true, // Link preview popups
|
||||
analytics: { provider: "postHog" }, // Analytics integration
|
||||
baseUrl: "plasticlabs.ai", // Production domain
|
||||
ignorePatterns: ["private", "templates"], // Exclude from build
|
||||
locale: "en-US", // Language/locale
|
||||
defaultDateType: "created", // Date display preference
|
||||
}
|
||||
```
|
||||
|
||||
### Theme Configuration
|
||||
```typescript
|
||||
theme: {
|
||||
cdnCaching: true, // Enable CDN for fonts
|
||||
typography: {
|
||||
header: "Departure Mono", // Header font
|
||||
body: "Roboto Mono", // Body text font
|
||||
code: "Ubuntu Mono", // Code block font
|
||||
},
|
||||
colors: {
|
||||
lightMode: { /* color definitions */ },
|
||||
darkMode: { /* color definitions */ }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Plugin Pipeline
|
||||
|
||||
### Plugin Types
|
||||
1. **Transformers** - Process content during build
|
||||
2. **Filters** - Include/exclude content
|
||||
3. **Emitters** - Generate output files
|
||||
|
||||
### Common Transformers
|
||||
```typescript
|
||||
transformers: [
|
||||
Plugin.FrontMatter(), // Parse YAML frontmatter
|
||||
Plugin.CreatedModifiedDate({ // Add file timestamps
|
||||
priority: ["frontmatter", "filesystem"]
|
||||
}),
|
||||
Plugin.Latex({ renderEngine: "katex" }), // Math rendering
|
||||
Plugin.SyntaxHighlighting({ // Code syntax highlighting
|
||||
theme: { light: "github-light", dark: "github-dark" },
|
||||
keepBackground: false
|
||||
}),
|
||||
Plugin.ObsidianFlavoredMarkdown(), // Wikilinks, callouts
|
||||
Plugin.GitHubFlavoredMarkdown(), // Tables, task lists
|
||||
Plugin.TableOfContents(), // Auto-generate TOCs
|
||||
Plugin.CrawlLinks(), // Process internal links
|
||||
Plugin.Description(), // Generate descriptions
|
||||
]
|
||||
```
|
||||
|
||||
### Essential Emitters
|
||||
```typescript
|
||||
emitters: [
|
||||
Plugin.AliasRedirects(), // Handle URL redirects
|
||||
Plugin.ComponentResources({
|
||||
fontOrigin: "googleFonts"
|
||||
}), // Load external resources
|
||||
Plugin.ContentPage(), // Generate content pages
|
||||
Plugin.FolderPage(), // Generate folder indexes
|
||||
Plugin.TagPage(), // Generate tag pages
|
||||
Plugin.ContentIndex({ // Generate search index
|
||||
enableSiteMap: true,
|
||||
enableRSS: true
|
||||
}),
|
||||
Plugin.Assets(), // Copy content assets
|
||||
CopyStatic(), // Custom: copy static/ files
|
||||
Plugin.Static(), // Copy other static assets
|
||||
Plugin.NotFoundPage(), // Generate 404 page
|
||||
]
|
||||
```
|
||||
|
||||
## Layout Configuration
|
||||
|
||||
### Page Layout Structure
|
||||
```typescript
|
||||
export const defaultContentPageLayout: PageLayout = {
|
||||
beforeBody: [ // Above main content
|
||||
Component.Breadcrumbs(),
|
||||
Component.ArticleTitle(),
|
||||
Component.ContentMeta(),
|
||||
Component.TagList(),
|
||||
],
|
||||
left: [ // Left sidebar
|
||||
Component.PageTitle(),
|
||||
Component.MobileOnly(Component.Spacer()),
|
||||
Component.Search(),
|
||||
Component.Darkmode(),
|
||||
Component.DesktopOnly(Component.Explorer()),
|
||||
],
|
||||
right: [ // Right sidebar
|
||||
Component.Graph(),
|
||||
Component.DesktopOnly(Component.TableOfContents()),
|
||||
Component.Backlinks(),
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
### Responsive Components
|
||||
- `Component.MobileOnly()` - Hidden on desktop
|
||||
- `Component.DesktopOnly()` - Hidden on mobile
|
||||
- `Component.Spacer()` - Adds vertical spacing
|
||||
|
||||
## Custom Plugins
|
||||
|
||||
### Creating Custom Plugins
|
||||
Follow the pattern of the existing `CopyStatic` plugin:
|
||||
```typescript
|
||||
const CustomPlugin = () => ({
|
||||
name: "CustomPlugin",
|
||||
getQuartzComponents() {
|
||||
return [] // Return components if needed
|
||||
},
|
||||
async emit({ argv, cfg }: BuildCtx): Promise<FilePath[]> {
|
||||
// Plugin logic here
|
||||
return outputFiles // Return generated file paths
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Plugin Registration
|
||||
Add custom plugins to the appropriate pipeline section in `quartz.config.ts`.
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Configuration Changes
|
||||
- Test configuration changes locally before deploying
|
||||
- Use TypeScript for type safety in config files
|
||||
- Keep plugin order consistent (some plugins depend on others)
|
||||
|
||||
### Performance Optimization
|
||||
- Enable CDN caching for fonts when possible
|
||||
- Minimize the number of transformers for faster builds
|
||||
- Use `ignorePatterns` to exclude unnecessary files
|
||||
|
||||
### Content Processing
|
||||
- Place transformers in logical order (FrontMatter should be first)
|
||||
- Configure syntax highlighting themes to match site theme
|
||||
- Enable SPA routing for better user experience
|
||||
|
||||
### Development Workflow
|
||||
- Use `npx quartz build --serve` for local development
|
||||
- Check build output in `public/` directory
|
||||
- Verify that all required files are being generated
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
- Plugin order matters - move problematic plugins earlier/later
|
||||
- Missing components in layout cause build failures
|
||||
- Invalid frontmatter breaks content processing
|
||||
- Asset path issues often relate to baseUrl configuration
|
||||
|
||||
### Debug Tips
|
||||
- Check build logs for plugin errors
|
||||
- Validate frontmatter syntax in problematic files
|
||||
- Test individual plugins by temporarily removing others
|
||||
- Use `--verbose` flag for detailed build information
|
||||
219
.cursor/rules/styling-conventions.mdc
Normal file
219
.cursor/rules/styling-conventions.mdc
Normal file
@ -0,0 +1,219 @@
|
||||
---
|
||||
globs: *.scss,*.css
|
||||
description: SCSS styling conventions and theme guidelines for the Plastic Labs blog
|
||||
---
|
||||
|
||||
# SCSS Styling Conventions
|
||||
|
||||
## File Organization
|
||||
|
||||
### Global Styles
|
||||
- **Base styles**: [quartz/styles/base.scss](mdc:quartz/styles/base.scss) - Core HTML element styles
|
||||
- **Variables**: [quartz/styles/variables.scss](mdc:quartz/styles/variables.scss) - CSS custom properties and SCSS variables
|
||||
- **Custom styles**: [quartz/styles/custom.scss](mdc:quartz/styles/custom.scss) - Site-specific overrides
|
||||
|
||||
### Component Styles
|
||||
- Component SCSS files live alongside their .tsx files in [quartz/components/styles/](mdc:quartz/components/styles/)
|
||||
- Name SCSS files to match component names (e.g., `search.scss` for Search component)
|
||||
- Import component styles in the component's .css property
|
||||
|
||||
## Theme System
|
||||
|
||||
### Color Variables
|
||||
Use CSS custom properties defined in the theme configuration:
|
||||
```scss
|
||||
.my-element {
|
||||
background-color: var(--light);
|
||||
color: var(--dark);
|
||||
border: 1px solid var(--lightgray);
|
||||
}
|
||||
```
|
||||
|
||||
### Available Color Variables
|
||||
- `--light` - Background color
|
||||
- `--lightgray` - Subtle elements (code, graph edges, outlines)
|
||||
- `--gray` - Graph nodes, medium contrast text
|
||||
- `--darkgray` - High contrast text
|
||||
- `--dark` - Primary text color
|
||||
- `--secondary` - Secondary text/UI elements
|
||||
- `--tertiary` - Accent color (green: #C0FFE1)
|
||||
- `--highlight` - Selection/hover backgrounds
|
||||
- `--customCallout` - Custom callout backgrounds
|
||||
|
||||
### Typography Variables
|
||||
```scss
|
||||
.text-element {
|
||||
font-family: var(--headerFont); // Departure Mono
|
||||
font-family: var(--bodyFont); // Roboto Mono
|
||||
font-family: var(--codeFont); // Ubuntu Mono
|
||||
}
|
||||
```
|
||||
|
||||
## Responsive Design
|
||||
|
||||
### Breakpoints
|
||||
Use consistent breakpoints across the site:
|
||||
```scss
|
||||
// Mobile-first approach
|
||||
.component {
|
||||
// Mobile styles by default
|
||||
|
||||
@media (min-width: 600px) {
|
||||
// Tablet styles
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
// Desktop styles
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Display Classes
|
||||
Leverage existing responsive classes:
|
||||
- `.mobile-only` - Hidden on desktop
|
||||
- `.desktop-only` - Hidden on mobile/tablet
|
||||
|
||||
## SCSS Best Practices
|
||||
|
||||
### BEM Methodology
|
||||
Use Block-Element-Modifier naming:
|
||||
```scss
|
||||
.search-component { // Block
|
||||
&__input { // Element
|
||||
border: 1px solid var(--lightgray);
|
||||
|
||||
&--focused { // Modifier
|
||||
border-color: var(--tertiary);
|
||||
}
|
||||
}
|
||||
|
||||
&__results { // Element
|
||||
background: var(--light);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Nesting Guidelines
|
||||
- Limit nesting to 3 levels maximum
|
||||
- Use parent selectors (&) judiciously
|
||||
- Prefer flat selectors for better performance
|
||||
|
||||
### Variable Usage
|
||||
```scss
|
||||
// Local variables for component-specific values
|
||||
.graph-component {
|
||||
$node-size: 8px;
|
||||
$link-opacity: 0.6;
|
||||
|
||||
.node {
|
||||
width: $node-size;
|
||||
height: $node-size;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Dark/Light Mode Support
|
||||
|
||||
### Automatic Theme Switching
|
||||
Colors automatically switch based on the theme configuration. Always use CSS custom properties:
|
||||
|
||||
```scss
|
||||
// ✅ Good - Uses theme variables
|
||||
.card {
|
||||
background: var(--light);
|
||||
border: 1px solid var(--lightgray);
|
||||
color: var(--dark);
|
||||
}
|
||||
|
||||
// ❌ Bad - Hard-coded colors
|
||||
.card {
|
||||
background: #E2E2E2;
|
||||
border: 1px solid #4e4e4e;
|
||||
color: #4E4E4E;
|
||||
}
|
||||
```
|
||||
|
||||
### Theme-Specific Overrides
|
||||
When necessary, use the body class for theme-specific styles:
|
||||
```scss
|
||||
.special-element {
|
||||
// Default styles
|
||||
|
||||
:root[data-theme="dark"] & {
|
||||
// Dark mode overrides
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Animation Guidelines
|
||||
|
||||
### Subtle Animations
|
||||
Keep animations minimal and performant:
|
||||
```scss
|
||||
.interactive-element {
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Reduced Motion
|
||||
Respect user preferences:
|
||||
```scss
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
* {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Card Components
|
||||
```scss
|
||||
.card {
|
||||
background: var(--light);
|
||||
border: 1px solid var(--lightgray);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
```
|
||||
|
||||
### Interactive Elements
|
||||
```scss
|
||||
.button {
|
||||
background: var(--tertiary);
|
||||
color: var(--dark);
|
||||
border: none;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: 2px solid var(--tertiary);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Efficient Selectors
|
||||
- Avoid universal selectors (*) where possible
|
||||
- Use class selectors over element selectors for components
|
||||
- Minimize expensive properties (box-shadow, filters, transforms)
|
||||
|
||||
### Asset Optimization
|
||||
- Use relative units (rem, em) for scalability
|
||||
- Prefer CSS transforms over changing layout properties
|
||||
- Test performance impact of new styles on slower devices
|
||||
@ -1,75 +0,0 @@
|
||||
---
|
||||
title: Founding ML Engineer
|
||||
date: 02.24.25
|
||||
tags:
|
||||
- positions
|
||||
- product
|
||||
- dev
|
||||
- announcements
|
||||
---
|
||||
|
||||
(NYC, Full-Time)
|
||||
|
||||
## About the Role
|
||||
|
||||
We're seeking a leading machine learning engineer who can architect breakthrough systems while staying immersed in cutting-edge research. In direct collaboration with the CEO, you'll shape the future of AI at Plastic--tackling challenges across the entire machine learning stack.
|
||||
|
||||
This role demands someone who thrives at the intersection of research and engineering; someone who can read and reproduce state-of-the-art papers, design novel architectures, and transform promising experiments into production-ready systems. You'll move fluidly between theoretical frameworks and practical implementation, making intuitive decisions about model architecture, quantization strategies, and serving infrastructure.
|
||||
|
||||
We need a technical polymath who excels across the ML stack: from designing systematic experiments and running rigorous evaluations to building robust data pipelines and scalable model serving/inference systems. You should be particularly adept with post-training techniques that are redefining the field--from advanced inference-time computation methods to reinforcement learning with reasoning models.
|
||||
|
||||
The LLM space moves at lightning speed, and so do we. You'll prototype rapidly while maintaining research rigor, implement robust MLOps practices, and craft observable systems that scale. Our small, interdisciplinary team moves fast--high agency is core to who we are. You'll have the freedom to directly impact our research and products to push the boundaries of what's possible in AI.
|
||||
|
||||
We're building systems that haven't been built before, solving problems that haven't been solved. If you're a technical leader who thrives on these challenges and can serve as our ML north star, we want you on our team.
|
||||
|
||||
## About You
|
||||
|
||||
- 3+ years of applied ML experience with deep LLM expertise
|
||||
- High cultural alignment with Plastic Labs' ethos
|
||||
- NYC-based or open to NYC relocation
|
||||
- Strong command of a popular Python ML library (e.g PyTorch, TF, JAX, HF transformers, etc)
|
||||
- Experience replicating research papers soon after publication
|
||||
- Experience building and scaling robust inference systems
|
||||
- Practical experience with post-training and inference-time techniques (RL a plus)
|
||||
- Ability to build reliable MLOps pipelines that perform under load
|
||||
- Proficiency with Unix environments and developer tools (Git, Docker, etc.)
|
||||
- Up-to-date with the Open Source AI community and emerging technologies
|
||||
- Self-directed with a bias toward rapid execution
|
||||
- Driven to push language models beyond conventional boundaries
|
||||
- Background in cognitive sciences (CS, linguistics, neuroscience, philosophy, psychology, etc...) or related fields a plus
|
||||
|
||||
> [!custom] [APPLY HERE](https://wellfound.com/l/2B3wwZ)
|
||||
|
||||
## Research We're Excited About
|
||||
|
||||
[s1: Simple test-time scaling](https://arxiv.org/abs/2501.19393)
|
||||
[Neural Networks Are Elastic Origami!](https://youtu.be/l3O2J3LMxqI?si=bhodv2c7GG75N2Ku)
|
||||
[Titans: Learning to Memorize at Test Time](https://arxiv.org/abs/2501.00663v1)
|
||||
[Mind Your Theory: Theory of Mind Goes Deeper Than Reasoning](https://arxiv.org/abs/2412.13631)
|
||||
[Generative Agent Simulations of 1,000 People](https://arxiv.org/abs/2411.10109)
|
||||
[DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning](https://arxiv.org/abs/2501.12948)
|
||||
[Multiagent Finetuning: Self Improvement with Diverse Reasoning Chains](https://arxiv.org/abs/2501.05707)
|
||||
[Prompt Programming for Large Language Models: Beyond the Few-Shot Paradigm](https://arxiv.org/pdf/2102.07350)
|
||||
[Theory of Mind May Have Spontaneously Emerged in Large Language Models](https://arxiv.org/pdf/2302.02083v3)
|
||||
[Think Twice: Perspective-Taking Improved Large Language Models' Theory-of-Mind Capabilities](https://arxiv.org/pdf/2311.10227)
|
||||
[Refusal in Language Models is Mediated by a Single Direction](https://arxiv.org/abs/2406.11717)
|
||||
[Representation Engineering: A Top-Down Approach to AI Transparency](https://arxiv.org/abs/2310.01405)
|
||||
[Theia Vogel's post on Representation Engineering Mistral 7B an Acid Trip](https://vgel.me/posts/representation-engineering/)
|
||||
[Cognitive Behaviors that Enable Self-Improving Reasoners](https://arxiv.org/abs/2503.01307)
|
||||
[All Roads Lead to Likelihood: The Value of Reinforcement Learning in Fine-Tuning](https://arxiv.org/abs/2503.01067)
|
||||
[A Roadmap to Pluralistic Alignment](https://arxiv.org/abs/2402.05070)
|
||||
[Open-Endedness is Essential for Artificial Superhuman Intelligence](https://arxiv.org/pdf/2406.04268)
|
||||
[Simulators](https://generative.ink/posts/simulators/)
|
||||
[Extended Mind Transformers](https://arxiv.org/pdf/2406.02332)
|
||||
[Violation of Expectation via Metacognitive Prompting Reduces Theory of Mind Prediction Error in Large Language Models](https://arxiv.org/abs/2310.06983)
|
||||
[Constitutional AI: Harmlessness from AI Feedback](https://arxiv.org/pdf/2212.08073)
|
||||
[Claude's Character](https://www.anthropic.com/research/claude-character)
|
||||
[Language Models Represent Space and Time](https://arxiv.org/pdf/2310.02207)
|
||||
[Generative Agents: Interactive Simulacra of Human Behavior](https://arxiv.org/abs/2304.03442)
|
||||
[Meta-Rewarding Language Models: Self-Improving Alignment with LLM-as-a-Meta-Judge](https://arxiv.org/abs/2407.19594)
|
||||
[Synthetic Sentience: Joscha Bach](https://www.youtube.com/watch?v=cs9Ls0m5QVE)
|
||||
[Cyborgism](https://www.lesswrong.com/posts/bxt7uCiHam4QXrQAA/cyborgism)
|
||||
[Spontaneous Reward Hacking in Iterative Self-Refinement](https://arxiv.org/abs/2407.04549)
|
||||
[... accompanying twitter thread](https://x.com/JanePan_/status/1813208688343052639)
|
||||
|
||||
(Back to [[Working at Plastic]])
|
||||
@ -1,61 +1,67 @@
|
||||
import { QuartzConfig } from "./quartz/cfg"
|
||||
import * as Plugin from "./quartz/plugins"
|
||||
import { Argv, BuildCtx } from "./quartz/util/ctx"
|
||||
import fs from "fs"
|
||||
import path from "path"
|
||||
import { FilePath } from "./quartz/util/path"
|
||||
import { glob } from "./quartz/util/glob"
|
||||
import { QuartzConfig } from "./quartz/cfg";
|
||||
import * as Plugin from "./quartz/plugins";
|
||||
import { Argv, BuildCtx } from "./quartz/util/ctx";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { FilePath } from "./quartz/util/path";
|
||||
import { glob } from "./quartz/util/glob";
|
||||
|
||||
// Custom plugin to copy all files from static/ to the root of public/
|
||||
const CopyStatic = () => ({
|
||||
name: "CopyStatic",
|
||||
getQuartzComponents() {
|
||||
return []
|
||||
return [];
|
||||
},
|
||||
async emit({ argv, cfg }: BuildCtx): Promise<FilePath[]> {
|
||||
const staticPath = "static"
|
||||
const publicPath = argv.output
|
||||
const staticPath = "static";
|
||||
const publicPath = argv.output;
|
||||
try {
|
||||
// Ensure static path exists
|
||||
if (!fs.existsSync(staticPath)) {
|
||||
console.log("Static directory does not exist, skipping copy.")
|
||||
return []
|
||||
console.log("Static directory does not exist, skipping copy.");
|
||||
return [];
|
||||
}
|
||||
|
||||
// Use glob to find all files in static directory, respecting ignore patterns
|
||||
const files = await glob("**/*", staticPath, cfg.configuration.ignorePatterns)
|
||||
const outputFiles: FilePath[] = []
|
||||
const files = await glob(
|
||||
"**/*",
|
||||
staticPath,
|
||||
cfg.configuration.ignorePatterns,
|
||||
);
|
||||
const outputFiles: FilePath[] = [];
|
||||
|
||||
for (const file of files) {
|
||||
const sourceFilePath = path.join(staticPath, file) as FilePath
|
||||
const sourceFilePath = path.join(staticPath, file) as FilePath;
|
||||
|
||||
// Skip if it's a directory (glob might return directories)
|
||||
if ((await fs.promises.lstat(sourceFilePath)).isDirectory()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const destFilePath = path.join(publicPath, file) as FilePath
|
||||
const destDir = path.dirname(destFilePath)
|
||||
const destFilePath = path.join(publicPath, file) as FilePath;
|
||||
const destDir = path.dirname(destFilePath);
|
||||
|
||||
// Ensure destination directory exists
|
||||
await fs.promises.mkdir(destDir, { recursive: true })
|
||||
await fs.promises.mkdir(destDir, { recursive: true });
|
||||
|
||||
// Copy file
|
||||
await fs.promises.copyFile(sourceFilePath, destFilePath)
|
||||
outputFiles.push(destFilePath)
|
||||
await fs.promises.copyFile(sourceFilePath, destFilePath);
|
||||
outputFiles.push(destFilePath);
|
||||
}
|
||||
|
||||
if (outputFiles.length > 0) {
|
||||
console.log(`Successfully copied ${outputFiles.length} files from static/ to public/`)
|
||||
console.log(
|
||||
`Successfully copied ${outputFiles.length} files from static/ to public/`,
|
||||
);
|
||||
}
|
||||
|
||||
return outputFiles
|
||||
return outputFiles;
|
||||
} catch (err) {
|
||||
console.error("Error copying static files:", err)
|
||||
return []
|
||||
console.error("Error copying static files:", err);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Quartz 4.0 Configuration
|
||||
@ -70,7 +76,7 @@ const config: QuartzConfig = {
|
||||
analytics: {
|
||||
provider: "postHog",
|
||||
},
|
||||
baseUrl: "plasticlabs.ai",
|
||||
baseUrl: "blog.plasticlabs.ai",
|
||||
ignorePatterns: ["private", "templates"],
|
||||
locale: "en-US",
|
||||
defaultDateType: "created",
|
||||
@ -128,6 +134,9 @@ const config: QuartzConfig = {
|
||||
Plugin.TableOfContents(),
|
||||
Plugin.CrawlLinks({ markdownLinkResolution: "shortest" }),
|
||||
Plugin.Description(),
|
||||
Plugin.OpenGraphImage({
|
||||
defaultImage: "/og-image.png",
|
||||
}),
|
||||
],
|
||||
filters: [Plugin.RemoveDrafts()],
|
||||
emitters: [
|
||||
@ -146,6 +155,6 @@ const config: QuartzConfig = {
|
||||
Plugin.NotFoundPage(),
|
||||
],
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export default config
|
||||
export default config;
|
||||
|
||||
@ -15,7 +15,20 @@ export default (() => {
|
||||
const baseDir = fileData.slug === "404" ? path : pathToRoot(fileData.slug!)
|
||||
|
||||
const iconPath = joinSegments(baseDir, "static/icon.png")
|
||||
const ogImagePath = `https://${cfg.baseUrl}/static/og-image.png`
|
||||
|
||||
// Use dynamic opengraph image from plugin, fallback to default
|
||||
const ogImagePath = fileData.opengraphImage
|
||||
? (fileData.opengraphImage.startsWith('http')
|
||||
? fileData.opengraphImage
|
||||
: `https://${cfg.baseUrl}${fileData.opengraphImage}`)
|
||||
: `https://${cfg.baseUrl}/og-image.png`
|
||||
|
||||
// Twitter image (same as OpenGraph)
|
||||
const twitterImagePath = fileData.twitterImage
|
||||
? (fileData.twitterImage.startsWith('http')
|
||||
? fileData.twitterImage
|
||||
: `https://${cfg.baseUrl}${fileData.twitterImage}`)
|
||||
: ogImagePath
|
||||
|
||||
return (
|
||||
<head>
|
||||
@ -33,6 +46,10 @@ export default (() => {
|
||||
{cfg.baseUrl && <meta property="og:image" content={ogImagePath} />}
|
||||
<meta property="og:width" content="1200" />
|
||||
<meta property="og:height" content="675" />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content={title} />
|
||||
<meta name="twitter:description" content={description} />
|
||||
{cfg.baseUrl && <meta name="twitter:image" content={twitterImagePath} />}
|
||||
<link rel="icon" href={iconPath} />
|
||||
<meta name="description" content={description} />
|
||||
<meta name="generator" content="Quartz" />
|
||||
|
||||
@ -9,3 +9,4 @@ export { OxHugoFlavouredMarkdown } from "./oxhugofm"
|
||||
export { SyntaxHighlighting } from "./syntax"
|
||||
export { TableOfContents } from "./toc"
|
||||
export { HardLineBreaks } from "./linebreaks"
|
||||
export { OpenGraphImage } from "./opengraphImage"
|
||||
|
||||
99
quartz/plugins/transformers/opengraphImage.ts
Normal file
99
quartz/plugins/transformers/opengraphImage.ts
Normal file
@ -0,0 +1,99 @@
|
||||
import { Root as HTMLRoot } from "hast"
|
||||
import { visit } from "unist-util-visit"
|
||||
import { QuartzTransformerPlugin } from "../types"
|
||||
import { FilePath, joinSegments, pathToRoot, isRelativeURL } from "../../util/path"
|
||||
|
||||
export interface Options {
|
||||
defaultImage: string
|
||||
}
|
||||
|
||||
const defaultOptions: Options = {
|
||||
defaultImage: "/og-image.png",
|
||||
}
|
||||
|
||||
function coalesceImageAliases(data: { [key: string]: any }, aliases: string[]): string | undefined {
|
||||
for (const alias of aliases) {
|
||||
if (data[alias] !== undefined && data[alias] !== null && typeof data[alias] === "string") {
|
||||
return data[alias]
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
export const OpenGraphImage: QuartzTransformerPlugin<Partial<Options> | undefined> = (userOpts) => {
|
||||
const opts = { ...defaultOptions, ...userOpts }
|
||||
|
||||
return {
|
||||
name: "OpenGraphImage",
|
||||
htmlPlugins() {
|
||||
return [
|
||||
() => {
|
||||
return async (tree: HTMLRoot, file) => {
|
||||
let opengraphImage: string | undefined
|
||||
|
||||
// Priority 1: Check frontmatter for opengraph image
|
||||
const frontmatterImage = coalesceImageAliases(
|
||||
file.data.frontmatter || {},
|
||||
["opengraph_image", "og_image", "opengraphImage", "ogImage"]
|
||||
)
|
||||
|
||||
if (frontmatterImage) {
|
||||
opengraphImage = frontmatterImage
|
||||
} else {
|
||||
// Priority 2: Find first image in content
|
||||
let firstImage: string | undefined
|
||||
|
||||
visit(tree, "element", (node) => {
|
||||
if (firstImage) return // Already found one
|
||||
|
||||
if (node.tagName === "img" && node.properties?.src) {
|
||||
const src = node.properties.src.toString()
|
||||
firstImage = src
|
||||
}
|
||||
})
|
||||
|
||||
if (firstImage) {
|
||||
opengraphImage = firstImage
|
||||
} else {
|
||||
// Priority 3: Use default image
|
||||
opengraphImage = opts.defaultImage
|
||||
}
|
||||
}
|
||||
|
||||
// Convert relative URLs to absolute paths from site root
|
||||
if (opengraphImage && isRelativeURL(opengraphImage)) {
|
||||
// If the image starts with "/" it's already relative to root, keep as-is
|
||||
if (opengraphImage.startsWith("/")) {
|
||||
// Already absolute path from root
|
||||
opengraphImage = opengraphImage
|
||||
} else {
|
||||
// Relative path like "../assets/image.png" - convert to absolute
|
||||
// Images in content are typically in the assets directory
|
||||
if (opengraphImage.includes("../assets/")) {
|
||||
opengraphImage = opengraphImage.replace(/\.\.\/+assets\//, "/assets/")
|
||||
} else if (opengraphImage.includes("assets/")) {
|
||||
opengraphImage = "/" + opengraphImage.replace(/^\.\/+/, "")
|
||||
} else {
|
||||
// Other relative paths - assume they're relative to content root
|
||||
opengraphImage = "/" + opengraphImage.replace(/^\.\.\/+/, "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Store the opengraph image in file data for use by Head component
|
||||
file.data.opengraphImage = opengraphImage
|
||||
// Also set for Twitter cards (same image)
|
||||
file.data.twitterImage = opengraphImage
|
||||
}
|
||||
},
|
||||
]
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
declare module "vfile" {
|
||||
interface DataMap {
|
||||
opengraphImage: string
|
||||
twitterImage: string
|
||||
}
|
||||
}
|
||||
BIN
static/og-image.png
Normal file
BIN
static/og-image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
Loading…
Reference in New Issue
Block a user