Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URLs added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Auto Save

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                body
  .container
    h1
      | Plotter Art Month
    p.
      This is my humble contribution to <a href="https://genuary.art/prompts" target="_blank">Genuary 2024</a>, tailored for a <a href="https://en.wikipedia.org/wiki/Plotter" target="_blank">pen plotter</a> context. I created <strong>one card per day</strong> for a month. There are 31 <strong>"day" cards</strong>, each representing a day's work, and about 10 <strong>"special series" cards</strong>. All of them are linked to a dedicated pen which is tweakable, feel free to explore.
    p.
      I mostly used <a href="https://github.com/sameer/svg2gcode" target="_blank">svg2gcode</a> for G-code generation. Here is the <a href="https://codepen.io/tfrere/pen/oNOKJwo?editors=0100" target="_blank">boilerplate</a> I used, along with some <a href="https://codepen.io/tfrere/pen/YzbXdBp?editors=0010" target="_blank">helpers</a> I created to manage all the cards. I also used my <a href="https://www.npmjs.com/package/@tfrere/generative-tools" target="_blank">generative-tools</a>, which is a fork of <a href="https://github.com/georgedoescode/generative-utils" target="_blank">georgedoescode generative utils</a>.
      If you want to use the boilerplate, here's a <a href="https://codepen.io/tfrere/pen/dyEowzo?editors=0010" target="_blank">2D base example</a> and a <a href="https://codepen.io/tfrere/pen/ExzYKvL" target="_blank">3D base example</a>. <br/><br/>
      All pens can also be found into these two collections: <a href="https://codepen.io/collection/QWPOzO" target="_blank">days</a> and <a href="https://codepen.io/collection/dbZkZg" target="_blank">miscs</a>. All the code is under MIT Licence.
    h2 Final result ↓
    img.main-img(src="https://assets.codepen.io/238916/board-x0.25.jpg")

    .mode-toggle-container
      button#mode-toggle-button 
        img#switch-mode-icon(
          src="https://assets.codepen.io/238916/switch-mode.svg"
        )
        svg#grid-icon(viewBox="0 0 24 24", width="24", height="24")
          path(
            d="M3 3h7v7H3V3zm11 0h7v7h-7V3zm-11 11h7v7H3v-7zm11 0h7v7h-7v-7z"
          )
        svg#list-icon(
          viewBox="0 0 24 24",
          width="24",
          height="24",
          style="display: none"
        )
          path(d="M3 13h18v-2H3v2zm0 6h18v-2H3v2zM3 7h18V5H3v2z")
    #card-container.card-container.grid-view
    #list-container.list-container
    footer.footer
      p.
        Made by <a href="https://tfrere.com" target="_blank">Thibaud frere</a>

              
            
!

CSS

              
                @import url("https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap");

h2 {
  margin: 40px 0;
}

body {
  display: flex;
  flex-direction: column;
  align-items: center;
  font-family: Arial, sans-serif;
  background-color: #f0f0f0;
  margin: 0;
  padding: 20px;
  font-family: "Poppins";
  margin-bottom: 100px;
  margin: 0 50px;
  margin-top: 100px;
}

#switch-mode-icon {
  position: absolute;
  width: 140px;
  opacity: 0.3;
  right: 60px;
  overflow: visible;
  object-fit: contain;
  top: 40px;
  transform: rotate(-3deg);
  transition: all 200ms ease;
}

#switch-mode-icon.hidden {
  opacity: 0;
}

footer {
  margin-top: 100px;
  //opacity: 0.1;
  margin-bottom: 100px;
}

.container {
  max-width: 760px;
  margin: 0 auto;
  padding: 0 20px;
}

.mode-toggle-container {
  width: 100%;
  display: flex;
  position: fixed;
  top: 20px;
  right: 20px;
  z-index: 999;
  justify-content: flex-end;
  margin-bottom: 20px;
}

#mode-toggle-button {
  padding: 10px 10px;
  height: 46px !important;
  border: 1px solid grey;
  border-radius: 3px;
  font-size: 16px;
  cursor: pointer;
}

#mode-toggle-button svg {
  margin: 0;
  padding: 0;
}

.card-container.grid-view {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
  width: 100%;
}

.grid-item--full {
  grid-column: 1 / -1;
  margin-top: 40px;
}

.list-container {
  display: none;
  width: 100%;
  flex-direction: column;
}

.main-img {
  width: 100%;
}

.card {
  width: 100%;
  height: 0;
  padding-bottom: calc(100% * 1.47); /* Maintain the 1.47 ratio */
  perspective: 1000px;
  position: relative;
}

.card-inner {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  text-align: center;
  transition: transform 0.6s;
  transform-style: preserve-3d;
}

.card:hover .card-inner {
  transform: rotateY(180deg);
}

.card-front,
.card-back {
  position: absolute;
  width: 100%;
  height: 100%;
  backface-visibility: hidden;
  border-radius: 12px;
  overflow: hidden;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}

a {
  color: black;
  font-weight: 700;
}

.card-front {
  display: flex;
  align-items: center;
  justify-content: center;
}

.card.card--black .card-front {
  background-color: black;
}

.card.card--white .card-front {
  background-color: white;
}

.card-back {
  background-color: white;
  color: black;
  transform: rotateY(180deg);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  padding: 0 15px;
  box-sizing: border-box;
}

.card.card--black .card-back {
  background-color: black;
  color: white;
}

h1 {
  font-size: 4rem;
}

h1 img {
  display: inline;
  width: 60px;
}

p {
  font-size: 1.1rem;
}

.card-back h3 {
  margin: 0;
  font-weight: 100;
  font-size: 3rem;
}

.card-back p {
  opacity: 0.3;
}

.card-front img {
  width: calc(100% + 4px); /* Increase the width to account for cropping */
  height: calc(100% + 4px); /* Increase the height to account for cropping */
  clip-path: inset(2px 2px 2px 2px);
  object-fit: cover; /* Ensures the image covers the area */
}

.list-container .list-item {
  display: flex;
  align-items: flex-start;
  //margin: 0;
  padding: 40px 0;
  border-bottom: 1px solid #ddd;
}

.list-container .list-item:first-of-type {
  padding-top: 20px;
}

.list-container .list-item__image-container {
  width: 33%; /* Ensure the image takes at least 1/3 of the space */
  margin-right: 40px;
  border-radius: 10px;
  overflow: hidden;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
  display: inline-flex;
  clip-path: none;
}

.list-container .list-item img {
  width: 100%;
  height: auto;
  object-fit: contain;
  margin: 0;
  padding: 0;
}

.list-container .list-item .details {
  flex: 1;
  position: relative;
}

.list-container .list-item h3 {
  margin: 0;
  font-size: 1.2rem;
  font-weight: 300;
  letter-spacing: 0.1rem;
  text-transform: uppercase;
}

.list-container .list-item h2 {
  margin: 5px 0;
  font-weight: 900;
  font-size: 1.5rem; /* Make the text larger */
}

.list-container .list-item p span {
  margin: 5px 0;
  font-weight: 100;
}

.list-container .list-item .keywords {
  display: flex;
  flex-wrap: wrap;
  margin-top: 20px;
}

.list-container .list-item .keywords span {
  margin-right: 10px;
  border: 1px solid #ddd;
  padding: 2px 5px;
  border-radius: 3px;
  margin-bottom: 3px;
}

@media (max-width: 650px) {
  .list-container .list-item {
    flex-direction: column;
    align-items: center;
    text-align: center;
  }

  .list-container .list-item ul {
    list-style-type: none;
  }

  .list-container .list-item .list-item__image-container {
    width: 60%;
    border-radius: 10px;
    margin: 0 0 20px 0;
  }

  .list-container .list-item h3 {
    position: static;
    margin-bottom: 10px;
  }

  .list-container .list-item .details {
    width: 100%;
  }

  .list-container .list-item .keywords {
    justify-content: center;
  }
}

ul {
  padding-left: 20px;
}

              
            
!

JS

              
                const romanNumerals = [
  {
    numeral: "I",
    title: "Particles. Lots of them.",
    url: "https://codepen.io/tfrere/pen/OJYLRRo",
    color: "black",
    description:
      "Generate <b>Perlin noise</b> and identify areas with brightness above 0.4 to create a <b>point cloud</b> with varying density. Distribute points according to the <b>Poisson distribution</b>.<br/><br/><b>Project improvements:</b> Boilerplate update and conduct initial printing tests.",
    keywords: ["perlin noise", "point cloud"]
  },
  {
    numeral: "II",
    title: "Recursion",
    color: "white",
    url: "https://codepen.io/tfrere/pen/ExzYpjG",
    description:
      "Take the largest <b>rectangle</b> (canvas) and randomly divide it horizontally or vertically, recursively up to 4 times. Add a random number of smaller rectangles with decreasing size and randomness.<br/><br/><b>Project improvements:</b> Select final paper and pens.",
    keywords: ["rectangle", "canvas"]
  },
  {
    numeral: "III",
    title: "Droste effect",
    url: "https://codepen.io/tfrere/pen/JjqjGpE",
    color: "black",
    description:
      "Repeat a <b>diamond shape</b> with linear changes in size and <b>rotation</b>. Adjust representation, size, scale, and base angle.<br/><br/><b>Project improvements:</b> Transforming the boilerplate into a well-structured JS class.",
    keywords: ["diamond shape", "rotation"]
  },
  {
    numeral: "IV",
    title: "Pixels",
    url: "https://codepen.io/tfrere/pen/bGyGMrx",
    color: "white",
    description:
      "Create a purely random <b>pixel grid</b> ensuring bi-axial <b>symmetry</b> and using three types of filling density.<br/><br/><b>Project improvements:</b> Add thick borders and finalize the aesthetic definition of a card.",
    keywords: ["random pixel grid", "symmetry"]
  },
  {
    numeral: "V",
    title: "Vera Molnar",
    color: "black",
    url: "https://codepen.io/tfrere/pen/dyEPXQy",
    description:
      "Make a <b>hand-drawn</b> squarish spiral and create a function to transform a path to look hand-drawn, ultimately using <b>rough.js</b>.<br/><br/><b>Project improvements:</b> Establish the first lines and rules of the project.",
    keywords: ["hand-drawn", "rough.js"]
  },
  {
    numeral: "VI",
    title: "Screensaver",
    color: "white",
    url: "https://codepen.io/tfrere/pen/MWdYoqG",
    description:
      "Draw inspiration from the 3D pipes screensaver aesthetic using simple <b>random walkers</b> placed on the <b>inner contour</b> of the canvas, allowing walkers to move randomly in four directions with a slight bias towards the center.<br/><br/><b>Project improvements:</b> Create base templates and improve the boilerplate.",
    keywords: ["random walkers", "inner contour"]
  },
  {
    numeral: "VII",
    title: "Motion",
    color: "black",
    url: "https://codepen.io/tfrere/pen/MWdYzqV",
    description:
      "Attempt to reproduce the <b>rotating snakes</b> optical illusion by filling areas with hatching lines at varying angles. Investigate why the plotter creates zigzags instead of hatches, and find an algorithm to apply hatching patterns within an SVG path.",
    keywords: ["rotating snakes", "optical illusion"]
  },
  {
    numeral: "VIII",
    title: "Chaotic system",
    color: "white",
    url: "https://codepen.io/tfrere/pen/VwOLPxO",
    description:
      "Simulate two inverted pendulums with <b>chaotic movement</b> and implement a 3D path simplification method.<br/><br/><b>Project improvements:</b> Add 3D mode to the boilerplate.",
    keywords: ["chaotic movement", "pendulums"]
  },
  {
    numeral: "IX",
    title: "ASCII",
    color: "black",
    url: "https://codepen.io/tfrere/pen/VwOvWWG",
    description:
      "Begin initial work on plotter and <b>typography</b> using <b>OpenType.js</b> to render typography and create a grid of characters sorted by luminance in a gradient.",
    keywords: ["opentype.js", "typography"]
  },
  {
    numeral: "X",
    title: "Hexagons",
    url: "https://codepen.io/tfrere/pen/vYwKdzG",
    color: "white",
    description:
      "Create a series of hexagons arranged in a <b>circle</b> and overlapping, repeat multiple layers along the <b>Y-axis</b>, and randomize final Y positions between -10 and 10.<br/><br/><b>Project improvements:</b> Explore Matt Deslaurier's boilerplate for further development.",
    keywords: ["circle", "y-axis"]
  },
  {
    numeral: "XI",
    title: "Anni Albers",
    url: "https://codepen.io/tfrere/pen/abrZKRr",
    color: "black",
    description:
      "Create an open <b>labyrinth</b> on the edges, focusing on creating continuous <b>paths</b> for humanization later.<br/><br/><b>Project improvements:</b> Now using svg2gcode from Sameer.",
    keywords: ["labyrinth", "paths"]
  },
  {
    numeral: "XII",
    title: "Lavalamp",
    url: "https://codepen.io/tfrere/pen/pombBgJ",
    color: "white",
    description:
      "Find a way to make an SVG lavalamp shape and implement a <b>ray marching</b> algorithm.",
    keywords: ["ray marching", "svg"]
  },
  {
    numeral: "XIII",
    title: "Wobble",
    url: "https://codepen.io/tfrere/pen/JjqEKaP",
    color: "black",
    description:
      "Create custom noise using <b>sine functions</b> and develop circles based on various sizes of circles using this <b>custom noise</b>.",
    keywords: ["custom noise", "sine functions"]
  },
  {
    numeral: "XIV",
    title: "1Ko",
    url: "https://codepen.io/tfrere/pen/xxNqXWP",
    color: "white",
    description:
      "Generate 1024 <b>random curves</b> and select a <b>visually pleasing</b> result.",
    keywords: ["random curves", "visually pleasing"]
  },
  {
    numeral: "XV",
    title: "Physics engine",
    url: "https://codepen.io/tfrere/pen/vYwxzLK",
    color: "black",
    description:
      "Simulate a <b>ball pit</b> with <b>matter.js</b> ensuring balls do not overlap and distribute as if piled up, then enhance with more shapes. <b>Project improvements:</b> Add an animation loop concept to the boilerplate.",
    keywords: ["ball pit", "matter.js"]
  },
  {
    numeral: "XVI",
    title: "10000",
    url: "https://codepen.io/tfrere/pen/MWdpzjv",
    color: "white",
    description:
      "Distribute 10,000 points in a <b>grid</b> influenced by a <b>sine wave</b>.",
    keywords: ["grid", "sine wave"]
  },
  {
    numeral: "XVII",
    title: "Islamic art inspired",
    url: "https://codepen.io/tfrere/pen/bGyWGYe",
    color: "black",
    description:
      "Create a circular <b>Penrose tiling</b> centered on the <b>canvas</b>.<br/><br/><b>Project improvements:</b> First repair on the pen plotter, a servomotor is dead.",
    keywords: ["penrose tiling", "canvas"]
  },
  {
    numeral: "XVIII",
    title: "Bauhaus",
    url: "https://codepen.io/tfrere/pen/wvbeqpe",
    color: "white",
    description: "Develop a classic <b>Bauhaus</b> style.",
    keywords: ["style", "classic"]
  },
  {
    numeral: "XIX",
    title: "Flocking",
    url: "https://codepen.io/tfrere/pen/JjqyYqB",
    color: "black",
    description:
      "Simulate a school of fish using a flocking algorithm biased by a <b>flow field</b>, representing them with little oriented arrows.",
    keywords: ["school of fish", "flow field"]
  },
  {
    numeral: "XX",
    title: "Generative typography",
    color: "white",
    url: "https://codepen.io/tfrere/pen/OJYxXvd",
    description:
      "Combine the ASCII card and the card with randomly moving <b>agents</b>. Add continuous movement for smoother lines, dropping random points on the letter contour and letting agents move while avoiding the letter and inner contours.",
    keywords: ["agents", "ascii"]
  },
  {
    numeral: "XXI",
    title: "Use an unknown library",
    url: "https://codepen.io/tfrere/pen/PovOoJB",
    color: "black",
    description:
      "Use <b>ln.js</b> as a replacement for the THREE.js SVGRenderer. Create a simple cube composed of <b>isometric cubes</b>, with many good alternate prints emerging.",
    keywords: ["ln.js", "isometric cubes"]
  },
  {
    numeral: "XXII",
    title: "Point, line, plane",
    url: "https://codepen.io/tfrere/pen/WNBXLPx",
    color: "white",
    description:
      "Use an image input and rasterize it in three passes, illustrating three black density passes with <b>points</b>, <b>lines</b>, and squares (planes).",
    keywords: ["points", "lines"]
  },
  {
    numeral: "XXIII",
    title: "16x16",
    url: "https://codepen.io/tfrere/pen/ExzoLvm",
    color: "black",
    description:
      "Cut a rectangle into 16 parts with <b>Voronoi</b>, place them in a physical simulation and let them settle on a floor. Deflate each polygon 16 times, making them progressively smaller.",
    keywords: ["voronoi", "polygons"]
  },
  {
    numeral: "XXIV",
    title: "Impossible geometry",
    url: "https://codepen.io/tfrere/pen/QWRaMgW",
    color: "white",
    description:
      "Create a generic code for an N-sided <b>Penrose polygon</b> and a Penrose triangle with a gradient based on a progressively dense <b>Poisson distribution</b>.",
    keywords: ["penrose polygon", "poisson distribution"]
  },
  {
    numeral: "XXV",
    title: "Try to recreate this with code",
    url: "https://codepen.io/tfrere/pen/WNBMyWj",
    color: "black",
    description:
      "Recreate a photo of an old stone wall with rounded stones embedded in each other and bound by cement. Use lots of points inside a rectangle then Voronoi cutting, finding an algorithm to make round corners to any polygon. Clip all the shapes inside a rectangle and fill the final shape with hatches.",
    keywords: ["stone wall", "voronoi"]
  },
  {
    numeral: "XXVI",
    title: "Grow a seed",
    url: "https://codepen.io/tfrere/pen/OJYQMNo",
    color: "white",
    description:
      "Implement an <b>L-system</b>, creating leaves on terminal branches and developing a visually pleasing result despite challenges.",
    keywords: ["l-system", "leaves"]
  },
  {
    numeral: "XXVII",
    title: "Code for one hour",
    url: "https://codepen.io/tfrere/pen/NWVyYLM",
    color: "black",
    description:
      "Load an image and place more points where the black intensity is greater. Use <b>Voronoi</b> relaxation on X frames for aesthetic enhancement.",
    keywords: ["voronoi", "image"]
  },
  {
    numeral: "XXVIII",
    title: "Neumorphism",
    url: "https://codepen.io/tfrere/pen/JjqLYKV",
    color: "white",
    description:
      "Simulate UI elements while remaining visually appealing with a <b>neumorphic style</b>. Make 3 primitives, then shadow them on a canvas. Take the final image to distribute points based on the grayscale value.",
    keywords: ["ui elements", "grayscale"]
  },
  {
    numeral: "XXIX",
    title: "Signed distance functions",
    url: "https://codepen.io/tfrere/pen/pomLmBe",
    color: "black",
    description:
      "Create two echoes of <b>circles</b> in the top left and bottom right using signed distance functions on a canvas, rendered using a grid of <b>pixels</b> with two different shapes, cross and squares.",
    keywords: ["circles", "pixels"]
  },
  {
    numeral: "XXX",
    title: "Shader",
    url: "https://codepen.io/tfrere/pen/mdYKPxG",
    color: "white",
    description:
      "Develop a kaleidoscope <b>shader</b>, draw some random black squares, raster image to <b>SVG</b> path, and fill the path.",
    keywords: ["kaleidoscope", "svg"]
  },
  {
    numeral: "XXXI",
    title: "Generative music",
    url: "https://codepen.io/tfrere/pen/pommNMb",
    color: "black",
    description:
      "Use 12 channels of a playing MP3 to influence successive <b>circles</b>, ensuring each circle is smaller than the previous with increasing musical impact. Remove the first two circles at the end due to excessive deformation.",
    keywords: ["mp3", "circles"]
  }
];

const specialCards = [
  {
    numeral: "I",
    title: "Cycloid",
    url: "https://codepen.io/tfrere/pen/BaedrQM",
    color: "white",
    description: "Implement a <b>cycloid</b>.",
    keywords: ["mathematics", "curve"]
  },
  {
    numeral: "II",
    title: "Lines",
    url: "https://codepen.io/tfrere/pen/vYwJNRQ",
    color: "black",
    description: "Implement a line influenced by a <b>sinusoid</b>.",
    keywords: ["sinusoid", "influence"]
  },
  {
    numeral: "III",
    title: "Fillpath",
    url: "https://codepen.io/tfrere/pen/MWdVwGo",
    color: "white",
    description: "Find a way to fill a <b>path</b>.",
    keywords: ["path", "fill"]
  },
  {
    numeral: "IV",
    title: "Quad tree",
    url: "https://codepen.io/tfrere/pen/ExzmMNb",
    color: "black",
    description: "Experiment with a <b>quad tree</b> data structure.",
    keywords: ["data structure", "experiment"]
  },
  {
    numeral: "V",
    title: "Moire",
    url: "https://codepen.io/tfrere/pen/VwOMzJZ",
    color: "white",
    description: "Implement a basic <b>moire pattern</b> concept.",
    keywords: ["pattern", "visual"]
  },
  {
    numeral: "VI",
    title: "Phyllotaxis",
    url: "https://codepen.io/tfrere/pen/WNBXpOQ",
    color: "black",
    description: "Implement a basic <b>phyllotaxis</b> algorithm.",
    keywords: ["algorithm", "nature"]
  },
  {
    numeral: "VII",
    title: "Flowfield",
    url: "https://codepen.io/tfrere/pen/JjqrMVx",
    color: "white",
    description: "Represent a basic <b>flowfield</b>.",
    keywords: ["representation", "vector"]
  },
  {
    numeral: "VIII",
    title: "Ln boolean",
    url: "https://codepen.io/tfrere/pen/ExzbNbM",
    color: "black",
    description:
      "Learn to use <b>ln.js</b> and apply a <b>boolean operator</b>.",
    keywords: ["ln.js", "boolean operator"]
  },
  {
    numeral: "IX",
    title: "Sun",
    url: "https://codepen.io/tfrere/pen/OJYzQBb",
    color: "white",
    description: "Experiment with creating a <b>sunset</b>.",
    keywords: ["sunset", "experiment"]
  },
  {
    numeral: "X",
    title: "Deflate",
    url: "https://codepen.io/tfrere/pen/gOJXvQm",
    color: "black",
    description: "Find a way to deflate <b>polygons</b>.",
    keywords: ["polygons", "geometry"]
  },
  {
    numeral: "XI",
    title: "Spirograph",
    url: "https://codepen.io/tfrere/pen/zYQRvwQ",
    color: "white",
    description: "Implement a basic <b>spirograph</b> concept.",
    keywords: ["pattern", "drawing"]
  }
];
const dayToDayTitleText = "Day to day ↓";
const specialEditionTitleText = "Special edition ↓";
const cardContainer = document.getElementById("card-container");
const listContainer = document.getElementById("list-container");
const modeToggleButton = document.getElementById("mode-toggle-button");
const gridIcon = document.getElementById("grid-icon");
const listIcon = document.getElementById("list-icon");
const switchModeIcon = document.getElementById("switch-mode-icon");

let isGridView = true;
let hasClickedToggleButton = false;

const createCard = (data, index, isSpecial = false) => {
  const card = document.createElement("div");
  card.classList.add(
    "card",
    data.color === "black" ? "card--black" : "card--white"
  );

  const cardInner = document.createElement("div");
  cardInner.classList.add("card-inner");

  const cardFront = document.createElement("div");
  cardFront.classList.add("card-front");
  //cardFront.style.backgroundColor = index % 2 === 0 ? "black" : "white";

  const svg = document.createElement("img");
  svg.src = `https://assets.codepen.io/238916/plotter-${
    isSpecial ? "special-edition-" : ""
  }${data.numeral}.svg`;
  svg.alt = data.numeral;
  cardFront.appendChild(svg);

  const cardBack = document.createElement("div");
  cardBack.classList.add("card-back");

  const title = document.createElement("h3");
  title.textContent = data.numeral;

  const text = document.createElement("p");
  text.textContent = data.title;

  cardBack.appendChild(title);
  cardBack.appendChild(text);

  cardInner.appendChild(cardFront);
  cardInner.appendChild(cardBack);
  card.appendChild(cardInner);

  card.addEventListener("click", () => window.open(data.url, "_blank"));
  card.style.cursor = "pointer";

  return card;
};

const createListItem = (data, isSpecial = false) => {
  const listItem = document.createElement("div");
  listItem.classList.add("list-item");

  const imageContainer = document.createElement("div");
  imageContainer.classList.add("list-item__image-container");

  const img = document.createElement("img");
  img.src = `https://assets.codepen.io/238916/plotter-${
    isSpecial ? "special-edition-" : ""
  }${data.numeral}.svg`;

  img.alt = data.title;

  imageContainer.appendChild(img);

  const details = document.createElement("div");
  details.classList.add("details");

  const title = document.createElement("h3");
  title.textContent = isSpecial
    ? `SPECIAL ${data.numeral}`
    : `Day ${data.numeral}`;

  const text = document.createElement("h2");
  text.textContent = data.title;

  const description = document.createElement("p");
  description.innerHTML = data.description;

  const keywordsTitle = document.createElement("h4");
  keywordsTitle.textContent = "Keywords";

  const link = document.createElement("a");
  link.href = data.url;
  link.textContent = "Go to pen";

  const keywordsList = document.createElement("div");
  keywordsList.classList.add("keywords");
  data.keywords.forEach((keyword) => {
    const span = document.createElement("span");
    span.textContent = keyword;
    keywordsList.appendChild(span);
  });

  details.appendChild(title);
  details.appendChild(text);
  details.appendChild(description);
  details.appendChild(keywordsList);

  listItem.appendChild(imageContainer);
  listItem.appendChild(details);

  return listItem;
};

const renderGridView = () => {
  cardContainer.innerHTML = "";

  const normalTitle = document.createElement("h2");
  normalTitle.classList.add("grid-item--full");
  normalTitle.textContent = dayToDayTitleText;
  cardContainer.appendChild(normalTitle);

  romanNumerals.forEach((data, index) => {
    cardContainer.appendChild(createCard(data, index));
  });

  const specialTitle = document.createElement("h2");
  specialTitle.classList.add("grid-item--full");
  specialTitle.textContent = specialEditionTitleText;
  cardContainer.appendChild(specialTitle);

  specialCards.forEach((data, index) => {
    cardContainer.appendChild(createCard(data, index, true));
  });
};

const renderListView = () => {
  listContainer.innerHTML = "";

  const normalTitle = document.createElement("h2");
  normalTitle.textContent = dayToDayTitleText;
  listContainer.appendChild(normalTitle);

  romanNumerals.forEach((data) => {
    listContainer.appendChild(createListItem(data));
  });

  const specialTitle = document.createElement("h2");
  specialTitle.textContent = specialEditionTitleText;
  listContainer.appendChild(specialTitle);

  specialCards.forEach((data) => {
    listContainer.appendChild(createListItem(data, true));
  });
};

const toggleViewMode = () => {
  isGridView = !isGridView;
  if (isGridView) {
    gridIcon.style.display = "block";
    listIcon.style.display = "none";
    listContainer.style.display = "none";
    cardContainer.style.display = "grid";
    renderGridView();
  } else {
    gridIcon.style.display = "none";
    listIcon.style.display = "block";
    cardContainer.style.display = "none";
    listContainer.style.display = "flex";
    renderListView();
  }
};

modeToggleButton.addEventListener("click", () => {
  toggleViewMode();
  hasClickedToggleButton = true;
  switchModeIcon.classList.add("hidden");
});

const handleScroll = () => {
  if (!hasClickedToggleButton) {
    switchModeIcon.classList.toggle("hidden", window.scrollY > 50);
  }
};

window.addEventListener("scroll", handleScroll);

// Initial render
toggleViewMode();
toggleViewMode();
renderGridView();
renderListView();

              
            
!
999px

Console