Skip to main content
Glama
conversion.html14.7 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Unit Conversion</title> <style> :root { /* Colors */ --color-primary: hsl(220, 70%, 50%); --color-primary-light: hsl(220, 70%, 60%); --color-primary-dark: hsl(220, 70%, 40%); --color-primary-bg: hsla(220, 70%, 50%, 0.1); --color-success: hsl(120, 50%, 45%); --color-warning: hsl(45, 70%, 50%); --color-error: hsl(0, 60%, 50%); --color-text-primary: hsl(220, 10%, 20%); --color-text-secondary: hsl(220, 10%, 40%); --color-text-tertiary: hsl(220, 10%, 60%); --color-bg-primary: hsl(0, 0%, 100%); --color-bg-secondary: hsl(220, 15%, 96%); --color-bg-tertiary: hsl(220, 15%, 92%); --color-border: hsl(220, 10%, 85%); --color-border-light: hsl(220, 10%, 90%); /* Typography */ --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; --font-size-xs: 0.75rem; --font-size-sm: 0.875rem; --font-size-base: 1rem; --font-size-lg: 1.125rem; --font-size-xl: 1.25rem; --font-size-2xl: 1.5rem; --font-size-3xl: 1.875rem; --font-weight-normal: 400; --font-weight-medium: 500; --font-weight-semibold: 600; --font-weight-bold: 700; --line-height-tight: 1.25; --line-height-normal: 1.5; --line-height-relaxed: 1.75; /* Spacing */ --spacing-xs: 0.25rem; --spacing-sm: 0.5rem; --spacing-md: 1rem; --spacing-lg: 1.5rem; --spacing-xl: 2rem; --spacing-2xl: 3rem; /* Borders */ --border-radius-sm: 0.25rem; --border-radius-md: 0.375rem; --border-radius-lg: 0.5rem; --border-radius-xl: 0.75rem; --border-radius-full: 9999px; /* Shadows */ --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); /* Transitions */ --transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1); --transition-normal: 300ms cubic-bezier(0.4, 0, 0.2, 1); --transition-slow: 500ms cubic-bezier(0.4, 0, 0.2, 1); } * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: var(--font-family); font-size: var(--font-size-base); line-height: var(--line-height-normal); color: var(--color-text-primary); background-color: var(--color-bg-primary); margin: 0; padding: 0; display: flex; } .container { width: 100%; max-width: 100%; padding: var(--spacing-lg); display: flex; flex-direction: column; gap: var(--spacing-lg); } .units-container { display: flex; flex-direction: column; gap: var(--spacing-md); } .unit-row { display: flex; flex-direction: column; gap: var(--spacing-xs); } .unit-label { font-weight: var(--font-weight-medium); color: var(--color-text-secondary); font-size: var(--font-size-sm); } .unit-input { flex: 1; padding: var(--spacing-sm) var(--spacing-md); font-size: var(--font-size-lg); font-family: var(--font-family); border: 2px solid var(--color-border); border-radius: var(--border-radius-md); background-color: var(--color-bg-primary); color: var(--color-text-primary); transition: border-color var(--transition-fast), background-color var(--transition-fast); } .unit-input:focus { outline: none; border-color: var(--color-primary); background-color: var(--color-bg-primary); } .unit-input::-webkit-inner-spin-button, .unit-input::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; } .unit-input[type=number] { -moz-appearance: textfield; } @media (max-width: 480px) { .container { padding: var(--spacing-md); } } </style> </head> <body> <script> const ro = new ResizeObserver( ( entries ) => { for ( const entry of entries ) { window.parent.postMessage( { type: "ui-size-change", payload: { height: entry.contentRect.height } }, "*" ); } } ); ro.observe( document.documentElement ); </script> <div class="container"> <div class="units-container" id="unitsContainer"> <!-- Units will be dynamically inserted here --> </div> </div> <script> const TEMPLATE_CONFIG = { units: [ { id: 'ft', name: 'Feet', formulas: { in: '{ft} * 12', m: '{ft} * 0.3048', cm: '{ft} * 30.48' } }, { id: 'in', name: 'Inches', formulas: { ft: '{in} / 12', m: '{in} * 0.0254', cm: '{in} * 2.54' } }, { id: 'm', name: 'Meters', formulas: { ft: '{m} / 0.3048', in: '{m} / 0.0254', cm: '{m} * 100' } }, { id: 'cm', name: 'Centimeters', formulas: { ft: '{cm} / 30.48', in: '{cm} / 2.54', m: '{cm} / 100' } } ], initialValue: { id: 'ft', value: 1 } }; class UnitConverter { constructor( config ) { this.units = config.units; this.values = {}; this.container = document.getElementById( 'unitsContainer' ); this.updating = false; this.init( config.initialValue ); } init( initialValue ) { this.units.forEach( unit => { const row = document.createElement( 'div' ); row.className = 'unit-row'; const label = document.createElement( 'label' ); label.className = 'unit-label'; label.textContent = unit.name; label.htmlFor = `unit-${unit.id}`; const input = document.createElement( 'input' ); input.type = 'number'; input.className = 'unit-input'; input.id = `unit-${unit.id}`; input.step = 'any'; input.addEventListener( 'input', () => { if ( !this.updating ) { this.updateFromUnit( unit.id, parseFloat( input.value ) || 0 ); } } ); row.appendChild( label ); row.appendChild( input ); this.container.appendChild( row ); } ); // Set initial value if ( initialValue ) { this.updateFromUnit( initialValue.id, initialValue.value ); } } updateFromUnit( sourceId, value ) { this.updating = true; // Update the source unit this.values[sourceId] = value; document.getElementById( `unit-${sourceId}` ).value = value; // Find the source unit const sourceUnit = this.units.find( u => u.id === sourceId ); if ( !sourceUnit ) return; // Update all other units this.units.forEach( targetUnit => { if ( targetUnit.id === sourceId ) return; // Get the formula for converting from source to target const formula = sourceUnit.formulas[targetUnit.id]; if ( !formula ) return; // Replace the placeholder with the actual value const expression = formula.replace( `{${sourceId}}`, value ); try { // Safely evaluate the expression const result = this.evaluateExpression( expression ); this.values[targetUnit.id] = result; const input = document.getElementById( `unit-${targetUnit.id}` ); if ( input ) { // Format the result to avoid long decimals input.value = this.formatNumber( result ); } } catch ( e ) { console.error( `Error evaluating formula: ${formula}`, e ); } } ); this.updating = false; } evaluateExpression( expr ) { // Simple safe math expression evaluator // Only allows numbers, operators, and parentheses const cleaned = expr.replace( /[^0-9+\-*/().\s]/g, '' ); // Parse and evaluate the mathematical expression safely return this.parseExpression( cleaned ); } parseExpression( expr ) { // Remove whitespace expr = expr.replace( /\s+/g, '' ); // Parse the expression into tokens const tokens = this.tokenize( expr ); // Evaluate the tokens return this.evaluateTokens( tokens ); } tokenize( expr ) { const tokens = []; let i = 0; while ( i < expr.length ) { const char = expr[i]; if ( /[0-9.]/.test( char ) ) { // Parse number let num = ''; while ( i < expr.length && /[0-9.]/.test( expr[i] ) ) { num += expr[i]; i++; } tokens.push( { type: 'number', value: parseFloat( num ) } ); } else if ( /[+\-*/()]/.test( char ) ) { // Parse operator or parenthesis tokens.push( { type: 'operator', value: char } ); i++; } else { // Skip invalid characters i++; } } return tokens; } evaluateTokens( tokens ) { // Convert infix to postfix (Shunting Yard algorithm) const output = []; const operators = []; const precedence = { '+': 1, '-': 1, '*': 2, '/': 2 }; for ( const token of tokens ) { if ( token.type === 'number' ) { output.push( token.value ); } else if ( token.type === 'operator' ) { if ( token.value === '(' ) { operators.push( token.value ); } else if ( token.value === ')' ) { while ( operators.length && operators[operators.length - 1] !== '(' ) { output.push( operators.pop() ); } operators.pop(); // Remove the '(' } else { while ( operators.length && operators[operators.length - 1] !== '(' && precedence[operators[operators.length - 1]] >= precedence[token.value] ) { output.push( operators.pop() ); } operators.push( token.value ); } } } while ( operators.length ) { output.push( operators.pop() ); } // Evaluate postfix expression const stack = []; for ( const item of output ) { if ( typeof item === 'number' ) { stack.push( item ); } else { const b = stack.pop(); const a = stack.pop(); switch ( item ) { case '+': stack.push( a + b ); break; case '-': stack.push( a - b ); break; case '*': stack.push( a * b ); break; case '/': stack.push( a / b ); break; } } } return stack[0] || 0; } formatNumber( num ) { // Format number to reasonable decimal places if ( Math.abs( num ) < 0.01 ) { return num.toExponential( 2 ); } else if ( Math.abs( num ) < 1 ) { return num.toFixed( 4 ); } else if ( Math.abs( num ) < 100 ) { return num.toFixed( 2 ); } else { return num.toFixed( 0 ); } } } // Initialize the converter const converter = new UnitConverter( TEMPLATE_CONFIG ); </script> </body> </html>

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ref-tools/widget-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server