UNPKG

@sc4rfurryx/proteusjs

Version:

The Modern Web Development Framework for Accessible, Responsive, and High-Performance Applications. Intelligent container queries, fluid typography, WCAG compliance, and performance optimization.

169 lines (167 loc) 6.36 kB
/*! * ProteusJS v2.0.0 * Shape-shifting responsive design that adapts like the sea god himself * (c) 2025 sc4rfurry * Released under the MIT License */ /** * @sc4rfurryx/proteusjs/typography * Fluid typography with CSS-first approach * * @version 2.0.0 * @author sc4rfurry * @license MIT */ /** * Generate pure-CSS clamp() rules for fluid typography */ function fluidType(minRem, maxRem, options = {}) { const { minViewportPx = 320, maxViewportPx = 1200, lineHeight, containerUnits = false } = options; // Convert rem to px for calculations (assuming 16px base) const minPx = minRem * 16; const maxPx = maxRem * 16; // Calculate slope and y-intercept for linear interpolation const slope = (maxPx - minPx) / (maxViewportPx - minViewportPx); const yIntercept = minPx - slope * minViewportPx; // Generate clamp() function const viewportUnit = containerUnits ? 'cqw' : 'vw'; const clampValue = `clamp(${minRem}rem, ${yIntercept / 16}rem + ${slope * 100}${viewportUnit}, ${maxRem}rem)`; let css = `font-size: ${clampValue};`; // Add line-height if specified if (lineHeight) { css += `\nline-height: ${lineHeight};`; } return { css }; } /** * Apply fluid typography to elements */ function applyFluidType(selector, minRem, maxRem, options = {}) { const { css } = fluidType(minRem, maxRem, options); const styleElement = document.createElement('style'); styleElement.textContent = `${selector} {\n ${css.replace(/\n/g, '\n ')}\n}`; styleElement.setAttribute('data-proteus-typography', selector); document.head.appendChild(styleElement); } /** * Create a complete typographic scale */ function createTypographicScale(baseSize = 1, ratio = 1.25, steps = 6, options = {}) { const scale = {}; for (let i = -2; i <= steps - 3; i++) { const size = baseSize * Math.pow(ratio, i); const minSize = size * 0.8; // 20% smaller at min viewport const maxSize = size * 1.2; // 20% larger at max viewport const stepName = i <= 0 ? `small${Math.abs(i)}` : `large${i}`; scale[stepName] = fluidType(minSize, maxSize, options); } return scale; } /** * Generate CSS custom properties for a typographic scale */ function generateScaleCSS(scale, prefix = '--font-size') { const cssVars = Object.entries(scale) .map(([name, result]) => ` ${prefix}-${name}: ${result.css.replace('font-size: ', '').replace(';', '')};`) .join('\n'); return `:root {\n${cssVars}\n}`; } /** * Optimize line height for readability */ function optimizeLineHeight(fontSize, measure = 65) { // Optimal line height based on font size and measure (characters per line) // Smaller fonts need more line height, larger fonts need less const baseLineHeight = 1.4; const sizeAdjustment = Math.max(0.1, Math.min(0.3, (1 - fontSize) * 0.5)); const measureAdjustment = Math.max(-0.1, Math.min(0.1, (65 - measure) * 0.002)); return baseLineHeight + sizeAdjustment + measureAdjustment; } /** * Calculate optimal font size for container width */ function calculateOptimalSize(containerWidth, targetCharacters = 65, baseCharWidth = 0.5) { // Calculate font size to achieve target characters per line const optimalFontSize = containerWidth / (targetCharacters * baseCharWidth); // Clamp to reasonable bounds (12px to 24px) return Math.max(0.75, Math.min(1.5, optimalFontSize)); } /** * Apply responsive typography to an element */ function makeResponsive(target, options = {}) { const targetEl = typeof target === 'string' ? document.querySelector(target) : target; if (!targetEl) { throw new Error('Target element not found'); } const { minSize = 0.875, maxSize = 1.25, targetCharacters = 65, autoLineHeight = true } = options; // Apply fluid typography const { css } = fluidType(minSize, maxSize); const element = targetEl; // Parse and apply CSS const styles = css.split(';').filter(Boolean); styles.forEach(style => { const [property, value] = style.split(':').map(s => s.trim()); if (property && value) { element.style.setProperty(property, value); } }); // Auto line height if enabled if (autoLineHeight) { const updateLineHeight = () => { const computedStyle = getComputedStyle(element); const fontSize = parseFloat(computedStyle.fontSize); const containerWidth = element.getBoundingClientRect().width; const charactersPerLine = containerWidth / (fontSize * 0.5); const optimalLineHeight = optimizeLineHeight(fontSize / 16, charactersPerLine); element.style.lineHeight = optimalLineHeight.toString(); }; updateLineHeight(); // Update on resize if ('ResizeObserver' in window) { const resizeObserver = new ResizeObserver(updateLineHeight); resizeObserver.observe(element); // Store cleanup function element._proteusTypographyCleanup = () => { resizeObserver.disconnect(); }; } } } /** * Remove applied typography styles */ function cleanup(target) { if (target) { const targetEl = typeof target === 'string' ? document.querySelector(target) : target; if (targetEl && targetEl._proteusTypographyCleanup) { targetEl._proteusTypographyCleanup(); delete targetEl._proteusTypographyCleanup; } } else { // Remove all typography style elements const styleElements = document.querySelectorAll('style[data-proteus-typography]'); styleElements.forEach(element => element.remove()); } } /** * Check if container query units are supported */ function supportsContainerUnits() { return CSS.supports('width', '1cqw'); } // Export default object for convenience var index = { fluidType, applyFluidType, createTypographicScale, generateScaleCSS, optimizeLineHeight, calculateOptimalSize, makeResponsive, cleanup, supportsContainerUnits }; export { applyFluidType, calculateOptimalSize, cleanup, createTypographicScale, index as default, fluidType, generateScaleCSS, makeResponsive, optimizeLineHeight, supportsContainerUnits }; //# sourceMappingURL=typography.esm.js.map