Effet de trace lumineuse du pointeur des abysses
Réaliser un effet de traînée lumineuse du pointeur dans un décor de profonde mer : il comprend du contenu réel, des contrôles interactifs, un retour d’état clair et une mise en page adaptée aux différentes tailles.
Après activation de JavaScript, ce cas peut être modifié, exécuté et enregistré. Code source fixe ci-dessous :
<!doctype html>
<html lang="fr" dir="ltr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Effet de trace lumineuse du pointeur des abysses</title>
<style>
:root { --ink:#18332f; --paper:#f4efe4; --accent:#d45f3e; --secondary:#8fc4ad; --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:18px;border:3px solid var(--ink);background:var(--paper);box-shadow:8px 8px 0 var(--ink)}.stack{display:grid;gap:12px}.row{display:flex;flex-wrap:wrap;gap:8px}.action{min-height:42px;padding:8px 13px;border:3px solid var(--ink);background:var(--paper);font-weight:900}.action.primary,.action.is-active{color:var(--paper);background:var(--ink)}.status{min-height:24px;padding:8px;color:var(--paper);background:var(--ink);font-weight:900}.metric{font-size:clamp(34px,8vw,76px);font-weight:900;letter-spacing:-.08em}.muted{font-weight:600}.chip{padding:5px 8px;border:2px solid var(--ink);font-weight:900}.meter{height:14px;border:3px solid var(--ink)}.meter>i{display:block;height:100%;background:var(--ink)}
.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}}
.cursor-trail-lab{display:grid;grid-template-columns:minmax(0,1.42fr) minmax(220px,.58fr);gap:16px}.trail-stage{position:relative;min-height:450px;overflow:hidden;border:1px solid var(--line);border-radius:24px;background:#070b18;color:#fff}.trail-stage canvas{position:absolute;inset:0;z-index:2;width:100%;height:100%;touch-action:none}.trail-stage>div{position:relative;z-index:1;max-width:590px;padding:clamp(30px,7vw,78px)}.trail-stage h2{font-size:clamp(36px,7vw,78px);line-height:.94}.trail-controls{display:grid;align-content:center;gap:17px}.trail-controls label{display:grid;grid-template-columns:1fr auto;gap:8px;font-weight:800}.trail-controls input{grid-column:1/-1}.cursor-trail-lab[data-trail-layout="garden-orbit"]{grid-template-columns:1fr}.cursor-trail-lab[data-trail-layout="garden-orbit"] .trail-stage{min-height:520px;border-radius:50% 50% 28px 28px;background:radial-gradient(circle at 50% 58%,#31572c,#101d16 48%,#050907 72%)}.cursor-trail-lab[data-trail-layout="garden-orbit"] .trail-stage>div{margin-left:auto;text-align:right}.cursor-trail-lab[data-trail-layout="garden-orbit"] .trail-controls{grid-template-columns:auto minmax(160px,1fr) minmax(160px,1fr) auto;align-items:end;border-radius:999px}.cursor-trail-lab[data-trail-layout="garden-orbit"] .trail-controls h2{grid-column:1/-1}.cursor-trail-lab[data-trail-layout="aurora-band"]{grid-template-columns:minmax(220px,.55fr) minmax(0,1.45fr)}.cursor-trail-lab[data-trail-layout="aurora-band"] .trail-stage{order:2;min-height:360px;border-radius:2px;background:linear-gradient(160deg,#071a2b,#123c49 55%,#39165c)}.cursor-trail-lab[data-trail-layout="aurora-band"] .trail-stage>div{max-width:470px}.cursor-trail-lab[data-trail-layout="aurora-band"] .trail-controls{border-left:8px solid var(--accent)}@media(max-width:740px){.cursor-trail-lab,.cursor-trail-lab[data-trail-layout="garden-orbit"],.cursor-trail-lab[data-trail-layout="aurora-band"]{grid-template-columns:1fr}.trail-stage{min-height:350px}.cursor-trail-lab[data-trail-layout="garden-orbit"] .trail-stage{min-height:410px;border-radius:50%}.cursor-trail-lab[data-trail-layout="garden-orbit"] .trail-controls{order:-1;grid-template-columns:1fr;border-radius:28px}.cursor-trail-lab[data-trail-layout="aurora-band"] .trail-stage{order:0;min-height:320px}}.workflow-dispatch{display:grid;grid-template-columns:minmax(210px,.48fr) minmax(0,1.52fr);gap:18px}.dispatch-console{display:grid;align-content:start;gap:16px;padding:22px;border:1px solid var(--line);border-radius:18px;background:color-mix(in srgb,var(--secondary) 12%,var(--paper))}.dispatch-console h2{margin:.2em 0}.dispatch-console form,.dispatch-console label{display:grid;gap:7px}.dispatch-console input,.dispatch-console select{min-width:0;width:100%;padding:10px;border:1px solid var(--line);border-radius:8px;background:var(--paper)}.dispatch-console fieldset{display:flex;gap:12px;border:1px solid var(--line);border-radius:10px}.dispatch-console fieldset label{display:flex;align-items:center}.dispatch-workbench{--dispatch-load:.22;min-width:0;padding:14px;border-left:8px solid color-mix(in srgb,var(--accent) calc(var(--dispatch-load)*100%),var(--line));background:linear-gradient(90deg,color-mix(in srgb,var(--accent) 8%,transparent),transparent 28%)}.dispatch-workbench[data-dispatch-priority="urgent"]{box-shadow:inset 0 0 0 2px var(--accent)}@media(max-width:900px){.workflow-dispatch{grid-template-columns:1fr}.dispatch-console{order:2}}
</style>
</head>
<body data-aesthetic="dashboard" data-composition="field-index">
<main class="page">
<header class="mast"><div><span class="eyebrow">tableau de bord / effet de trace du pointeur / INTERMEDIATE</span><h1>Effet de trace lumineuse du pointeur des abysses</h1><p class="intro">Réaliser un effet de traînée lumineuse du pointeur dans un décor de profonde mer : il comprend du contenu réel, des contrôles interactifs, un retour d’état clair et une mise en page adaptée aux différentes tailles.</p></div><span class="scenario">Mer profonde</span></header>
<div class="composition-index"><section class="demo" aria-label="Zone interactive de l’effet de traînée du pointeur en haute mer"><div class="workflow-dispatch"><aside class="dispatch-console"><span class="eyebrow">DISPATCH des grands fonds</span><h2>Poste de planification de la mission de plongée</h2><form id="workflowDispatchForm"><label>Type de tâche<select id="workflowDispatchKind"><option>Balayage sonar</option><option>Collecte de sources chaudes</option><option>Balise de retour</option></select></label><label>Profondeur de plongée<input id="workflowDispatchAmount" type="number" min="1" max="9999" value="45"><span>m</span></label><fieldset><legend>Priorité de traitement</legend><label><input type="radio" name="workflowPriority" value="normal" checked>Standard</label><label><input type="radio" name="workflowPriority" value="urgent">Urgent</label></fieldset><button class="action primary" type="submit">Lancer la plongée</button></form><label>Tâche en cours<select id="workflowDispatchActive" aria-label="Basculer la tâche en cours"><option value="1">Balayage sonar</option><option value="2">Collecte de sources chaudes</option></select></label><output id="workflowDispatchSummary">En attente de planification</output></aside><section id="workflowCore" class="workflow-core dispatch-workbench" data-dispatch-priority="normal"><!-- 11DZ_CORE_START:creative-effects-cursor-trail --><div class="cursor-trail-lab" data-trail-layout="deep-current"><section class="trail-stage"><canvas id="trailCanvas" width="960" height="460" tabindex="0" aria-label="Toile de traînées lumineuses du pointeur en haute mer ; déplacez le pointeur ou utilisez les touches fléchées pour dessiner des traînées lumineuses" aria-describedby="trailHelp">Le navigateur actuel ne prend pas en charge Canvas.</canvas><div><span>Mer profonde</span><h2>Le courant emporte chaque trace de mouvement vers les profondeurs</h2><p id="trailHelp">Déplacez la souris, touchez la toile, ou, une fois au focus, utilisez les touches fléchées pour dessiner.</p></div></section><aside class="trail-controls panel"><span class="eyebrow">CURSOR TRAIL</span><h2>Traînée du courant des profondeurs</h2><label>Longueur de la traînée lumineuse <input id="trailLength" type="range" min="12" max="90" value="54" data-core-bridge="creative-effects-cursor-trail"><output id="trailLengthValue">54</output></label><label>Taille des points lumineux <input id="trailSize" type="range" min="3" max="18" value="9"><output id="trailSizeValue">9px</output></label><button id="trailClear" class="action" type="button">Effacer la trace lumineuse</button></aside></div><!-- 11DZ_CORE_END --></section></div><p id="status" class="status" role="status" aria-live="polite"></p></section><aside class="index-mark" aria-hidden="true"><strong>05</strong><span>Sous-marin profond FIELD INDEX</span></aside></div>
</main>
<script>
'use strict';
const statusOutput = document.querySelector('#status');
const announce = message => { statusOutput.textContent = message; };
/* 11DZ_CORE_SCRIPT_START:creative-effects-cursor-trail */
const trailCanvas=document.querySelector('#trailCanvas');const trailContext=trailCanvas.getContext('2d');const trailLength=document.querySelector('#trailLength');const trailSize=document.querySelector('#trailSize');const trailReduced=matchMedia('(prefers-reduced-motion: reduce)').matches;const trailHueOffset=0;let trailPoints=[];let trailCursor={x:0,y:0};let trailFrame=0;function sizeTrailCanvas(){const rect=trailCanvas.getBoundingClientRect();const ratio=Math.min(2,devicePixelRatio||1);trailCanvas.width=Math.max(320,Math.round(rect.width*ratio));trailCanvas.height=Math.max(280,Math.round(rect.height*ratio));trailContext.setTransform(ratio,0,0,ratio,0,0);trailCursor={x:rect.width/2,y:rect.height/2};}function addTrailPoint(x,y){trailCursor={x,y};trailPoints.push({x,y,life:1,hue:(trailHueOffset+trailPoints.length*17)%360});while(trailPoints.length>Number(trailLength.value))trailPoints.shift();cancelAnimationFrame(trailFrame);drawTrail();}function drawTrail(){const rect=trailCanvas.getBoundingClientRect();trailContext.clearRect(0,0,rect.width,rect.height);trailPoints.forEach((point,index)=>{if(!trailReduced)point.life-=.018;const ratio=(index+1)/trailPoints.length;trailContext.beginPath();trailContext.fillStyle='hsla('+point.hue+',90%,68%,'+Math.max(0,point.life*ratio)+')';trailContext.shadowBlur=18;trailContext.shadowColor='hsl('+point.hue+' 90% 68%)';trailContext.arc(point.x,point.y,Number(trailSize.value)*ratio,0,Math.PI*2);trailContext.fill();});trailContext.shadowBlur=0;trailPoints=trailPoints.filter(point=>point.life>0);if(trailPoints.length&&!trailReduced)trailFrame=requestAnimationFrame(drawTrail);}function pointerTrail(event){const rect=trailCanvas.getBoundingClientRect();addTrailPoint(event.clientX-rect.left,event.clientY-rect.top);}trailCanvas.addEventListener('pointermove',pointerTrail);trailCanvas.addEventListener('pointerdown',pointerTrail);trailCanvas.addEventListener('keydown',event=>{if(!['ArrowLeft','ArrowRight','ArrowUp','ArrowDown'].includes(event.key))return;event.preventDefault();const rect=trailCanvas.getBoundingClientRect();trailCursor.x=Math.max(0,Math.min(rect.width,trailCursor.x+(event.key==='ArrowRight'?18:event.key==='ArrowLeft'?-18:0)));trailCursor.y=Math.max(0,Math.min(rect.height,trailCursor.y+(event.key==='ArrowDown'?18:event.key==='ArrowUp'?-18:0)));addTrailPoint(trailCursor.x,trailCursor.y);});trailLength.addEventListener('input',()=>{document.querySelector('#trailLengthValue').textContent=trailLength.value;announce('Longueur de la traînée lumineuse '+trailLength.value);});trailSize.addEventListener('input',()=>{document.querySelector('#trailSizeValue').textContent=trailSize.value+'px';announce('Taille des points lumineux '+trailSize.value+' pixel');});document.querySelector('#trailClear').addEventListener('click',()=>{trailPoints=[];cancelAnimationFrame(trailFrame);drawTrail();announce('La trace du pointeur a été effacée');});addEventListener('resize',sizeTrailCanvas,{passive:true});sizeTrailCanvas();
/* 11DZ_CORE_SCRIPT_END */
const workflowBridgeSpec={"name":"creative-effects-cursor-trail","selector":"[data-core-bridge=\"creative-effects-cursor-trail\"]","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+' · Version de base '+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 workflowDispatchCore=document.querySelector('#workflowCore');const workflowDispatchForm=document.querySelector('#workflowDispatchForm');const workflowDispatchSelect=document.querySelector('#workflowDispatchActive');const workflowDispatchJobs=new Map([["1",{"label":"Balayage sonar","amount":12,"priority":"normal","touches":0}],["2",{"label":"Collecte de sources chaudes","amount":21,"priority":"urgent","touches":0}]]);let workflowDispatchSerial=workflowDispatchJobs.size;function workflowDispatchDrive(job,activate=false){workflowDispatchCore.dataset.dispatchPriority=job.priority;workflowDispatchCore.style.setProperty('--dispatch-load',Math.min(.95,.16+job.amount/120));const bridge=activate?workflowBridgeActivate():{changed:false,summary:workflowBridgeSummary()};document.querySelector('#workflowDispatchSummary').textContent=job.label+' · '+job.amount+' M '+bridge.summary;announce('Mission de plongée : tâche en cours :'+job.label);}function workflowDispatchRender(activeId,activate=false){workflowDispatchSelect.replaceChildren(...[...workflowDispatchJobs].map(([id,job])=>{const option=document.createElement('option');option.value=id;option.textContent=job.label+(job.priority==='urgent'?' · Urgent':'');return option;}));workflowDispatchSelect.value=activeId;workflowDispatchDrive(workflowDispatchJobs.get(activeId),activate);}workflowDispatchForm.addEventListener('submit',event=>{event.preventDefault();const data=new FormData(workflowDispatchForm);const id=String(++workflowDispatchSerial);workflowDispatchJobs.set(id,{label:document.querySelector('#workflowDispatchKind').value,amount:Math.max(1,Number(document.querySelector('#workflowDispatchAmount').value)||1),priority:data.get('workflowPriority'),touches:0});workflowDispatchRender(id,true);});workflowDispatchSelect.addEventListener('change',()=>workflowDispatchDrive(workflowDispatchJobs.get(workflowDispatchSelect.value),true));workflowDispatchCore.addEventListener('focusin',()=>{const job=workflowDispatchJobs.get(workflowDispatchSelect.value);if(!job)return;job.touches+=1;document.querySelector('#workflowDispatchSummary').textContent=job.label+' · '+job.amount+' M '+workflowBridgeSummary();});workflowDispatchRender('1',false);
workflowBridgeReset();
</script>
</body>
</html>