An Introduction to css-doodle

Yuan Chuan

How it begins

by Jan-Paul Koudstaal
<div class="grid">
  <div class="cell">
  <div class="cell">
  <div class="cell">
  <div class="cell">
  <!-- ... -->
</div>

<style>
  .cell:nth-child(1) { /* ... */ }
  .cell:nth-child(2) { /* ... */ }
  .cell:nth-child(3) { /* ... */ }
  .cell:nth-child(4) { /* ... */ }
  <!-- ... -->
</style>
@grid: 6x5 / 37vw auto / #f5f5f5; background: @p(#252386, #C92995, #91228C, #4C1981); border-radius: @p.cycle(100% 0 0 0);
<x-pattern>
  _.backgroundColor = color();
  _.borderRadius = rand(
    '100% 0 0 0',
    '0 100% 0 0',
    '0 0 100% 0',
    '0 0 0 100%'
  );
</x-pattern>
css-shapes-patterns
<css-doodle>
  @grid: 6x5 / 37vw auto / #f5f5f5;

  background: @p(
    #252386, #C92995, #91228C, #4C1981
  );

  border-radius: @p.cycle(
    100% 0 0 0
  );
</css-doodle>
@grid: 6x5 / 37vw auto / #f5f5f5; background: @p(#252386, #C92995, #91228C, #4C1981); border-radius: @p.cycle(100% 0 0 0);
<css-doodle>
  @grid: 6x5 / 37vw auto / #f5f5f5;

  background: @p(
    #252386, #C92995, #91228C, #4C1981
  );

  clip-path: circle(
    100% at @M2.p(0, 100%)
  );
</css-doodle>
@grid: 6x5 / 37vw auto / #f5f5f5; background: @p(#252386, #C92995, #91228C, #4C1981); clip-path: circle( 100% at @M2.p(0, 100%) );
<css-doodle>
  @grid: 6x5 / 37vw auto / #f5f5f5;

  background: radial-gradient(
    @p(#252386, #C92995, #91228C, #4C1981)
    70.7%, #0000 0
  );

  background-size: 200% 200%;
  background-position: @M2.p(-100%, 0);
</css-doodle>
@grid: 6x5 / 37vw auto / #f5f5f5; @seed: 1718218308200; background: radial-gradient( @p(#252386, #C92995, #91228C, #4C1981) 70.7%, #0000 0 ) @M2.p(-100%, 0) / 200% 200%;

css-doodle doesn't have a real
CSS parser

selector {
  property: value;
}

Selector

@col {}
@row {}
@at {}
@nth {}
@even {}
@odd {}
@random {}
@match {}
@hover {}

Property

@grid
@seed
@gap
@size
@place
@use
@shape
@content

Value

@i, @I, @x, @y, @X, @Y
@m, @M, @n, @nx, @ny, @N
@p, @P, @pl, @pr, @pd, @lp,
@r, @rn, @lr,
@svg, @svg-polygon,
@doodle, @shaders, @pattern
@mirror, @cycle, @reverse,
@plot, @shape
...
@grid: 5 / 32vw;
@content: @i;

@odd {
  background: #000;
  color: #fff;
}

@nth(13) {
  background: red;
  @shape: star;
}
@grid: 5 / 32vw; @content: @i; @odd { background: #000; color: #fff; } @nth(13) { background: red; @shape: star; }
@grid: 10 / 32vw no-clip;
background: #000;
margin: auto;

@size: calc(@i * 1%);

rotate: calc(360deg / @I * @i);
@grid: 10 / 32vw no-clip; background: #000; margin: auto; @size: calc(@i * 1%); rotate: calc(360deg / @I * @i);
@grid: 10 / 32vw no-clip;
background: #000;
margin: auto;

@size: calc(@i * 1%);
@size: @i(*1%);

rotate: calc(360deg / @I * @i); 
rotate: @iI(*360deg);
@grid: 10 / 32vw no-clip; background: #000; margin: auto; @size: calc(@i * 1%); @size: @i(*1%); rotate: calc(360deg / @I * @i); rotate: @iI(*360deg);

Function syntax

@i
@i()
@i(5)
@i(*10)
@i(10/)
@i(*10, -2, 48/)
calc(48 / (@i * 10 - 2))
@r
@r(1)
@r(0, 1)
@r255
@r.@r255
@r.r.r.r.r255
@r(@r(@r(@r(@r(255)))))
@grid: 2000x1 / 32vw;
@place: center;
@size: 4px;
border-radius: 50%;
background: #000;

--d: 1 - @r.r.r.r;

translate:
  $vw(15d * @cos.iI(*2π))
  $vw(15d * @sin.iI(*2π))
@grid: 2000x1 / 32vw; @size: 4px; border-radius: 50%; background: #000; @place: center; --d: 1 - @r.r.r.r1; translate: $vw(15d * @cos.iI(*2π)) $vw(15d * @sin.iI(*2π))

Random functions

@pick, @p
@P
@pl, @pn
@pr, @pnr
@pd
@grid: 5x1 / 320px auto / #999 border; background: #fff; @gap: 1px; @content: @p(1, 2, 3, 4, 5);
@p(1,2,3,4,5)
@grid: 5x1 / 320px auto / #999 border; background: #fff; @gap: 1px; @content: @p([1-5]);
@p([1-5])
@grid: 5x1 / 320px auto / #999 border; background: #fff; @gap: 1px; @content: @P([1-5]);
@P([1-5])
@grid: 5x1 / 320px auto / #999 border; background: #fff; @gap: 1px; @content: @pd([1-5]);
@pd([1-5])
@grid: 5x1 / 320px auto / #999 border; background: #fff; @gap: 1px; @content: @pl([1-5]);
@pl([1-5]) / @pn([1-5])
@grid: 5x1 / 320px auto / #999 border; background: #fff; @gap: 1px; @content: @pr([1-5]);
@pr([1-5])
@grid: 3x6 / 50vw auto;

--mode: @pl(
  normal, multiply, screen,
  overlay, darken, lighten,
  color-dodge, color-burn, hard-light,
  soft-light, difference, exclusion,
  hue, saturation, color,
  luminosity, plus-darker, plus-lighter
);

background: @doodle(
  @grid: 3x1;
  mix-blend-mode: @p(--mode);
  border-radius: 50%;
  background: @pl(red, blue, yellow);
  @place: @plot(r: .25);
  @size: 40%;
);

@content: @p(--mode);
place-content: end center;
font-size: .5em;
@grid: 6x3 / 50vw auto; --mode: @pl( normal, multiply, screen, overlay, darken, lighten, color-dodge, color-burn, hard-light, soft-light, difference, exclusion, hue, saturation, color, luminosity, plus-darker, plus-lighter ); background: @doodle( @grid: 3x1; mix-blend-mode: @p(--mode); border-radius: 50%; background: @pn(red, blue, yellow); @size: 40%; @place: @plot(r: .25); ); @content: @p(--mode); font-size: .5em; place-content: end center;
@r,
@R, @rn
@grid: 24 / 400px auto; background: #000; scale: @r(0, 1); rotate: @r(0, 1turn);
scale: @r(0, 1);
rotate: @r(0, 1turn);
@grid: 24 / 400px auto; background: #000; scale: @rn(0, 1); rotate: @rn(0, 1turn);
scale: @R(0, 1);
rotate: @R(0, 1turn);
@grid: 24 / 400px auto; background: #000; scale: @rn(0, 1, 3); rotate: @rn(0, 1turn, 3);
scale: @R(0, 1, 3);
rotate: @R(0, 1turn, 3);
@grid: 24 / 61.8vh 80vh / #f1fbff;
@seed: 1718264392842;

scale: @R(0, 2, 2);
rotate: @R(±30deg, 2);

--c: @p(#155674,#60beb3,#79f8bb,#f5ffae);

:after {
  content: @p('⬮', '.');
  z-index: 1;
  position: absolute;
  font-size: @R(4vmin);
  line-height: 0;
  color: @p(--c);
  scale: @R(.1, 2);
  text-shadow: @m2(@M2.r(±2vmin) 0 @p(--c));
}
@random(.05) {
  :after { color: #ff0000 }
}
@random(.3) {
  rotate: @R(±90deg, 2);
  :after {
    z-index: 0;
    @size: 1px @R(0, 30px, 3);
    background: linear-gradient(@p(--c) @R(50%), #0000);
  }
}
@random(.1) {
  :before {
    content: '.';
    position: absolute;
    color: @p(--color);
    opacity: @R1;
    text-shadow: @m24(@plot(r: @R(3em)) 0 currentColor);
  }
}
@grid: 24 / 61.8vh 80vh / #f1fbff; @seed: 1718264392842; scale: @R(0, 2, 2); rotate: @R(±30deg, 2); --c: @p(#155674,#60beb3,#79f8bb,#f5ffae); :after { content: @p('⬮', '.'); position: absolute; font-size: @R(4vmin); line-height: 0; color: @p(--c); scale: @R(.1, 2); z-index: 1; text-shadow: @m2(@M2.r(±2vmin) 0 @p(--c)); } @random(.05) { :after { color: #ff0000 } } @random(.3) { rotate: @R(±90deg, 2); :after { z-index: 0; @size: 1px @R(0, 40px, 3); background: linear-gradient(@p(--c) @R(50%), #0000); } } @random(.1) { :before { content: '.'; position: absolute; color: @p(--c); opacity: @R1; text-shadow: @m24(@plot(r: @R(3em)) 0 currentColor); } }

Random seed

<css-doodle seed="anything">
  @seed: anything;

  /* ... */
</css-doodle>
  

generator functions

@m
@M
@rep
@mirror
@Mirror
@cycle
@reverse
@grid: 1 / 320px 2em; @content: @m(10, 0); justify-self: start;
@m(10, 0)
@grid: 1 / 320px 2em; @content: @m(10, 0); justify-self: start;
@m10(0)
@grid: 1 / 320px 2em; @content: @M10(0); justify-self: start;
@M10(0)
@grid: 1 / 320px 2em; @content: @M10(@n); justify-self: start;
@M10(@n)
@grid: 1 / 320px 2em; @content: @M10.n; justify-self: start;
@M10.n
@grid: 1 / 320px 2em; @content: @M12-2.n; justify-self: start;
@M12-2.n
@grid: 1 / 320px 2em; @content: @mirror.m5.n; justify-self: start;
@mirror.m5.n
@grid: 1 / 320px 2em; @content: @Mirror.m5.n; justify-self: start;
@Mirror.m5.n
@grid: 1 / 320px 2em; :doodle { height: 5.1em; } @content: @cycle.m5.n; font-size: .75em; word-break: break-all; width: 7ch; justify-self: start;
@cycle.m5.n
@grid: 1 / 75vh;

background: @m100(
  conic-gradient(
    from @pd(±15, ±30, ±45, ±60, ±90)deg,
    @m100(red, blue, yellow)
  )
  @pl(0%, 100%) 100% / 2000% 2000%
  no-repeat
);

background-blend-mode: difference;
filter: invert(1) hue-rotate(220deg);
  
@grid: 1 / 75vh; background: @m100( conic-gradient( from @pd(±15, ±30, ±45, ±60, ±90)deg, @m100(red, blue, yellow) ) @pl(0%, 100%) 100% / 1500% 1500% no-repeat ); background-blend-mode: difference; filter: invert(1) hue-rotate(220deg);

background functions

background: @doodle()
background: @shaders()
background: @pattern()
background: @svg()
background: @svg-polygon()
background: @canvas()
  

CSS background is like a mirror or a digital screen, everything inside it is virtual and untouchable since there's no actual DOM inside there. But it gives us a window for imagination and a bridge to connect other things.

background: @doodle(
  @grid: 2x2;
  background: @p(red, blue);
)
@grid: @p(4, 5) / 75vh _1px;

--c: #FDF9EB, #BADFDB, #4ABFB8, #FF8B5C,
     #E63946, #F1FAEE, #A8DADC, #457B9D;

background: @p(@p(--c), @m2.doodle(
  @grid: @r4 _1px;
  background: @p(@p(--c), @m3.doodle(
    @grid: @r4 _1px;
    background: @p(@p(--c), @m2.doodle(
      @grid: @r4 _1px;
      background: @p(--c);
    ))
  ))
))
@grid: @p(4, 5) / 75vh _1px; --c: #FDF9EB, #BADFDB, #4ABFB8, #FF8B5C, #E63946, #F1FAEE, #A8DADC, #457B9D, #1D3557; background: @p(@p(--c), @m2.doodle( @grid: @r4 _1px; background: @p(@p(--c), @m3.doodle( @grid: @r4 _1px; background: @p(@p(--c), @m2.doodle( @grid: @r4 _1px; background: @p(--c); )); )); ));
@grid: 1 / 60vmin;

background: @shaders(
  void main() {
    vec2 p = gl_FragCoord.xy / u_resolution.xy;
    FragColor = vec4(p.xy, .8, 1.);
  }
)
@grid: 1 / 75vh; background: @shaders( void main() { vec2 p = gl_FragCoord.xy / u_resolution.xy; FragColor = vec4(p.xy, .8, 1.); } );
@grid: 1 / 75vh;

background: @pattern(
  grid: 71;
  fill: #333;
  match(((int(x*y*x*y*7.)>>4)&2) == 2) {
    fill: #fff;
  }
)
@grid: 1 / 75vh; background: @pattern( grid: 71; fill: #333; match(((int(x*y*x*y*7.)>>4)&2) == 2) { fill: #fff; } )
@grid: 1 / 75vh / #283944;

--c: #fff, #fb56ce, #88ffea, #ffd52d, #65b5b5;

background: @svg(
  viewBox: -50 -50 100 100;
  circle*20 {
    r: @nN(*30, 4);
    fill: none;
    stroke: @p(--c);
    stroke-linecap: round;
    stroke-width: @r(.5, 1.5);
    stroke-dasharray: @nN(*90, 10);
    transform: rotate(@nN(* -720));
  }
)
@grid: 1 / 75vh / #283944; --c: #fff, #fb56ce, #88ffea, #ffd52d, #65b5b5; background: @svg( viewBox: -50 -50 100 100; circle*20 { r: @nN(*32, 4); fill: none; stroke: @p(--c); stroke-linecap: round; stroke-width: @r(.5, 1.5); stroke-dasharray: @nN(*90, 10); transform: rotate(@nN(* -720)); } );

Experimenting A New Syntax To Write SVG

https://yuanchuan.dev/experimenting-a-new-syntax-to-write-svg

Shapes

@shape: heart;

/* or */
clip-path: @shape(heart);
    clip-path: @shape(

      fill:nonzero | evenodd;
      frame:number for frame size;
      points:number between 3 - 3600;
      rotate:number in degree for rotation;
      scale:number for scale factor;
      move:a pair of value for translating x, y coords;
      turn:angle between start/end point, defaults to be 1;
      x:x coordinate for cartesian equation;
      y:y coordinate for cartesian equation;
      r:polar equation;

    )
@grid: 1 / 200px; background: #000; clip-path: @shape( points: 360; scale: .5; x: cos(2t) + cos(7t); y: sin(2t) + sin(7t); fill: evenodd; )
points: 360;
scale: .5;
x: cos(2t) + cos(7t);
y: sin(2t) + sin(7t);
fill: evenodd;
@grid: 1 / 200px; background: #000; clip-path: @shape( points: 360; scale: .49; x: sin(5t) + sin(4t); -y: cos(4t) + cos(10t); )
points: 360;
scale: .49;
x: sin(5t) + sin(4t);
-y: cos(4t) + cos(10t);
@grid: 1 / 200px; background: #000; clip-path: @shape( points: 1000; scale: .061 .06; x: (11*cos(.6t) + cos(55t) - 2.8) * -1.81; -y: (11*sin(9.03t) - sin(77t)) * 2.5 + 17; )
points: 1000;
scale: .061 .06;
x: (11*cos(.6t) + cos(55t) - 2.8) * -1.81;
-y: (11*sin(9.03t) - sin(77t)) * 2.5 + 17; 
@grid: 1 / 200px; background: #000; clip-path: @shape( points: 480; scale: .3; move: 0 .35; x: sin(t) + sin(6t) + tan.sin(2t); y: cos(t) + cos(5t) + tan.cos(8t); )
points: 480;
scale: .3;
move: 0 .35;
x: sin(t) + sin(6t) + tan.sin(2t);
y: cos(t) + cos(5t) + tan.cos(8t);
  

Playground

https://css-doodle.com/shapes

Notes about shapes

https://yuanchuan.dev/polygon-shapes

@grid: 1 / 70vh border;
background: #000;

clip-path: @shape(
  points: 1000;
  scale: .8;
  move: .5 .64;
  x: cos(t^t) + cos(1.8^t);
  y: sin(t) + sin(2.305t)*sin(t);
)
@grid: 1 / 70vh border; background: #000; clip-path: @shape( points: 1000; scale: .8; move: .5 .64; x: cos(t^t) + cos(1.8^t); y: sin(t) + sin(2.305t)*sin(t); )

Inge Druckrey: Teaching to See

https://vimeo.com/45232468

Documentation

https://css-doodle.com

CodePen collection

https://codepen.io/collection/XyVkpQ/

Thank you