Tag: Architecture

  • 7 ShapeCalc: unit-less values with calc()

    ShapeCalc is the next step in Shape Pack 2 and continues the shift from fixed to parameter‑driven geometry. It builds on ShapeScope by converting the pixel‑based translate-z custom property into a unit‑less numeric value, then re‑applying the pixel units in the transform using calc() to multiply the unit-less number by 1px with calc(var(--translate-z) * 1px).

    The following code snippet contains the base HTML for the CSS 3d shape.

    <div class="scene">
            <div class="shape">
                    <div class="shape__face shape__face--front">FRONT<br />ShapeCalc</div>
                    <div class="shape__face shape__face--left">LEFT</div>
                    <div class="shape__face shape__face--back">BACK</div>
                    <div class="shape__face shape__face--right">RIGHT</div>
                    <div class="shape__face shape__face--top">TOP</div>
                    <div class="shape__face shape__face--bottom">BOTTOM</div>
            </div>
    </div>

    The code below shows the current CSS for the shape.

    body
    {
            margin: 0;
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            background: #05060a;
            color: #f5f5f5;
            font-family: system-ui, -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", sans-serif;
    }
    
    .scene
    {
            width: 200px;
            height: 200px;
            perspective: 800px;
            perspective-origin: 50% 50%;
    }
    
    @scope (.shape) 
    {
            :scope
            {                             
                    --total-degrees:360;
                    --number-of-sides:4;
                    --degrees-per-side:90;
                    
                    --translate-x:0px;                        
                    --translate-y:0px;
                    --translate-z:100;  
                    
                    --shape-size: 200px;
                    --shape-depth: 200px;
                    --panel-gap: 0px;
    
                    --rotate-x: -20deg;
                    --rotate-y: 25deg;
                    --rotate-z: 0deg;
    
                    --perspective: 800px;
                    --perspective-origin-x: 50%;
                    --perspective-origin-y: 50%;
    
                    --face-front-rotate: 0deg;
                    --face-left-rotate: -90deg;
                    --face-back-rotate: 180deg;
                    --face-right-rotate: 90deg;
                    --face-top-rotate: 90deg;
                    --face-bottom-rotate: -90deg;
                    
                    position: relative;
                    width: 100%;
                    height: 100%;
                    transform-style: preserve-3d;
                    transform: rotateX(-20deg) rotateY(25deg) rotateZ(0deg);
            }
    
            :scope .shape__face
            {
                    position: absolute;
                    width: 100%;
                    height: 100%;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    background: linear-gradient(135deg, #1b1f2a, #2c3244);
                    outline: 1px solid rgba(255, 255, 255, 0.15);
                    box-sizing: border-box;
            }
    
            :scope .shape__face--front      {  transform: rotateY(var(--face-front-rotate))  translateZ(calc(var(--translate-z) * 1px)); }
            :scope .shape__face--left       {  transform: rotateY(var(--face-left-rotate))   translateZ(calc(var(--translate-z) * 1px)); }
            :scope .shape__face--back       {  transform: rotateY(var(--face-back-rotate))   translateZ(calc(var(--translate-z) * 1px)); }
            :scope .shape__face--right      {  transform: rotateY(var(--face-right-rotate))  translateZ(calc(var(--translate-z) * 1px)); }
            :scope .shape__face--top        {  transform: rotateX(var(--face-top-rotate))    translateZ(calc(var(--translate-z) * 1px)); }
            :scope .shape__face--bottom     {  transform: rotateX(var(--face-bottom-rotate)) translateZ(calc(var(--translate-z) * 1px)); }
    }      

    The goal of this step is to separate data from presentation, allowing the shape’s internal math to stay clean, scalable, and re-usable.

    The folder includes:

    • a CSS file showing the unit‑less variable pattern
    • a custom property rewritten as a pure number instead of using a unit after the number
    • updated transforms that applies units through calc() at the moment of use

    This step shows how geometry can become more flexible without changing the visual output. The cube still behaves the same, but some of it’s internal values are now mathematical rather than fixed. By removing units from the variable and restoring them only when needed, the shape becomes easier to scale, easier to compute with, and better prepared for future components that rely on dynamic geometry.

  • 6 ShapeScope: modularize with @scope

    ShapeScope is the first step in Shape Pack 2 and the point where the shape becomes fully self‑contained. It builds on ShapeEngine by moving all custom properties and internal selectors into a top‑level @scope (.shape) block, turning the shape into a modular unit that no longer depends on global variables.

    Current state of the shape, shown here as the ShapeScope component on a black background.

    The following code snippet contains the base HTML for the CSS 3d shape.

    <div class="scene">
            <div class="shape">
                    <div class="shape__face shape__face--front">FRONT<br />ShapeScope</div>
                    <div class="shape__face shape__face--left">LEFT</div>
                    <div class="shape__face shape__face--back">BACK</div>
                    <div class="shape__face shape__face--right">RIGHT</div>
                    <div class="shape__face shape__face--top">TOP</div>
                    <div class="shape__face shape__face--bottom">BOTTOM</div>
            </div>
    </div>

    The following code snippet contains the CSS for ShapeScope.

    body
    {
            margin: 0;
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            background: #05060a;
            color: #f5f5f5;
            font-family: system-ui, -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", sans-serif;
    }
    
    .scene
    {
            width: 200px;
            height: 200px;
            perspective: 800px;
            perspective-origin: 50% 50%;
    }
    
    @scope (.shape) 
    {
            :scope
            {
                    --shape-size: 200px;
                    --shape-depth: 200px;
                    --panel-count: 6;
                    --panel-gap: 0px;
    
                    --rotate-x: -20deg;
                    --rotate-y: 25deg;
                    --rotate-z: 0deg;
    
                    --perspective: 800px;
                    --perspective-origin-x: 50%;
                    --perspective-origin-y: 50%;
    
                    --animation-duration: 2000ms;
                    --animation-easing: ease-in-out;
    
                    --face-front-rotate: 0deg;
                    --face-left-rotate: -90deg;
                    --face-back-rotate: 180deg;
                    --face-right-rotate: 90deg;
                    --face-top-rotate: 90deg;
                    --face-bottom-rotate: -90deg;
    
                    
                    position: relative;
                    width: 100%;
                    height: 100%;
                    transform-style: preserve-3d;
                    transform: rotateX(-20deg) rotateY(25deg) rotateZ(0deg);
                    transition: transform 2000ms ease-in-out;
            }
    
            :scope .shape__face
            {
                    position: absolute;
                    width: 100%;
                    height: 100%;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    background: linear-gradient(135deg, #1b1f2a, #2c3244);
                    outline: 1px solid rgba(255, 255, 255, 0.15);
                    box-sizing: border-box;
            }
    
            :scope .shape__face--front      { transform: rotateY(var(--face-front-rotate))  translateZ(calc(var(--shape-depth) / 2)); }
            :scope .shape__face--left       { transform: rotateY(var(--face-left-rotate))   translateZ(calc(var(--shape-depth) / 2)); }
            :scope .shape__face--back       { transform: rotateY(var(--face-back-rotate))   translateZ(calc(var(--shape-depth) / 2)); }
            :scope .shape__face--right      { transform: rotateY(var(--face-right-rotate))  translateZ(calc(var(--shape-depth) / 2)); }
            :scope .shape__face--top        { transform: rotateX(var(--face-top-rotate))    translateZ(calc(var(--shape-depth) / 2)); }
            :scope .shape__face--bottom     { transform: rotateX(var(--face-bottom-rotate)) translateZ(calc(var(--shape-depth) / 2)); }
    }      

    The goal of this step is to convert the shape from a global pattern into an isolated, reusable component.

    The folder includes:

    • a CSS file with the entire shape wrapped inside a single @scope (.shape) block
    • all former :root custom properties moved into :scope so they apply only to the component
    • updated selectors that ensure every face and transform is local to the .shape instance

    This step shows how modularity can evolve without changing the visual output or adding complexity. The cube behaves exactly the same, but its internal logic is now protected. By isolating variables and selectors, the shape becomes safer to reuse, easier to drop into larger projects, and more predictable when multiple shapes coexist.

  • 4 Shape Engine: Our configurable base shape

    ShapeEngine is the third step in the pack and the point where the shape gains internal structure. It builds on ShapeWithVariables by using the CSS custom properties defined there to replace the hard‑coded values in the 3d transform.

    Screenshot of the ShapeEngine component, showing a centered CSS cube on a black background.
    Current state of the shape, shown here as the ShapeEngine component on a black background.

    The code below shows the base HTML for the shape.

    <div class="scene">
            <div class="shape">
                    <div class="shape__face shape__face--front">FRONT<br />ShapeEngine</div>
                    <div class="shape__face shape__face--left">LEFT</div>
                    <div class="shape__face shape__face--back">BACK</div>
                    <div class="shape__face shape__face--right">RIGHT</div>
                    <div class="shape__face shape__face--top">TOP</div>
                    <div class="shape__face shape__face--bottom">BOTTOM</div>
            </div>
    </div>

    The code below shows the current CSS for the shape.

    :root
    {
            --shape-size: 200px;
            --shape-depth: 200px;
            --panel-count: 6;
            --panel-gap: 0px;
    
            --rotate-x: -20deg;
            --rotate-y: 25deg;
            --rotate-z: 0deg;
    
            --perspective: 800px;
            --perspective-origin-x: 50%;
            --perspective-origin-y: 50%;
    
            --face-front-rotate: 0deg;
            --face-left-rotate: -90deg;
            --face-back-rotate: 180deg;
            --face-right-rotate: 90deg;
            --face-top-rotate: 90deg;
            --face-bottom-rotate: -90deg;
    }
    
    body
    {
            margin: 0;
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            background: #05060a;
            color: #f5f5f5;
            font-family: system-ui, -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", sans-serif;
    }
    
    .scene
    {
            width: 200px;
            height: 200px;
            perspective: 800px;
            perspective-origin: 50% 50%;
    }
    
    .shape
    {
            position: relative;
            width: 100%;
            height: 100%;
            transform-style: preserve-3d;
            transform: rotateX(-20deg) rotateY(25deg) rotateZ(0deg);
    }
    
    .shape__face
    {
            position: absolute;
            width: 100%;
            height: 100%;
            display: flex;
            align-items: center;
            justify-content: center;
            background: linear-gradient(135deg, #1b1f2a, #2c3244);
            outline: 1px solid rgba(255, 255, 255, 0.15);
            box-sizing: border-box;
    }
    
    .shape__face--front { transform: rotateY(var(--face-front-rotate)) translateZ(calc(var(--shape-depth) / 2)); }
    .shape__face--left { transform: rotateY(var(--face-left-rotate)) translateZ(calc(var(--shape-depth) / 2)); }
    .shape__face--back { transform: rotateY(var(--face-back-rotate)) translateZ(calc(var(--shape-depth) / 2)); }
    .shape__face--right { transform: rotateY(var(--face-right-rotate)) translateZ(calc(var(--shape-depth) / 2)); }
    .shape__face--top { transform: rotateX(var(--face-top-rotate)) translateZ(calc(var(--shape-depth) / 2)); }
    .shape__face--bottom { transform: rotateX(var(--face-bottom-rotate)) translateZ(calc(var(--shape-depth) / 2)); }
    

    The goal of this step is to complete the foundation of an extensible CSS shape.

    The folder includes:

    • an updated HTML file with the simple shape
    • a CSS file that replaces the hard‑coded values in the transforms with CSS custom properties
    • a short note describing what changed from ShapeWithVariables

    What changed from the previous step.

    • Change: Completed the move from fixed transform values to fully variable‑driven transforms.
    • Reason: Make the shape fully adjustable through custom properties while keeping the component simple and readable.

    This step completes the core of the shape engine. With all transform values now driven by custom properties, the component becomes flexible without adding complexity, and the progression reaches its intended foundation.

  • 3 ShapeWithVariables: Adding Custom Variables for Future Flexibility

    ShapeWithVariables is the second step in the pack and the first moment where the shape gains a bit of structure. It takes the minimal form from SimpleShape and introduces a small set of CSS variables to make the component easier to adjust without changing the core rules.

    Screenshot of the ShapeWithVariables component, showing a centered CSS cube on a black background.
    Current state of the shape, shown here as the ShapeWithVariables component on a black background.

    This example shows the base HTML for the CSS 3d shape.

    <div class="scene">
            <div class="shape">
                    <div class="shape__face shape__face--front">FRONT<br />ShapeWithVariables</div>
                    <div class="shape__face shape__face--left">LEFT</div>
                    <div class="shape__face shape__face--back">BACK</div>
                    <div class="shape__face shape__face--right">RIGHT</div>
                    <div class="shape__face shape__face--top">TOP</div>
                    <div class="shape__face shape__face--bottom">BOTTOM</div>
            </div>
    </div>

    The example shows the new CSS for the CSS 3d shape.

    :root
    {
            --shape-size: 200px;
            --shape-depth: 200px;
            --panel-count: 6;
            --panel-gap: 0px;
    
            --rotate-x: -20deg;
            --rotate-y: 25deg;
            --rotate-z: 0deg;
    
            --perspective: 800px;
            --perspective-origin-x: 50%;
            --perspective-origin-y: 50%;
            
            --face-front-rotate: 0deg;
            --face-left-rotate: -90deg;
            --face-back-rotate: 180deg;
            --face-right-rotate: 90deg;
            --face-top-rotate: 90deg;
            --face-bottom-rotate: -90deg;
    }
    
    body
    {
            margin: 0;
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            background: #05060a;
            color: #f5f5f5;
            font-family: system-ui, -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", sans-serif;
    }
    
    .scene
    {
            width: 200px;
            height: 200px;
            perspective: 800px;
            perspective-origin: 50% 50%;
    }
    
    .shape
    {
            position: relative;
            width: 100%;
            height: 100%;
            transform-style: preserve-3d;
            transform: rotateX(-20deg) rotateY(25deg) rotateZ(0deg);
    }
    
    .shape__face
    {
            position: absolute;
            width: 100%;
            height: 100%;
            display: flex;
            align-items: center;
            justify-content: center;
            background: linear-gradient(135deg, #1b1f2a, #2c3244);
            outline: 1px solid rgba(255, 255, 255, 0.15);
            box-sizing: border-box;
    }
    
    .shape__face--front { transform: translateZ(100px); }
    .shape__face--left { transform: rotateY(-90deg) translateZ(100px); }
    .shape__face--back { transform: rotateY(180deg) translateZ(100px); }
    .shape__face--right { transform: rotateY(90deg) translateZ(100px); }
    .shape__face--top { transform: rotateX(90deg) translateZ(100px); }
    .shape__face--bottom { transform: rotateX(-90deg) translateZ(100px); }
    

    The goal of this step is flexibility. By moving key values—like size, color, or border thickness—into variables, the shape becomes easier to tune while still staying simple and readable. The component remains minimal, but now it has room to adapt.

    The folder includes:

    • an updated HTML file using the same basic structure
    • a CSS file that adds CSS custom properties
    • a short note describing what changed from ShapeSimple

    What changed from the previous step.

    • Change: Introduced a small set of CSS custom properties to eventually replace fixed values.
    • Reason: Make the shape easier to adjust while keeping the component simple and readable.

    This step shows how a shape can evolve without becoming complicated. CSS custom properties give you control, but the component stays small, direct, and easy to understand.

  • 2 ShapeSimple: The First Iteration of Our Shape Component System

    This first step focuses on clarity. ShapeSimple has no variables, layers, or abstractions so you can see the core structure without distraction. It’s the cleanest version of the shape and the starting point for the entire progression.

    Screenshot of the ShapeSimple component, showing a centered CSS cube on a black background
    Current state of the shape, shown here as the SimpleShape component on a black background.

    The following code snippet contains the base HTML for the CSS 3d shape.

    <div class="scene">
            <div class="shape">
                    <div class="shape__face shape__face--front">FRONT<br />ShapeSimple</div>
                    <div class="shape__face shape__face--left">LEFT</div>
                    <div class="shape__face shape__face--back">BACK</div>
                    <div class="shape__face shape__face--right">RIGHT</div>
                    <div class="shape__face shape__face--top">TOP</div>
                    <div class="shape__face shape__face--bottom">BOTTOM</div>
            </div>
    </div>
    

    The following code snippet contains the CSS for the CSS 3d shape’s first iteration.

    
    
    
    body
    {
            margin: 0;
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            background: #05060a;
            color: #f5f5f5;
            font-family: system-ui, -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", sans-serif;
    }
    
    .scene
    {
            width: 200px;
            height: 200px;
            perspective: 800px;
            perspective-origin: 50% 50%;
    }
    
    .shape
    {
            position: relative;
            width: 100%;
            height: 100%;
            transform-style: preserve-3d;
            transform: rotateX(-20deg) rotateY(25deg) rotateZ(0deg);
    }
    
    .shape__face
    {
            position: absolute;
            width: 100%;
            height: 100%;
            display: flex;
            align-items: center;
            justify-content: center;
            background: linear-gradient(135deg, #1b1f2a, #2c3244);
            outline: 1px solid rgba(255, 255, 255, 0.15);
            box-sizing: border-box;
    }
    
    .shape__face--front { transform: translateZ(100px); }
    .shape__face--left { transform: rotateY(-90deg) translateZ(100px); }
    .shape__face--back { transform: rotateY(180deg) translateZ(100px); }
    .shape__face--right { transform: rotateY(90deg) translateZ(100px); }
    .shape__face--top { transform: rotateX(90deg) translateZ(100px); }
    .shape__face--bottom { transform: rotateX(-90deg) translateZ(100px); }

    The folder contains three small files:

    • an HTML file with the basic element structure
    • a CSS file with the minimal rules needed to form the shape
    • a short note describing what this step introduces

    What changed from the previous step.

    • Change: This is the first step, so nothing is being modified yet.
    • Reason: Establish a clean, minimal foundation for all later shapes.

    This component sets the foundation for the rest of the pack. Every later shape builds on this one, adding structure or behavior in small, deliberate steps.

  • 1 Introducing Shape Pack 1: A Set of Shape‑Based UI Components

    The Shape Pack is a small collection of components that show how a simple shape evolves through clear, incremental steps.

    Screenshot of the first component ShapeSimple, showing a centered CSS cube on a black background
    Current state of the shape, shown here as the SimpleShape component on a black background.

    This pack collects a small set of shape components arranged in a simple progression. Each folder represents one stage of the shape’s build, from the first basic form to more structured versions. The pack started as a way to explore simple, readable component structure.

    The files are kept separate and readable so you can open any component and understand what it’s doing without tracing through a larger system.

    The structure is consistent across the pack. Every component has its own HTML, its own CSS, and a short note describing what changed from the previous step. This makes the progression easy to follow whether you move through it in order or jump between pieces. The pack isn’t tied to a framework or a specific workflow, so it can fit into different projects or be used as a reference on its own.

    The pack shows how a shape can evolve through small, deliberate steps, and it keeps each step isolated so the ideas stay easy to see. You can use the components directly, adapt them, or treat the pack as a starting point for your own variations.