← Archive07 / 16
Lab noteNo. 07
100 Days of CSS
My version of the 100 Days of CSS challenge: one self-contained CSS piece a day.
- Made
- Oct 2023
- Type
- Challenge
- Stack
- CSS / Sass / Webpack

Why I built it
To sharpen my CSS through pure CSS art, animation, 3D transforms and SVG. I got through 82 of the 100 days, most of them between October and December 2023, and wrote a README for each one with the key concepts and what tripped me up.
What I learned
- One folder per day, with its styles, markup, an optional script and its notes, each wrapped in its own class (
.day-001) so styles never bleed across days. A Webpack setup loads every entry. - Stacking:
z-indexonly works on positioned elements, and pseudo-elements stack above their parent. To put one behind, I gave it its own stacking context and a negativez-index(day 1). - Restarting an animation: removing and re-adding a class doesn’t replay it, so I forced a reflow in between (day 2).
- Timing inside
@keyframes: percentages start each circle’s scale at a different point in one cycle, with noanimation-delay(day 4). - Transforms overwrite each other: a
scale()in an animation cancels thetranslate(-50%, -50%)used to centre. On day 26 I centred with negative margins instead, and left day 16’s workaround as it was, as a reminder of why. - SVG elements can’t have pseudo-elements, so the tooltips on day 5’s graph moved into one absolutely positioned div.
- Gradients don’t animate: a
conic-gradientspinner wouldn’t run, so I used an SVG stroke instead.outline-offsetgave me a dotted border I could push in or out (day 10). - Drawing SVG lines with
stroke-dasharrayandstroke-dashoffset, made simpler with thepathLengthattribute (days 22, 30 and 68). - Drawing with strokes: a stroke so thick it fills the circle, so
stroke-dasharrayandstroke-dashoffsetopen and close Pac-Man’s mouth (day 21). Text turned into outlines in Figma could then be drawn in too (day 34). - Placing SVG: a
<polygon>can’t be positioned on its own, so I wrapped one in three<svg>tags, rotated them and trimmed the corners withoverflow: hiddenfor a Penrose triangle (day 17). Leaving outviewBoxlet CSS do the scaling (day 24), and in a<path>, uppercase commands are absolute and lowercase ones relative (day 28). - Invisible space: a giant quote mark carried extra space above and below it until
line-height: 0madetop: 0mean the top (day 12). - Out of sight isn’t out of the way: hidden elements still caught clicks until I set
pointer-events: none(days 13, 24 and 39). - State without JavaScript: hidden radio and checkbox inputs, styled labels and the
~selector for tabs, toggles and buttons, including two labels for one checkbox (days 19, 20, 27, 36, 71 and 74). - One animation for many elements: an
--indexset from JavaScript, thencalc()for each element’s duration and delay. Variables inside@keyframesturned four animations into two, and negative delays start an element partway through (days 31, 39, 63 and 68). - Different transitions in and out: one sequence for opening and another for closing, plus removing the grey tap highlight on mobile (day 25).
- Flexbox beyond one row: an auto margin instead of a nested container (day 9), and
flex-basis: 100%to break a table onto its own row (day 62). - Forms: a custom file upload, number and range inputs styled across browsers, and
:validflipping a form over when the password matches (days 15, 32, 53 and 76). - SCSS from day 51:
@forloops that write a separate@keyframesfor each element, mixins with parameters,@function,@if/@else, and the modulo operator for alternating transitions (days 51 to 59). - 3D in practice:
perspectivesets how deep the space is, so a bigger value pushes things further away (day 14).preserve-3dkeeps children in their parent’s 3D space, andtranslateZ()pushes faces out along the depth axis to build a cube and a carousel (days 37 and 48).perspective()gives one element its own space (day 25), andbackface-visibilityhides the back of a flip card (days 14 and 76). - Learning 3D by taking it apart: on day 18 I couldn’t see how the flip worked, so I rebuilt it from the creator’s code and removed properties until I understood how it was happening. Days 57 and 58 had harder formulas, which I reverse-engineered to understand.