<div class="container">
<div class="preview">
<div class="text-display"><span>Hlop</span><sup>2</sup><span> x</span><sub>2</sub></div>
</div>
<div class="controls-container">
<div class="controls">
<div class="control-group">
<label>
Font Size: <span id="fontSizeValue">84</span>px
<input type="range" id="fontSizeSlider" min="12" max="168" value="84">
</label>
</div>
<div class="control-group">
<fieldset>
<legend>Scaling Type</legend>
<label class="radio">
<input type="radio" name="scalingType" value="fluid" checked>
Fluid
</label>
<label class="radio">
<input type="radio" name="scalingType" value="static">
Static
</label>
</fieldset>
<select id="scalingMode">
<option value="fluidLarger">Larger</option>
<option value="fluidDefault" selected>Default</option>
<option value="fluidSmaller">Smaller</option>
</select>
</div>
<div class="control-group">
<label>
Superscript Position: <span id="supPositionValue">-0.5</span>em
<input type="range" id="supPositionSlider" min="-1" max="0" value="-0.5" step="0.01">
</label>
</div>
<div class="control-group">
<label>
Subscript Position: <span id="subPositionValue">0.25</span>em
<input type="range" id="subPositionSlider" min="0" max="1" value="0.25" step="0.01">
</label>
</div>
</div>
<div class="css-display">
<h2>Current CSS</h2>
<pre><code id="currentCSS"></code></pre>
</div>
</div>
</div>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: system-ui, -apple-system, sans-serif;
line-height: 1.5;
padding: 20px;
background: #f5f5f5;
}
/* Default static styles for sup/sub */
sup, sub {
position: relative;
vertical-align: baseline;
}
.container {
max-width: 1000px;
margin: 0 auto;
display: flex;
flex-direction: column;
gap: 2rem;
}
.preview {
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
display: flex;
align-items: center;
justify-content: center;
min-height: 200px;
overflow: hidden;
}
.controls-container {
display: flex;
gap: 2rem;
}
.controls, .css-display {
background: white;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
flex: 1;
min-width: 0;
}
/* Stack vertically on mobile */
@media (max-width: 768px) {
.controls-container {
flex-direction: column;
}
}
.text-display {
font-family: "Times New Roman", serif;
line-height: 1.2;
color: #18181b;
}
.control-group {
margin-bottom: 1.5rem;
}
.control-group:last-child {
margin-bottom: 0;
}
label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
color: #18181b;
}
fieldset {
border: none;
padding: 0;
}
legend {
font-weight: 500;
margin-bottom: 0.5rem;
}
.radio {
display: inline-flex;
align-items: center;
margin-right: 1rem;
font-weight: normal;
}
.radio input {
margin-right: 0.5rem;
}
input[type="range"] {
width: 100%;
margin: 0.5rem 0;
}
select {
width: 100%;
padding: 0.5rem;
border: 1px solid #e4e4e7;
border-radius: 4px;
margin: 0.5rem 0;
background: white;
}
select:disabled {
background: #f5f5f5;
cursor: not-allowed;
}
h2 {
font-size: 1.125rem;
font-weight: 500;
margin-bottom: 1rem;
color: #18181b;
}
pre {
background: #f8f9fa;
padding: 1rem;
border-radius: 4px;
overflow-x: auto;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
font-size: 0.875rem;
line-height: 1.7;
}
// Constants
const BROWSER_SMALLER = 0.83;
const SCALING_OPTIONS = {
static: {
formula: "smaller",
multiplier: 1,
offset: 0
},
fluidLarger: {
formula: "calc(0.6em + 3px)",
multiplier: 1.66667,
offset: 3
},
fluidDefault: {
formula: "calc(0.5em + 4px)",
multiplier: 2,
offset: 4
},
fluidSmaller: {
formula: "calc(0.4em + 5px)",
multiplier: 2.5,
offset: 5
}
};
// Elements
const textDisplay = document.querySelector('.text-display');
const fontSizeSlider = document.getElementById('fontSizeSlider');
const fontSizeValue = document.getElementById('fontSizeValue');
const scalingTypeRadios = document.querySelectorAll('input[name="scalingType"]');
const scalingMode = document.getElementById('scalingMode');
const supPositionSlider = document.getElementById('supPositionSlider');
const supPositionValue = document.getElementById('supPositionValue');
const subPositionSlider = document.getElementById('subPositionSlider');
const subPositionValue = document.getElementById('subPositionValue');
const currentCSS = document.getElementById('currentCSS');
const sup = document.querySelector('sup');
const sub = document.querySelector('sub');
// Calculate top position for sup/sub elements
function calculateTopPosition(position, scaling) {
const option = SCALING_OPTIONS[scaling === 'static' ? 'static' : scalingMode.value];
if (scaling === 'static') {
return `${position}em`;
}
const scalingFactor = BROWSER_SMALLER * position * option.multiplier;
const emValue = Math.round(scalingFactor * 100) / 100;
const pxValue = Math.round(scalingFactor * option.offset * 100) / 100;
const operator = pxValue >= 0 ? '-' : '+';
const absolutePxValue = Math.abs(pxValue);
return `calc(${emValue}em ${operator} ${absolutePxValue}px)`;
}
// Generate CSS code
function generateCSS(scalingType) {
const option = SCALING_OPTIONS[scalingType === 'static' ? 'static' : scalingMode.value];
const supPosition = supPositionSlider.value;
const subPosition = subPositionSlider.value;
const formulaComment = scalingType === 'static' ? '' : `
/* Formula: Superscript/Subscript Position × Browser's default sup/sub scaling factor (${BROWSER_SMALLER}) × Parent Font Size (${option.multiplier} × (1em - ${option.offset}px)) */`;
return `sup, sub {
position: relative;
vertical-align: baseline;
}
sup {
font-size: ${option.formula};${formulaComment}
top: ${calculateTopPosition(supPosition, scalingType)};
}
sub {
font-size: ${option.formula};${formulaComment}
top: ${calculateTopPosition(subPosition, scalingType)};
}`;
}
// Update scaling mode dropdown based on scaling type
function updateScalingModeDropdown(scalingType) {
if (scalingType === 'static') {
scalingMode.innerHTML = '<option value="static">Default</option>';
scalingMode.disabled = true;
} else {
scalingMode.innerHTML = `
<option value="fluidLarger">Larger</option>
<option value="fluidDefault" selected>Default</option>
<option value="fluidSmaller">Smaller</option>
`;
scalingMode.disabled = false;
}
}
// Update display
function updateDisplay() {
const fontSize = fontSizeSlider.value;
const scalingType = document.querySelector('input[name="scalingType"]:checked').value;
const supPosition = supPositionSlider.value;
const subPosition = subPositionSlider.value;
// Update display values
fontSizeValue.textContent = fontSize;
supPositionValue.textContent = supPosition;
subPositionValue.textContent = subPosition;
// Update text display
textDisplay.style.fontSize = `${fontSize}px`;
// Update sup/sub styles
const option = SCALING_OPTIONS[scalingType === 'static' ? 'static' : scalingMode.value];
[sup, sub].forEach(elem => {
elem.style.fontSize = option.formula;
});
sup.style.top = calculateTopPosition(supPosition, scalingType);
sub.style.top = calculateTopPosition(subPosition, scalingType);
// Update CSS display
currentCSS.textContent = generateCSS(scalingType);
}
// Event listeners
[fontSizeSlider, scalingMode, supPositionSlider, subPositionSlider].forEach(
elem => elem.addEventListener('input', updateDisplay)
);
scalingTypeRadios.forEach(radio => {
radio.addEventListener('change', (e) => {
updateScalingModeDropdown(e.target.value);
updateDisplay();
});
});
// Initial update
updateDisplay();
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.