cssAudio - Activefile-genericCSS - ActiveGeneric - ActiveHTML - ActiveImage - ActiveJS - ActiveSVG - ActiveText - Activefile-genericVideo - ActiveLovehtmlicon-new-collectionicon-personicon-teamlog-outoctocatpop-outspinnerstartv

Pen Settings

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

Quick-add: + add another resource

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.

Quick-add: + add another resource

Code Indentation

     

Save Automatically?

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.

            
              - var humpsCount = 10;
- var slicesPerHump = 9;
- var stemSegments = 16;

- var cursorSegmentsX = 11;
- var cursorSegmentsY = 11;



input(type="radio")(name="expression")(value="funny")(id="radio-expression-funny")(checked)
label(class="switch-face")(for="radio-expression-funny")
  span.icon 🎃
  span.label funny face

input(type="radio")(name="expression")(value="scary")(id="radio-expression-scary")
label(class="switch-face")(for="radio-expression-scary")
  span.icon 👻
  span.label scary face


.scene

  - for (var x = 1; x <= cursorSegmentsX; x++)
    - for (var y = 1; y <= cursorSegmentsY; y++)
      div(class="cursor-segment-" + x + "-" + y)(tabindex=-1)

  .scene-x
    .scene-y
      .scene-z

        .pumpkin
          .stem
            - for (var s = 0; s < stemSegments; s++)
              .stem-slice
          .body
            - for (var b = 0; b < humpsCount * slicesPerHump; b++)
              .body-slice
          .face
            - for (var x = 1; x <= 9; x++)
              .face-column
                - for (var y = 1; y <= 8; y++)
                  .face-segment

            
          
!
            
              // I wrote an article about it 
// https://codepen.io/ThiemelJiri/post/how-i-built-3d-css-only-halloween-jack-o-lantern-pumpkin
// --------------------------------------------------------------------------------------------
// Tested in: Chrome :), Firefox :), Safari :(, Opera :)
// --------------------------------------------------------------------------------------------



/////////////////////////////
//  Customizable settings  //
/////////////////////////////

$pumpkinWidth: 60vmin;
$pumpkinHeight: 45vmin;
$humpsCount: 10; // keep in sync with the same name variable in Pug
$slicesPerHump: 9; // keep in sync with the same name variable in Pug
$pumpkinColorLight: #FF7F00;
$pumpkinColorDark: darken(#FD5500, 5%);

$stemSegments: 16; // keep in sync with the same name variable in Pug
$stemHeight: 15vmin;
$stemTaper: 0.4;
$stemMove: 10vmin;
$stemColorLight: #6B6529;
$stemColorDark: #3C370F;

$faceColorLight: #f00;
$faceColorDark: #232526;

$cursorSegmentsX: 11; // keep in sync with the same name variable in Pug
$cursorSegmentsY: 11; // keep in sync with the same name variable in Pug
$sceneRotateX: 60deg;
$sceneRotateY: 60deg;
$backgroundColor: #232526;



//////////////////////////////////////////
//  Internal calculations and settings  //
//////////////////////////////////////////

$slicesCount: $humpsCount * $slicesPerHump;
$middleSlice: ceil($slicesPerHump/2);

$middleSegmentX: ceil($cursorSegmentsX / 2);
$middleSegmentY: ceil($cursorSegmentsY / 2);

$faceWidth: $pumpkinWidth * .6;
$faceHeight: $pumpkinHeight * .7;
$faceRadius: $pumpkinWidth / 2 + .6vmin;
$faceSegmentsX: 9;
$faceSegmentsY: 8;
$faceSegmentWidth: $faceWidth / $faceSegmentsX;
$faceSegmentHeight: $faceHeight / $faceSegmentsY;
$faceMiddleSegmentX: ceil($faceSegmentsX / 2);
$faceMiddleSegmentY: ceil($faceSegmentsY / 2);
$faceColumnRotation: asin($faceSegmentWidth / 2 / $faceRadius) * 2;
$faceSegmentRotation: asin($faceSegmentHeight / 2 / $faceRadius) * 2;
$faceTransformOriginZ: sqrt( pow($faceRadius, 2) - pow($faceSegmentWidth/2, 2) );

$faceMapFunny: (
  // eyes
  "2-2": (100% 0%, 100% 100%, 50% 100%),
  "2-3": (0% 0%, 25% 50%, 50% 100%, 0% 100%),
  "2-7": (75% 50%, 100% 0%, 100% 100%, 50% 100%),
  "2-8": (0% 0%, 50% 100%, 0% 100%),
  "3-2": (50% 0%, 100% 0%, 100% 100%, 0% 100%),
  "3-3": (0% 0%, 50% 0%, 100% 100%, 0% 100%),
  "3-7": (50% 0%, 100% 0%, 100% 100%, 0% 100%),
  "3-8": (0% 0%, 50% 0%, 100% 100%, 0% 100%),

  // nose
  "3-5": (50% 0%, 100% 100%, 0% 100%),
  "4-4": (100% 0%, 100% 100%, 50% 100%),
  "4-5": (0% 0%, 100% 0%, 100% 0%, 100% 100%, 50% 100%, 0% 100%, 0% 25%),
  "4-6": (0% 0%, 50% 100%, 0% 100%),
  "5-4": (50% 0%, 100% 0%, 100% 50%, 25% 50%),
  "5-5": (0% 0%, 100% 0%, 100% 50%, 0% 50%),
  "5-6": (0% 0%, 50% 0%, 75% 50%, 0% 50%),
  
  // mouth
  "5-1": (0% 0%, 100% 100%, 50% 100%),
  "5-9": (100% 0%, 50% 100%, 0% 100%),
  "6-1": (100% 0%, 100% 0%, 100% 100%, 100% 100%, 50% 0%),
  "6-2": (0% 0%, 100% 50%, 100% 100%, 0% 100%),
  "6-3": (100% 100%, 100% 100%, 100% 100%, 100% 100%),
  "6-4": (0% 80%, 100% 100%, 100% 100%, 0% 100%),
  "6-5": (100% 100%, 100% 100%, 0% 100%, 0% 100%, 50% 100%),
  "6-6": (100% 80%, 100% 100%, 0% 100%, 0% 100%),
  "6-7": (0% 80%, 100% 50%, 100% 100%, 0% 100%),
  "6-8": (100% 0%, 100% 100%, 0% 100%, 0% 50%),
  "6-9": (0% 0%, 50% 0%, 0% 100%, 0% 100%, 0% 0%),
  "7-1": (100% 0%, 100% 0%, 100% 0%),
  "7-2": (0% 0%, 100% 0%, 100% 100%, 0% 0%),
  "7-3": (0% 20%, 100% 20%, 100% 100%, 0% 100%),
  "7-4": (0% 0%, 100% 0%, 100% 100%, 0% 100%, 0% 100%),
  "7-5": (0% 0%, 100% 0%, 100% 100%, 0% 100%),
  "7-6": (0% 0%, 100% 0%, 100% 100%, 100% 100%, 0% 100%),
  "7-7": (0% 0%, 100% 0%, 100% 100%, 0% 100%),
  "7-8": (0% 0%, 100% 0%, 100% 0%, 0% 100%),
  "7-9": (0% 0%, 0% 0%, 0% 0%),
  "8-3": (0% 0%, 100% 0%, 100% 70%, 50% 35%),
  "8-4": (0% 0%, 100% 0%, 100% 100%, 0% 70%),
  "8-5": (0% 0%, 100% 0%, 100% 100%, 50% 100%, 0% 100%),
  "8-6": (0% 0%, 0% 0%, 0% 0%),
  "8-7": (0% 0%, 100% 0%, 50% 35%, 0% 70%),
);

$faceMapScary: (
  // left eye
  "2-2": (100% 0%, 100% 100%, 33.3% 100%),
  "2-3": (0% 0%, 100% 50%, 100% 100%, 0% 100%),
  "2-4": (0% 50%, 50% 75%, 100% 100%, 0% 100%),
  "3-2": (33.3% 0%, 100% 0%, 100% 33.3%, 0% 50%),
  "3-3": (0% 0%, 100% 0%, 100% 16.7%, 0% 33.3%),
  "3-4": (0% 0%, 100% 0%, 50% 8.35%, 0% 16.7%),

  // right eye
  "2-6": (50% 75%, 100% 50%, 100% 100%, 0% 100%),
  "2-7": (0% 50%, 100% 0%, 100% 100%, 0% 100%),
  "2-8": (0% 0%, 66.7% 100%, 0% 100%),
  "3-6": (0% 0%, 100% 0%, 100% 16.7%, 50% 8.35%),
  "3-7": (0% 0%, 100% 0%, 100% 33.3%, 0% 16.7%),
  "3-8": (0% 0%, 66.7% 0%, 100% 50%, 0% 33.3%),
  
  // nose
  "3-5": (50% 50%, 83.3% 100%, 16.7% 100%),
  "4-4": (100% 25%, 100% 87.5%, 50% 100%),
  "4-5": (16.7% 0%, 83.3% 0%, 100% 25%, 100% 87.5%, 50% 75%, 0% 87.5%, 0% 25%),
  "4-6": (0% 25%, 50% 100%, 0% 87.5%),
  
  // mouth
  "5-1": (0% 0%, 66.5% 100%, 37.2% 100%),
  "5-9": (100% 0%, 62.8% 100%, 33.5% 100%),
  "6-1": (66.5% 0%, 100% 50%, 100% 100%, 74.4% 100%, 37.2% 0%),
  "6-2": (0% 50%, 50% 75%, 100% 100%, 0% 100%),
  "6-3": (50% 50%, 100% 0%, 100% 100%, 0% 100%),
  "6-4": (0% 0%, 100% 64.2%, 100% 100%, 0% 100%),
  "6-5": (100% 64.2%, 100% 100%, 0% 100%, 0% 64.2%, 50% 100%),
  "6-6": (100% 0%, 100% 100%, 0% 100%, 0% 64.2%),
  "6-7": (0% 0%, 100% 100%, 100% 100%, 0% 100%),
  "6-8": (100% 50%, 100% 100%, 0% 100%, 50% 75%),
  "6-9": (33.5% 0%, 62.8% 0%, 25.6% 100%, 0% 100%, 0% 50%),
  "7-1": (74.4% 0%, 100% 0%, 100% 68.8%),
  "7-2": (0% 0%, 100% 0%, 100% 60.8%, 0% 68.8%),
  "7-3": (0% 0%, 100% 0%, 100% 52.8%, 0% 60.8%),
  "7-4": (0% 0%, 100% 0%, 100% 100%, 71.8% 100%, 0% 52.8%),
  "7-5": (0% 0%, 100% 0%, 100% 100%, 0% 100%),
  "7-6": (0% 0%, 100% 0%, 100% 52.8%, 28.2% 100%, 0% 100%),
  "7-7": (0% 0%, 100% 0%, 100% 60.8%, 0% 52.8%),
  "7-8": (0% 0%, 100% 0%, 100% 68.8%, 0% 60.8%),
  "7-9": (0% 0%, 25.6% 0%, 0% 68.8%),
  "8-4": (71.8% 0%, 100% 0%, 100% 18.5%, 71.8% 0%),
  "8-5": (0% 0%, 100% 0%, 100% 18.5%, 50% 51.4%, 0% 18.5%),
  "8-6": (0% 0%, 28.2% 0%, 0% 18.5%),
);

:root {
  --rotationX: 0deg;
  --rotationY: 12deg;
  --face-color: mix($faceColorLight, $faceColorDark, 65%);
}



////////////////////
//  Pumpkin body  //
////////////////////

.pumpkin {
  position: absolute;
  width: $pumpkinWidth;
  height: $pumpkinHeight;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  margin: auto;
  transform: translateY(10%);
  transform-style: preserve-3d;
}

.body {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  transform-style: preserve-3d;
  backface-visibility: hidden;
}

.body-slice {
  position: absolute;
  left: 50%;
  width: 50%;
  height: 100%;
  transform-origin: 0 66%;
  
  &::before {
    content: '';
    display: block;
    width: 100%;
    height: 100%;
    border-top-left-radius: 100% 30%;
    border-top-right-radius: 100% 90%;
    border-bottom-left-radius: 100% 20%;
    border-bottom-right-radius: 100% 100%;
    transform-origin: 0 66%;
  }
  
  @for $i from 1 through $slicesCount {
    &:nth-child(#{$i}) {
      transform: rotateY($i/$slicesCount * 360deg);
    }
  }
    
  @for $y from 0 through ($middleSlice - 1) {
    &:nth-child(#{$slicesPerHump}n - #{$y}),
    &:nth-child(#{$slicesPerHump}n + #{$y}) {
      &::before {
        $factor: $y/($middleSlice - 1);
        $factor: $factor * $factor; // smoothing
        $scaleX: 1 - (1 - .83) * $factor;
        $scaleY: 1 - (1 - .92) * $factor;
        background: mix($pumpkinColorDark, $pumpkinColorLight, $factor * 100%);
        transform: scale($scaleX, $scaleY);
      }
    }
  }
}



////////////////////
//  Pumpkin stem  //
////////////////////

.stem {
  position: absolute;
  left: 0;
  right: 0;
  top: -2%;
  width: 10%;
  margin: auto;
  transform: rotateX(90deg);
  transform-style: preserve-3d;
  
  &::after {
    content: '';
    display: block;
    padding-bottom: 100%;
    border-radius: 100%;
    background: darken($stemColorDark, 5);
    transform: translateZ(#{ (-$stemHeight / ($stemSegments - 1)) / 2 });
    backface-visibility: hidden;
  }
}


@function getStemTranslateX($i) {
  $factor: pow(($i/$stemSegments), 2);
  @return $stemMove * $factor;
}

@function getStemAngle($i) {
  $x: getStemTranslateX($i) - getStemTranslateX($i - 1);
  $y: $stemHeight / ($stemSegments - .5);
  @return atan($x/$y);
}

.stem-slice {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  border-radius: 100%;
  transform-style: preserve-3d;
  
  &::before,
  &::after {
    content: '';
    display: block;
    position: absolute;
    left: 0;
    right: 0;
    margin: auto;
    width: calc(#{$stemHeight / ($stemSegments - 1)} + 2px);
    height: 100%;
    background: currentColor;
  }
  
  &::before {
    box-shadow: inset 0 0 0 $stemHeight rgba(0,0,0,.1);
  }
  
  &::after {
    box-shadow: inset 0 0 0 $stemHeight rgba(0,0,0,.2);
  }
  
  @for $i from 1 through $stemSegments {
    &:nth-child(#{$i}) {
      $factor: ($i - 1) / ($stemSegments - 1);
      $factorS: $factor * (2 - $factor); // smoothing
      $scale: 1 - (1 - $stemTaper) * $factorS;
      $translateX: getStemTranslateX($i);
      $translateZ: $stemHeight * $factor;
      color: mix($stemColorLight, $stemColorDark, $factor * 100%);
      transform: scale($scale) translate3D($translateX, 0, $translateZ);
      
      $beforeAngle: getStemAngle($i);
      
      &::before {
        $factorB: ($i + 1) / ($stemSegments - 1);
        $factorBS: $factorB * $factorB; // smoothing
        $beforeScale: (strip-unit($stemHeight) / cos($beforeAngle)) / strip-unit($stemHeight);
        transform: rotateY($beforeAngle) scaleZ($beforeScale) rotateY(90deg);
      }

      &::after {
        transform: rotateX(90deg) skewX($beforeAngle) rotateZ(90deg);
      }
    }
  }
}



////////////////
//  Face mesh //
////////////////

@function getFaceStepWidth($y) {
  @if $y < 1 {
    @return $faceSegmentWidth;
  }
  
  $stepAngle: ((180 - $faceSegmentRotation) / 2) - ($faceSegmentRotation * (($y - 1) + 0.5));
  $stepOffset: $faceSegmentHeight * cos($stepAngle);
  $skewDistance: $stepOffset * tan($faceColumnRotation/2);
  @return getFaceStepWidth($y - 1) - $skewDistance * 2;
}

.face {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  margin: auto;
  width: $faceWidth;
  height: $faceHeight;
  transform: rotateX(10deg) translate3d(0, 0, $faceTransformOriginZ);
  transform-style: preserve-3d;
}

.face-column {
  position: absolute;
  width: 100% / $faceSegmentsX;
  height: 100%;
  left: 0;
  right: 0;
  margin: auto;
  transform-origin: 50% 50% (-$faceTransformOriginZ);
  transform-style: preserve-3d;
  
  @for $x from 1 through $faceSegmentsX {
    &:nth-child(#{$x}) {
      transform: rotateY(#{ ($x - $faceMiddleSegmentX) * $faceColumnRotation });
    }
  }
}

.face-segment {
  position: absolute;
  width: 100%;
  height: 100% / $faceSegmentsY;
  top: 0;
  bottom: 0;
  margin: auto;
  background: var(--face-color);
  backface-visibility: hidden;
  transition: background 600ms, clip-path 300ms;
  transform-origin: 50% 50% (-$faceTransformOriginZ);

  @for $y from 1 through $faceSegmentsY {
    &:nth-child(#{$y}) {
      transform: rotateX(#{ ($faceMiddleSegmentY - $y) * $faceSegmentRotation });
    }
  }
}



////////////////////////
//  Face expressions  //
////////////////////////

@function mapPolygonToTrapezoid ($path, $topScale: 1, $bottomScale: 1) {
  $transformedPath: "";
  @each $point in $path {
    $xMin: 50% + (nth($point, 1) - 50%) * $topScale;
    $xMax: 50% + (nth($point, 1) - 50%) * $bottomScale;
    $xDiff: $xMax - $xMin;
    $yScale: nth($point, 2) / 100%;
    $newPosX: $xMin + $xDiff * $yScale;

    $transformedPath: if($transformedPath == "", "", $transformedPath + " ");
    $transformedPath: $transformedPath + $newPosX;
    $transformedPath: $transformedPath + " " + nth($point, 2) + ",";
  }
  $transformedPath: str-slice($transformedPath, 1, str-length($transformedPath) - 1);

  @return $transformedPath;
}

@mixin buildFaceExpression ($faceMap: $faceMapFunny) {
  @for $x from 1 through $faceSegmentsX {
    .face-column:nth-child(#{$x}) {
      @for $y from 1 through $faceSegmentsY {
        .face-segment:nth-child(#{$y}) {
          $path: map-get($faceMap, "#{$y}-#{$x}");
          $scaleTop: getFaceStepWidth(abs($faceMiddleSegmentY - $y) - if(($faceMiddleSegmentY - $y) > 0, 0, 1)) / $faceSegmentWidth;
          $scaleBottom: getFaceStepWidth(abs($faceMiddleSegmentY - $y) - if(($faceMiddleSegmentY - $y) > 0, 1, 0)) / $faceSegmentWidth;

          $transformedPath: 50% 50%, 50% 50%, 50% 50%, 50% 50%;
          @if $path != null {
            $transformedPath: mapPolygonToTrapezoid($path, $scaleTop, $scaleBottom);
          }

          clip-path: polygon(#{ $transformedPath });
        }
      }
    }
  }
}

@include buildFaceExpression ($faceMapFunny);

.switch-face {
  box-sizing: border-box;
  position: absolute;
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 1;
  top: 0;
  width: 50%;
  min-height: calc(2rem + 5vh);
  padding: .5em 1em;
  color: #ccc;
  background: rgba(0,0,0,.1);
  border-bottom: 1px solid rgba(0,0,0,.2);
  text-align: center;
  line-height: 1.2;
  cursor: pointer;
  
  .icon {
    margin-right: 0.5em;
  }
  
  input[value="funny"] + & {
    border-right: 1px solid $backgroundColor;
  }
  
  input[value="scary"] + & {
    left: 50%;
  }

  input:checked + & {
    color: #fff;
    background: rgba(0,0,0,.25);
  }

  input:focus + & .label {
    text-decoration: underline;
    text-decoration-skip: ink;
  }
}

input[name="expression"] {
  opacity: 0;
}

input[value="scary"]:checked ~ .scene {
  @include buildFaceExpression ($faceMapScary);
}



///////////////////
//  Scene setup  //
///////////////////

body {
  background: $backgroundColor;
}

.scene {
  position: absolute;
  top: calc(2rem + 5vh);
  right: 0;
  bottom: 0;
  left: 0;
  perspective: 66vmin;
  overflow: hidden;
}

.scene-x,
.scene-y,
.scene-z {
  position: relative;
  width: 100%;
  height: 100%;
  transform-style: preserve-3d;
  will-change: transform;
  transition: transform 200ms;
}
  
.scene-x {
  transform: rotateX(var(--rotationX));
}

.scene-y {
  transform: rotateY(var(--rotationY));
}

.scene-z {
  transform: rotateZ(var(--rotationZ));
}



////////////////////////
//   Scene rotation   //
////////////////////////

[class^="cursor-segment"] {
  position: absolute;
  z-index: 10;
  width: 100% / $cursorSegmentsX;
  height: 100% / $cursorSegmentsY;
  outline: none;
}

@for $x from 1 through $cursorSegmentsX {
  @for $y from 1 through $cursorSegmentsY {
    .cursor-segment-#{$x}-#{$y} {
      left: ($x - 1) / $cursorSegmentsX * 100%;
      top: ($y - 1) / $cursorSegmentsY * 100%;

      &:hover:hover, &:focus {
        ~ .scene-x {
          $lightFactor: min(
                          sqrt( pow($x - $middleSegmentX, 2) + pow($y - $middleSegmentY, 2) ) /
                            sqrt( pow($cursorSegmentsX - $middleSegmentX, 2) + pow($cursorSegmentsY - $middleSegmentY, 2) )
                            * 2 * 100%,
                          100%
                        );
          
          --rotationX: ($middleSegmentY - $y) / ($middleSegmentY - 1) * $sceneRotateX;
          --rotationY: ($x - $middleSegmentX) / ($middleSegmentX - 1) * $sceneRotateY;
          --face-color: mix($faceColorDark, $faceColorLight, $lightFactor);
        }
      }
    }
  }
}
            
          
!
999px
Loading ..................

Console