Typography and Text Effects

City Magazine Text Mask Effect

Complete a text masking effect for a city magazine scenario: includes real content, interactive controls, clear status feedback, and responsive layouts.

Typography and Text EffectsText mask effectHTMLCSS TypographyJavaScript
With JavaScript enabled, you can edit, run, and save this example. The fixed source is as follows:
<!doctype html>
<html lang="en" dir="ltr">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>City Magazine Text Mask Effect</title>
  <style>
    :root { --ink:#102c44; --paper:#eef6f8; --accent:#e05263; --secondary:#79b7c7; --line:color-mix(in srgb, var(--ink) 18%, transparent); --shadow:0 24px 60px color-mix(in srgb, var(--ink) 16%, transparent); color-scheme:light; }
    * { box-sizing:border-box; }
    html { min-width:320px; background:var(--paper); }
    body { min-height:100vh; margin:0; overflow-x:hidden; color:var(--ink); font-family:"IBM Plex Sans","PingFang SC",sans-serif; background:linear-gradient(color-mix(in srgb,var(--ink) 5%,transparent) 1px,transparent 1px),var(--paper);background-size:100% 36px; }
    button,input,select,textarea { color:inherit; font:inherit; }
    button,[role="button"],input,select,textarea { accent-color:var(--accent); }
    button { cursor:pointer; }
    button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible,[tabindex]:focus-visible { outline:3px solid var(--accent); outline-offset:3px; }
    [hidden] { display:none !important; }
    @media (max-width:700px) { .mast h1 { line-height:1.08 !important; letter-spacing:-.035em !important; overflow-wrap:anywhere; } }
    @media (prefers-reduced-motion:reduce) { *,*::before,*::after { scroll-behavior:auto !important; animation-duration:.01ms !important; animation-iteration-count:1 !important; transition-duration:.01ms !important; } }
    .page{width:min(1280px,calc(100% - 24px));margin:auto;padding:28px 0 64px}.mast{display:flex;justify-content:space-between;padding:18px;border:1px solid var(--line);background:color-mix(in srgb,#fff 60%,transparent);gap:18px;margin-bottom:14px}.mast h1{font-family:"IBM Plex Sans","PingFang SC",sans-serif;font-weight:800;font-size:clamp(34px,6vw,66px);letter-spacing:-.045em;margin:0;line-height:1}.eyebrow{display:block;margin-bottom:6px;color:var(--accent);font:800 10px/1 ui-monospace,monospace;letter-spacing:.16em}.intro{max-width:720px;margin:10px 0 0;font-size:14px;line-height:1.6}.scenario{padding:7px 10px;border:1px solid var(--line);border-radius:8px;background:var(--paper);font-size:12px}.demo{border-radius:14px;box-shadow:0 14px 40px color-mix(in srgb,var(--ink) 11%,transparent);position:relative;min-height:390px;overflow:hidden;padding:24px;background:color-mix(in srgb,var(--paper) 90%,#fff)}@media(max-width:700px){.page{width:calc(100% - 12px);padding-top:12px}.mast{display:grid}.demo{padding:12px}}
    .panel{padding:20px;border:1px dashed var(--secondary);border-radius:8px;background:color-mix(in srgb,var(--paper) 92%,#000)}.stack{display:grid;gap:10px}.row{display:flex;flex-wrap:wrap;gap:7px}.action{min-height:40px;padding:8px 12px;border:1px solid var(--secondary);border-radius:5px;color:var(--ink);background:transparent;font:700 12px/1 ui-monospace,monospace}.action.primary,.action.is-active{color:var(--paper);background:var(--accent)}.status{min-height:22px;color:var(--secondary);font:700 12px/1.5 ui-monospace,monospace}.status::before{content:"status: "}.metric{font:800 clamp(28px,6vw,56px)/1 ui-monospace,monospace}.muted{opacity:.64;font-family:ui-monospace,monospace}.meter{height:8px;background:repeating-linear-gradient(90deg,var(--line) 0 8px,transparent 8px 10px)}.meter>i{display:block;height:100%;background:var(--secondary)}.chip{padding:4px 7px;border:1px solid var(--secondary);font:700 10px/1 ui-monospace,monospace}
    .composition-index{position:relative;display:grid;grid-template-columns:minmax(0,1fr) auto;gap:18px}.index-mark{display:grid;align-content:space-between;width:76px;padding:16px 10px;border:1px solid var(--line);color:var(--accent);background:color-mix(in srgb,var(--paper) 82%,transparent);text-align:center}.index-mark strong{font:900 34px/1 ui-monospace,monospace}.index-mark span{writing-mode:vertical-rl;font:800 10px/1.2 ui-monospace,monospace;letter-spacing:.18em}.composition-index .demo{grid-column:1;grid-row:1}.composition-index::before{content:"";position:absolute;left:-12px;top:38px;width:5px;height:45%;background:linear-gradient(var(--accent),var(--secondary))}@media(max-width:700px){.composition-index{grid-template-columns:1fr}.index-mark{grid-row:1;width:auto;grid-template-columns:auto 1fr}.index-mark span{writing-mode:horizontal-tb}.composition-index .demo{grid-row:2}}
    .text-mask-lab{display:grid;gap:18px}.mask-controls{display:grid;grid-template-columns:minmax(180px,1fr) minmax(220px,1fr) auto;gap:18px;align-items:end}.mask-controls h2{margin:.3em 0 0}.mask-controls label{display:grid;grid-template-columns:1fr auto;gap:8px;font-weight:800}.mask-controls input{grid-column:1/-1;width:100%}.mask-stage{display:grid;align-content:center;min-height:390px;padding:clamp(28px,7vw,80px);overflow:hidden;border:1px solid var(--line);background:var(--ink);color:var(--paper)}.mask-stage p{letter-spacing:.18em}.mask-stage h3{--mask-angle:125deg;--mask-a:#ff7a59;--mask-b:#67e8f9;--mask-c:#f9e27d;max-width:15ch;margin:.2em 0;font-size:clamp(46px,10vw,118px);line-height:.86;color:transparent;background:linear-gradient(var(--mask-angle),var(--mask-a),var(--mask-b),var(--mask-c),var(--mask-a));background-size:220% 220%;background-clip:text;-webkit-background-clip:text;animation:maskFlow 6s linear infinite}.mask-stage h3.is-paused{animation-play-state:paused}.mask-stage small{max-width:58ch;line-height:1.6}@keyframes maskFlow{to{background-position:200% 200%}}@media(max-width:760px){.mask-controls{grid-template-columns:1fr}.mask-stage{min-height:320px}}.workflow-session{display:grid;gap:14px}.session-command{display:grid;grid-template-columns:1fr auto minmax(170px,.45fr) auto;gap:16px;align-items:end;padding:18px;border-block:4px double var(--ink)}.session-command h2{margin:.2em 0}.session-command output{font:900 clamp(28px,5vw,54px)/1 ui-monospace,monospace}.session-command label{display:grid;grid-template-columns:1fr auto;gap:7px;font-size:12px}.session-command input{grid-column:1/-1;width:100%}#workflowSessionProgress{height:12px;overflow:hidden;border-radius:2px;background:var(--line)}#workflowSessionProgress i{display:block;width:0;height:100%;background:var(--accent)}.session-stage{min-width:0;transition:filter .25s,transform .25s}.session-stage[data-session-phase="running"]{transform:translateY(-3px)}.session-stage[data-session-phase="paused"]{filter:grayscale(.65)}.session-stage[data-session-phase="complete"]{filter:contrast(.78);opacity:.72}.session-log{display:grid;grid-template-columns:auto 1fr;gap:18px;align-items:start}.session-log ol{display:flex;flex-wrap:wrap;gap:7px;padding:0;margin:0;list-style:none}.session-log li{padding:5px 8px;border:1px solid var(--line);font-size:12px}@media(max-width:900px){.session-stage{order:-1}.session-command{grid-template-columns:1fr auto}.session-command label{grid-column:1/-1}.session-log{grid-template-columns:1fr}}
  </style>
</head>
<body data-aesthetic="dashboard" data-composition="field-index">
  <main class="page">
    <header class="mast"><div><span class="eyebrow">dashboard / text mask effect / INTERMEDIATE</span><h1>City Magazine Text Mask Effect</h1><p class="intro">Complete a text masking effect for a city magazine scenario: includes real content, interactive controls, clear status feedback, and responsive layouts.</p></div><span class="scenario">City Magazine</span></header>
    <div class="composition-index"><section class="demo" aria-label="City magazine text mask effect interactivity area"><div class="workflow-session"><header class="session-command"><div><span class="eyebrow">City Magazine SESSION</span><h2>Magazine deadline timer</h2></div><output id="workflowSessionClock">00:45</output><label>Round Duration<input id="workflowSessionLength" type="range" min="15" max="90" step="15" value="45"><span>sec</span></label><button id="workflowSessionToggle" class="action primary" type="button">Start Copyediting</button></header><div id="workflowSessionProgress" role="progressbar" aria-label="Magazine deadline progress" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0"><i></i></div><section id="workflowCore" class="workflow-core session-stage" data-session-phase="ready" tabindex="-1"><!-- 11DZ_CORE_START:typography-text-text-mask --><div class="text-mask-lab"><header class="mask-controls panel"><div><span class="eyebrow">TEXT MASK</span><h2>Text mask effect</h2></div><label>Gradient angle <input id="maskAngle" type="range" min="0" max="360" value="125" data-core-bridge="typography-text-text-mask"><output id="maskAngleValue">125°</output></label><div class="row"><button id="maskPalette" class="action" type="button">Switch Color Spectrum</button><button id="maskPause" class="action" type="button" aria-pressed="false">Pause flow</button></div></header><section class="mask-stage" aria-label="City Magazine Text Mask Preview"><p>City Magazine</p><h3 id="maskedHeading">Let color flow only inside the text</h3><small>The original text remains in the document and can be selected, zoomed, and read by assistive technologies.</small></section></div><!-- 11DZ_CORE_END --></section><footer class="session-log"><strong id="workflowSessionScore">0 interaction checkpoints</strong><ol id="workflowSessionLaps" aria-label="Round Log"></ol></footer></div><p id="status" class="status" role="status" aria-live="polite"></p></section><aside class="index-mark" aria-hidden="true"><strong>05</strong><span>City Magazine FIELD INDEX</span></aside></div>
  </main>
  <script>
    'use strict';
    const statusOutput = document.querySelector('#status');
    const announce = message => { statusOutput.textContent = message; };
    /* 11DZ_CORE_SCRIPT_START:typography-text-text-mask */
const maskedHeading=document.querySelector('#maskedHeading');const maskAngle=document.querySelector('#maskAngle');let maskPaletteIndex=0;const maskPalettes=[['#ff7a59','#67e8f9','#f9e27d'],['#b8f2e6','#ffa69e','#faf3dd'],['#c4b5fd','#f0abfc','#93c5fd']];function renderMaskAngle(){maskedHeading.style.setProperty('--mask-angle',maskAngle.value+'deg');document.querySelector('#maskAngleValue').textContent=maskAngle.value+'°';announce('Gradient angle '+maskAngle.value+' degree');}maskAngle.addEventListener('input',renderMaskAngle);document.querySelector('#maskPalette').addEventListener('click',()=>{maskPaletteIndex=(maskPaletteIndex+1)%maskPalettes.length;maskPalettes[maskPaletteIndex].forEach((color,index)=>maskedHeading.style.setProperty('--mask-'+String.fromCharCode(97+index),color));announce('Text color spectrum switched');});document.querySelector('#maskPause').addEventListener('click',event=>{const paused=maskedHeading.classList.toggle('is-paused');event.currentTarget.textContent=paused?'Keep flowing':'Pause flow';event.currentTarget.setAttribute('aria-pressed',String(paused));announce(paused?'Text mask paused':'Text mask keeps flowing');});renderMaskAngle();
/* 11DZ_CORE_SCRIPT_END */
const workflowBridgeSpec={"name":"typography-text-text-mask","selector":"[data-core-bridge=\"typography-text-text-mask\"]","mode":"range","key":"ArrowRight"};const workflowBridgeCore=document.querySelector('#workflowCore');const workflowBridgeControl=workflowBridgeCore.querySelector(workflowBridgeSpec.selector);if(!workflowBridgeControl)throw new Error('Missing core bridge: '+workflowBridgeSpec.name);const workflowBridgeControlledId=workflowBridgeControl.getAttribute('aria-controls');const workflowBridgeControlled=workflowBridgeControlledId?document.getElementById(workflowBridgeControlledId):null;workflowBridgeCore.dataset.workflowBridge=workflowBridgeSpec.name;workflowBridgeCore.dataset.workflowVersion='0';function workflowBridgeSerialize(){const visualNode=workflowBridgeCore.firstElementChild||workflowBridgeCore;const visual=getComputedStyle(visualNode);const outputs=[...workflowBridgeCore.querySelectorAll('output,[role="status"],[aria-live],meter,progress')].map(node=>node.textContent.trim()+'|'+node.getAttribute('aria-valuenow')+'|'+node.getAttribute('value'));const canvas=[...workflowBridgeCore.querySelectorAll('canvas')].map(node=>{try{return node.toDataURL().slice(-160);}catch{return 'canvas-unavailable';}});return JSON.stringify({html:workflowBridgeCore.innerHTML,status:statusOutput.textContent,outputs,canvas,visual:[visual.display,visual.visibility,visual.opacity,visual.transform,visual.filter,visual.backgroundColor,visual.width,visual.height],scroll:workflowBridgeSpec.mode==='scroll'?workflowBridgeControl.scrollTop:null});}let workflowBridgeLastEffect=workflowBridgeSerialize();function workflowBridgeSummary(){const label=(workflowBridgeControl.getAttribute('aria-label')||workflowBridgeControl.textContent||workflowBridgeControl.value||workflowBridgeSpec.name).trim().replace(/\s+/g,' ').slice(0,72);return label+' · Core Version '+workflowBridgeCore.dataset.workflowVersion;}function workflowBridgeCommit(){const effect=workflowBridgeSerialize();if(effect===workflowBridgeLastEffect)return {changed:false,summary:workflowBridgeSummary()};workflowBridgeLastEffect=effect;workflowBridgeCore.dataset.workflowVersion=String(Number(workflowBridgeCore.dataset.workflowVersion||0)+1);return {changed:true,summary:workflowBridgeSummary()};}function workflowBridgeScheduleCommit(){queueMicrotask(workflowBridgeCommit);setTimeout(workflowBridgeCommit,0);}workflowBridgeControl.addEventListener("input",workflowBridgeScheduleCommit);workflowBridgeControl.addEventListener('change',workflowBridgeScheduleCommit);const workflowBridgeStateTargets=new Set(workflowBridgeCore.querySelectorAll('dialog'));if(workflowBridgeControlled&&workflowBridgeCore.contains(workflowBridgeControlled))workflowBridgeStateTargets.add(workflowBridgeControlled);workflowBridgeStateTargets.forEach(target=>['close','cancel','toggle'].forEach(eventName=>target.addEventListener(eventName,workflowBridgeScheduleCommit)));function workflowBridgeActivate(){workflowBridgeControl.focus();const step=Number(workflowBridgeControl.step)||1;const min=workflowBridgeControl.min===''?-Infinity:Number(workflowBridgeControl.min);const max=workflowBridgeControl.max===''?Infinity:Number(workflowBridgeControl.max);let next=Math.min(max,Math.max(min,(Number(workflowBridgeControl.value)||0)+step));if(next===Number(workflowBridgeControl.value))next=Math.max(min,Number(workflowBridgeControl.value)-step);workflowBridgeControl.value=String(next);workflowBridgeControl.dispatchEvent(new Event('input',{bubbles:true}));workflowBridgeControl.dispatchEvent(new Event('change',{bubbles:true}));const result=workflowBridgeCommit();setTimeout(workflowBridgeCommit,0);return result;}function workflowBridgeReset(){workflowBridgeCore.dataset.workflowVersion='0';workflowBridgeLastEffect=workflowBridgeSerialize();}
const workflowSessionCore=document.querySelector('#workflowCore');const workflowSessionToggle=document.querySelector('#workflowSessionToggle');const workflowSessionLength=document.querySelector('#workflowSessionLength');const workflowSessionState={phase:'ready',elapsed:0,limit:Number(workflowSessionLength.value),checkpoints:[],timer:null};function workflowSessionFormat(seconds){return String(Math.floor(seconds/60)).padStart(2,'0')+':'+String(seconds%60).padStart(2,'0');}function workflowSessionDrive(){const percent=Math.min(100,Math.round(workflowSessionState.elapsed/workflowSessionState.limit*100));document.querySelector('#workflowSessionClock').textContent=workflowSessionFormat(Math.max(0,workflowSessionState.limit-workflowSessionState.elapsed));const progress=document.querySelector('#workflowSessionProgress');progress.setAttribute('aria-valuenow',String(percent));progress.querySelector('i').style.width=percent+'%';workflowSessionCore.dataset.sessionPhase=workflowSessionState.phase;document.querySelector('#workflowSessionScore').textContent=workflowSessionState.checkpoints.length+' interactive checkpoints';}function workflowSessionLap(label){workflowSessionState.checkpoints.push({label,at:workflowSessionState.elapsed});const item=document.createElement('li');item.textContent=label+' · '+workflowSessionFormat(workflowSessionState.elapsed);document.querySelector('#workflowSessionLaps').append(item);workflowSessionDrive();}function workflowSessionStop(complete=false){clearInterval(workflowSessionState.timer);workflowSessionState.timer=null;workflowSessionState.phase=complete?'complete':'paused';workflowSessionToggle.textContent=complete?'Restart':'Continue round';workflowSessionToggle.setAttribute('aria-pressed','false');workflowSessionDrive();announce(complete?'Magazine closing round complete':'Round paused');}function workflowSessionStart(){if(workflowSessionState.phase==='complete'){workflowSessionState.elapsed=0;workflowSessionState.checkpoints=[];document.querySelector('#workflowSessionLaps').replaceChildren();}workflowSessionState.phase='running';workflowSessionToggle.textContent='Pause round';workflowSessionToggle.setAttribute('aria-pressed','true');const bridge=workflowBridgeActivate();workflowSessionLap(bridge.summary);clearInterval(workflowSessionState.timer);workflowSessionState.timer=setInterval(()=>{workflowSessionState.elapsed+=1;if(workflowSessionState.elapsed>=workflowSessionState.limit){workflowSessionStop(true);return;}workflowSessionDrive();},1000);workflowSessionDrive();announce('Magazine deadline round has started · '+bridge.summary);}workflowSessionToggle.addEventListener('click',()=>workflowSessionState.phase==='running'?workflowSessionStop():workflowSessionStart());workflowSessionLength.addEventListener('input',()=>{workflowSessionState.limit=Number(workflowSessionLength.value);workflowSessionState.elapsed=Math.min(workflowSessionState.elapsed,workflowSessionState.limit);workflowSessionDrive();});workflowSessionCore.addEventListener('keydown',event=>{if(event.key==='Enter'||event.key===' '){workflowSessionLap('Keyboard Interaction');}});workflowSessionCore.addEventListener('click',event=>{if(event.target.closest('[data-core-bridge]'))workflowSessionLap('Core action · '+workflowBridgeSummary());});document.addEventListener('visibilitychange',()=>{if(document.hidden&&workflowSessionState.phase==='running')workflowSessionStop();});workflowSessionDrive();
workflowBridgeReset();
  </script>
</body>
</html>
Page feedbackFound an issue or have suggestions for improvement?
Feedback features need to be used in the online version

Please enable JavaScript in the online version before submitting; local features of the current tool or example are not affected.

Go to the online version for feedback