@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
JavaScript
/*!
* 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