{ CF }

11 CSS Page Transitions

Ink Bleed

A Canvas ink drop radiates organically from the centre with seismic roughness — like sumi ink spreading on wet paper, covering, then receding. Set in a Japanese ceramics studio.

CSS + JS MIT licensed

Ink Bleed the 7th of 11 designs in the 11 CSS Page Transitions collection. The design pairs CSS styling with a small amount of JavaScript for interactivity. Copy the HTML, CSS and JavaScript panels below into your project — the JS is self-contained, has zero dependencies, and is safe to drop into any framework (React, Vue, Svelte, plain HTML). The design honours prefers-reduced-motion and uses real semantic markup, so it ships accessibility-ready out of the box.

Live preview

This is a full-page demo — interact inside the frame above, or open it in the playground for the full-screen experience.

Open in playground

The code

<canvas id="inkCanvas"></canvas>

<div class="pages">

  <div class="page active" id="p0">
    <div class="p0-left">
      <div class="studio-mark">白泥窯 · Hakudei Kiln</div>
      <div class="kanji-bg">器</div>
      <div class="p0-hero">
        <div class="p0-tagline">Handmade · Raku · Seasonal</div>
        <h1 class="p0-title">Things made<br>to last</h1>
        <div class="p0-title-jp">一期一会の器</div>
        <p class="p0-body">Each piece is made once, in the way the clay demands. We do not repeat ourselves. We do not sell in quantity. We work with fire and patience.</p>
      </div>
      <div class="p0-footer">
        <div class="season-badge">Autumn 2025</div>
        <div style="font-size:11px;font-style:italic;color:var(--smoke)">Kyoto, Japan</div>
      </div>
    </div>
    <div class="p0-right">
      <div class="brush-art">
        <div class="bowl-art">
          <div class="ceramic-ring ring-1"></div>
          <div class="ceramic-ring ring-2"></div>
          <div class="ceramic-ring ring-3"></div>
          <div class="ring-inner"></div>
          <div class="stroke stroke-1"></div>
          <div class="stroke stroke-2"></div>
          <div class="stroke stroke-3"></div>
        </div>
      </div>
      <div class="p0-caption">茶碗 · Chawan · Tea Bowl · Shino glaze · 2025</div>
    </div>
  </div>

  <div class="page" id="p1">
    <div class="p1-bg">器</div>
    <div class="p1-eyebrow">Autumn Collection 2025</div>
    <h2 class="p1-title">New work<br>from the kiln</h2>
    <div class="p1-grid">
      <div class="ceramic-card">
        <div class="ceramic-thumb"><div class="c-shape"></div></div>
        <div class="ceramic-name">Shino Chawan</div>
        <div class="ceramic-series">Tea Series · No.01</div>
        <div class="ceramic-price">¥ 68,000</div>
      </div>
      <div class="ceramic-card">
        <div class="ceramic-thumb" style="aspect-ratio:1"><div class="c-shape" style="border-radius:50%"></div></div>
        <div class="ceramic-name">Ash Katakuchi</div>
        <div class="ceramic-series">Vessel Series · No.04</div>
        <div class="ceramic-price">¥ 42,000</div>
      </div>
      <div class="ceramic-card">
        <div class="ceramic-thumb"><div class="c-shape" style="height:100px;border-radius:20% 20% 50% 50%"></div></div>
        <div class="ceramic-name">Raku Guinomi</div>
        <div class="ceramic-series">Sake Series · No.07</div>
        <div class="ceramic-price">¥ 24,000</div>
      </div>
      <div class="ceramic-card">
        <div class="ceramic-thumb"><div class="c-shape" style="width:80px;height:60px;border-radius:50%"></div></div>
        <div class="ceramic-name">Oribe Hachi</div>
        <div class="ceramic-series">Bowl Series · No.02</div>
        <div class="ceramic-price">¥ 38,000</div>
      </div>
    </div>
  </div>

  <div class="page" id="p2">
    <div class="p2-left">
      <div class="p2-eyebrow">The Process</div>
      <h2 class="p2-title">Made by<br>hand and<br>fire</h2>
      <p class="p2-body">Nothing here is hurried. The kiln dictates the schedule, not the calendar. Each firing takes three days and reveals results that cannot be predicted or reproduced.</p>
      <div class="process-list">
        <div class="proc-item"><div class="proc-num">一</div><div class="proc-text">Clay reclaimed from mountain sources and aged for one year</div></div>
        <div class="proc-item"><div class="proc-num">二</div><div class="proc-text">Hand-thrown or slab-built, each piece unique in form</div></div>
        <div class="proc-item"><div class="proc-num">三</div><div class="proc-text">Wood-fired in anagama kiln at cone 12 over 60 hours</div></div>
        <div class="proc-item"><div class="proc-num">四</div><div class="proc-text">Unboxed by hand and inspected. Most pieces are not kept.</div></div>
      </div>
    </div>
    <div class="p2-right">
      <div class="form-lg"></div>
      <div class="form-sm"></div>
      <div style="font-size:10px;letter-spacing:0.2em;color:var(--smoke);text-align:center;font-style:italic">木灰釉薬 · Wood-ash glaze</div>
    </div>
  </div>

  <div class="page" id="p3">
    <div class="p3-bg"></div>
    <div class="p3-content">
      <div class="p3-jp">白泥窯 · 一九八九年創業</div>
      <h2 class="p3-title">Making is<br>a form of<br><em>listening</em></h2>
      <blockquote class="p3-quote">The clay knows what it wants to become. Our work is only to be present for long enough to hear it — and skilled enough to help it arrive.</blockquote>
      <div class="p3-meta">
        <div class="p3-meta-item"><div class="p3-meta-num">36</div><div class="p3-meta-label">Years at the kiln</div></div>
        <div class="p3-meta-item"><div class="p3-meta-num">4</div><div class="p3-meta-label">Annual firings</div></div>
        <div class="p3-meta-item"><div class="p3-meta-num">~60</div><div class="p3-meta-label">Pieces kept per year</div></div>
      </div>
    </div>
  </div>

</div>

<div class="dot-nav" id="dotNav">
  <button class="dot active" data-i="0"></button>
  <button class="dot" data-i="1"></button>
  <button class="dot" data-i="2"></button>
  <button class="dot" data-i="3"></button>
</div>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
:root{
  --ash:#e8e2d8;
  --pale:#f4f0ea;
  --clay:#c4a882;
  --smoke:#8a8070;
  --ink:#1a1410;
  --charcoal:#2a2420;
}
html,body{height:100%;overflow:hidden;background:var(--pale);font-family:'Cormorant',serif;color:var(--ink);}

/* Ink canvas overlay */
#inkCanvas{
  position:fixed;inset:0;z-index:800;
  pointer-events:none;
}

/* Pages */
.pages{position:fixed;inset:0;}
.page{
  position:absolute;inset:0;
  visibility:hidden;pointer-events:none;
}
.page.active{visibility:visible;pointer-events:auto;}

/* PAGE 0 — STUDIO */
#p0{
  background:var(--pale);
  display:grid;grid-template-columns:1fr 1fr;
}

.p0-left{
  padding:80px 70px;
  display:flex;flex-direction:column;justify-content:space-between;
  border-right:1px solid rgba(26,20,16,0.08);
  position:relative;
}

.studio-mark{
  font-family:'Noto Serif JP',serif;
  font-size:13px;letter-spacing:0.2em;
  color:rgba(26,20,16,0.3);
  font-weight:200;
}

.p0-hero{
  flex:1;
  display:flex;flex-direction:column;justify-content:flex-end;
  padding-bottom:20px;
}

.kanji-bg{
  position:absolute;
  font-family:'Noto Serif JP',serif;
  font-size:40vw;font-weight:200;
  color:rgba(26,20,16,0.025);
  line-height:1;
  top:50%;left:50%;
  transform:translate(-35%,-55%);
  pointer-events:none;
  user-select:none;
}

.p0-tagline{
  font-size:9px;letter-spacing:0.35em;text-transform:uppercase;
  color:var(--smoke);margin-bottom:20px;
}

.p0-title{
  font-size:clamp(48px,7vw,88px);
  font-weight:300;line-height:0.95;
  letter-spacing:-0.02em;
  color:var(--ink);
  margin-bottom:20px;
  position:relative;
}

.p0-title-jp{
  font-family:'Noto Serif JP',serif;
  font-size:11px;letter-spacing:0.2em;
  color:var(--clay);font-weight:300;
  margin-bottom:32px;
}

.p0-body{
  font-size:15px;line-height:1.8;font-weight:300;
  color:rgba(26,20,16,0.5);
  max-width:360px;font-style:italic;
}

.p0-footer{
  display:flex;align-items:center;justify-content:space-between;
}

.season-badge{
  font-size:9px;letter-spacing:0.25em;text-transform:uppercase;
  color:var(--clay);
  border:1px solid rgba(196,168,130,0.4);
  padding:6px 16px;
}

.p0-right{
  display:flex;flex-direction:column;
  background:var(--ash);
  position:relative;overflow:hidden;
}

/* Sumi-e brush artwork area */
.brush-art{
  flex:1;
  display:flex;align-items:center;justify-content:center;
  position:relative;
}

.bowl-art{
  position:relative;
  width:240px;height:240px;
}

.ceramic-ring{
  position:absolute;
  border-radius:50%;
  border:1px solid rgba(26,20,16,0.12);
}
.ring-1{inset:0;background:rgba(196,168,130,0.15);}
.ring-2{inset:16px;background:rgba(196,168,130,0.1);border-color:rgba(26,20,16,0.08);}
.ring-3{inset:40px;background:rgba(196,168,130,0.08);border-color:rgba(26,20,16,0.05);}
.ring-inner{
  position:absolute;inset:80px;
  border-radius:50%;
  background:var(--ink);opacity:0.06;
}

.stroke{
  position:absolute;
  background:rgba(26,20,16,0.12);
  border-radius:50% 20% 50% 20%;
  transform-origin:center;
}
.stroke-1{width:3px;height:80px;top:-40px;left:50%;transform:translateX(-50%) rotate(-10deg);}
.stroke-2{width:2px;height:60px;top:20px;right:20px;transform:rotate(35deg);opacity:0.6;}
.stroke-3{width:2px;height:40px;bottom:20px;left:24px;transform:rotate(-20deg);opacity:0.4;}

.p0-caption{
  padding:32px 48px;
  border-top:1px solid rgba(26,20,16,0.08);
  font-size:11px;letter-spacing:0.12em;
  color:rgba(26,20,16,0.35);
  text-align:center;font-style:italic;
}

/* PAGE 1 — COLLECTION */
#p1{
  background:var(--charcoal);
  color:var(--ash);
  display:flex;flex-direction:column;
  padding:80px;
  position:relative;
  overflow:hidden;
}

.p1-bg{
  position:absolute;
  font-family:'Noto Serif JP',serif;
  font-size:500px;font-weight:200;
  color:rgba(255,255,255,0.015);
  line-height:1;
  bottom:-100px;right:-60px;
  pointer-events:none;
}

.p1-eyebrow{
  font-size:9px;letter-spacing:0.35em;text-transform:uppercase;
  color:var(--clay);margin-bottom:40px;
  position:relative;z-index:1;
  display:flex;align-items:center;gap:12px;
}
.p1-eyebrow::after{
  content:'';flex:1;height:1px;
  background:rgba(196,168,130,0.2);
}

.p1-title{
  font-size:clamp(40px,6vw,72px);
  font-weight:300;color:var(--ash);
  letter-spacing:-0.02em;line-height:1.05;
  margin-bottom:60px;position:relative;z-index:1;
}

.p1-grid{
  display:grid;grid-template-columns:repeat(4,1fr);gap:24px;
  flex:1;align-content:start;
  position:relative;z-index:1;
}

.ceramic-card{
  display:flex;flex-direction:column;gap:12px;
  cursor:pointer;
  transition:transform .3s;
}
.ceramic-card:hover{transform:translateY(-4px);}

.ceramic-thumb{
  aspect-ratio:3/4;
  background:rgba(255,255,255,0.03);
  border:1px solid rgba(232,226,216,0.08);
  display:flex;align-items:center;justify-content:center;
  position:relative;overflow:hidden;
  transition:border-color .3s;
}
.ceramic-card:hover .ceramic-thumb{border-color:rgba(196,168,130,0.3);}

.c-shape{
  width:60px;height:80px;
  background:linear-gradient(160deg,rgba(196,168,130,0.3),rgba(196,168,130,0.1));
  border-radius:50% 50% 40% 40%;
  border:1px solid rgba(196,168,130,0.2);
  position:relative;
}
.c-shape::before{
  content:'';position:absolute;
  top:-8px;left:50%;transform:translateX(-50%);
  width:24px;height:12px;
  background:rgba(196,168,130,0.25);
  border-radius:50% 50% 0 0;
  border:1px solid rgba(196,168,130,0.2);
  border-bottom:none;
}

.ceramic-name{
  font-size:13px;font-weight:300;color:var(--ash);
  letter-spacing:0.04em;
}
.ceramic-series{
  font-size:9px;letter-spacing:0.2em;text-transform:uppercase;
  color:rgba(232,226,216,0.3);
}
.ceramic-price{
  font-size:11px;color:var(--clay);letter-spacing:0.08em;
}

/* PAGE 2 — PROCESS */
#p2{
  background:var(--pale);
  display:grid;grid-template-columns:480px 1fr;
}

.p2-left{
  padding:80px 70px;
  display:flex;flex-direction:column;justify-content:center;
  border-right:1px solid rgba(26,20,16,0.08);
}

.p2-eyebrow{
  font-size:9px;letter-spacing:0.35em;text-transform:uppercase;
  color:var(--clay);margin-bottom:28px;
}

.p2-title{
  font-size:clamp(36px,5vw,64px);
  font-weight:300;color:var(--ink);
  letter-spacing:-0.02em;line-height:1.05;
  margin-bottom:24px;
}

.p2-body{
  font-size:15px;line-height:1.85;font-weight:300;font-style:italic;
  color:rgba(26,20,16,0.5);margin-bottom:48px;
}

.process-list{
  display:flex;flex-direction:column;gap:20px;
}

.proc-item{
  display:flex;align-items:flex-start;gap:20px;
  padding-bottom:20px;
  border-bottom:1px solid rgba(26,20,16,0.06);
}
.proc-num{
  font-family:'Noto Serif JP',serif;
  font-size:9px;color:var(--clay);
  width:20px;flex-shrink:0;
  padding-top:2px;font-weight:300;
}
.proc-text{
  font-size:13px;line-height:1.7;
  color:rgba(26,20,16,0.5);letter-spacing:0.03em;
}

.p2-right{
  background:var(--ash);
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  padding:60px;
  position:relative;overflow:hidden;
  gap:48px;
}

.form-lg{
  width:160px;height:220px;
  background:linear-gradient(160deg,rgba(196,168,130,0.4),rgba(26,20,16,0.06));
  border-radius:50% 50% 40% 40%;
  border:1px solid rgba(196,168,130,0.3);
  box-shadow:12px 24px 40px rgba(26,20,16,0.08);
}
.form-sm{
  width:100px;height:60px;
  background:linear-gradient(160deg,rgba(196,168,130,0.3),rgba(26,20,16,0.04));
  border-radius:50%;
  border:1px solid rgba(196,168,130,0.2);
}

/* PAGE 3 — ABOUT */
#p3{
  background:var(--ink);
  color:var(--ash);
  display:flex;align-items:center;justify-content:center;
  padding:80px;
  position:relative;overflow:hidden;
}

.p3-bg{
  position:absolute;inset:0;
  background:radial-gradient(ellipse 60% 60% at 30% 50%,rgba(196,168,130,0.04),transparent);
}

.p3-content{max-width:640px;position:relative;}

.p3-jp{
  font-family:'Noto Serif JP',serif;
  font-size:11px;letter-spacing:0.3em;font-weight:200;
  color:rgba(196,168,130,0.5);margin-bottom:32px;
}

.p3-title{
  font-size:clamp(36px,6vw,72px);
  font-weight:300;color:var(--ash);
  line-height:1.05;letter-spacing:-0.02em;
  margin-bottom:32px;
}
.p3-title em{font-style:italic;color:var(--clay);}

.p3-quote{
  font-size:17px;line-height:1.8;font-style:italic;font-weight:300;
  color:rgba(232,226,216,0.55);
  border-left:2px solid rgba(196,168,130,0.3);
  padding-left:24px;margin-bottom:40px;
}

.p3-meta{
  display:flex;gap:48px;
  padding-top:32px;
  border-top:1px solid rgba(232,226,216,0.08);
}
.p3-meta-item{display:flex;flex-direction:column;gap:6px;}
.p3-meta-num{
  font-size:32px;font-weight:300;color:var(--clay);
}
.p3-meta-label{
  font-size:9px;letter-spacing:0.25em;text-transform:uppercase;
  color:rgba(232,226,216,0.25);
}

/* Nav */
.dot-nav{
  position:fixed;bottom:40px;left:50%;transform:translateX(-50%);
  z-index:900;display:flex;gap:10px;align-items:center;
}
.dot{
  width:5px;height:5px;border-radius:50%;
  background:rgba(26,20,16,0.2);border:none;cursor:pointer;
  transition:transform .35s,background .25s;
}
.dot.on-dark{background:rgba(232,226,216,0.2);}
.dot.active{transform:scale(1.8);background:var(--clay);}
.dot.on-dark.active{background:var(--clay);}

@media(max-width:768px){
  #p0,#p2{grid-template-columns:1fr;}
  .p0-right,.p2-right{display:none;}
  .p0-left,.p2-left,.p3-content{padding:48px 28px;}
  #p1{padding:48px 28px;}
  .p1-grid{grid-template-columns:1fr 1fr;}
  .p3-meta{gap:24px;}
}
const canvas=document.getElementById('inkCanvas');
const ctx=canvas.getContext('2d');
const pages=document.querySelectorAll('.page');
const dots=document.querySelectorAll('.dot');
const darkPages=[1,3];
let cur=0,busy=false;

function resize(){
  canvas.width=window.innerWidth;
  canvas.height=window.innerHeight;
}
resize();
window.addEventListener('resize',resize);

function inkTransition(x,y,color,midCallback,done){
  const W=canvas.width,H=canvas.height;
  const maxR=Math.sqrt(W*W+H*H)*0.6;
  let r=0,phase=0;
  const speed=28;
  const roughnessSeeds=Array.from({length:16},(_,i)=>({
    angle:i/16*Math.PI*2,
    amp:Math.random()*0.15+0.05,
    freq:Math.random()*3+2,
    phase:Math.random()*Math.PI*2
  }));

  function draw(){
    ctx.clearRect(0,0,W,H);
    if(phase===0&&r>maxR*0.45&&!midCallback._called){
      midCallback();midCallback._called=true;
    }

    if(phase===1&&r<=0){
      ctx.clearRect(0,0,W,H);done&&done();return;
    }

    ctx.beginPath();
    const steps=120;
    for(let i=0;i<=steps;i++){
      const a=i/steps*Math.PI*2;
      let rad=r;
      roughnessSeeds.forEach(s=>{
        rad+=r*s.amp*Math.sin(a*s.freq+s.phase+(phase===0?r*0.01:-r*0.01));
      });
      rad=Math.max(0,rad);
      const px=x+Math.cos(a)*rad;
      const py=y+Math.sin(a)*rad;
      i===0?ctx.moveTo(px,py):ctx.lineTo(px,py);
    }
    ctx.closePath();

    const g=ctx.createRadialGradient(x,y,0,x,y,r*1.1);
    g.addColorStop(0,color);
    g.addColorStop(0.7,color);
    g.addColorStop(1,'transparent');
    ctx.fillStyle=g;
    ctx.fill();

    ctx.beginPath();
    for(let i=0;i<=steps;i++){
      const a=i/steps*Math.PI*2;
      let rad=r;
      roughnessSeeds.forEach(s=>{rad+=r*s.amp*Math.sin(a*s.freq+s.phase+(phase===0?r*0.01:-r*0.01));});
      const edgeR=rad*0.92;
      const px=x+Math.cos(a)*edgeR;
      const py=y+Math.sin(a)*edgeR;
      i===0?ctx.moveTo(px,py):ctx.lineTo(px,py);
    }
    ctx.closePath();
    ctx.fillStyle='rgba(0,0,0,0.08)';
    ctx.fill();

    if(phase===0){
      r+=speed;
      if(r>=maxR){phase=1;}
    }else{
      r-=speed*1.4;
    }
    requestAnimationFrame(draw);
  }
  draw();
}

const inkColors=['#1a1410','#e8e2d8','#1a1410','#1a1410'];

function goTo(n,ex,ey){
  if(busy||n===cur)return;
  busy=true;
  const prev=cur;cur=n;

  const x=ex||window.innerWidth/2;
  const y=ey||window.innerHeight/2;
  const color=inkColors[cur];

  const mid=()=>{
    pages[prev].classList.remove('active');
    pages[cur].classList.add('active');
    dots.forEach((d,i)=>{
      d.classList.toggle('active',i===cur);
      d.classList.toggle('on-dark',darkPages.includes(cur));
    });
  };
  mid._called=false;
  inkTransition(x,y,color,mid,()=>{busy=false;});
}

dots.forEach(d=>d.addEventListener('click',e=>{
  const r=e.currentTarget.getBoundingClientRect();
  goTo(+d.dataset.i,r.left+r.width/2,r.top+r.height/2);
}));

let wt=0;
window.addEventListener('wheel',e=>{
  const now=Date.now();if(now-wt<1000)return;wt=now;
  goTo(Math.max(0,Math.min(3,cur+(e.deltaY>0?1:-1))),window.innerWidth/2,window.innerHeight/2);
},{passive:true});

document.addEventListener('keydown',e=>{
  const c=window.innerWidth/2,m=window.innerHeight/2;
  if(e.key==='ArrowRight')goTo(Math.min(3,cur+1),c,m);
  if(e.key==='ArrowLeft')goTo(Math.max(0,cur-1),c,m);
});

Search CodeFronts

Loading…