PageSourceSearch

https://leandrolinares.com/dev/css-sphere

html leandrolinares.com collected 2026-10-03 23:19:51 UTC 8,065 bytes, 225 lines download raw bytes

1<!doctype html>
2<html lang="en" itemscope itemtype="https://schema.org/Article">
3<head>
4  <meta charset="utf-8">
5  <title>CSS-Only 3D Sphere - Leandro Linares</title>
6  <meta name="viewport" content="width=device-width">
7  <meta name="theme-color" content="#d9514e">
8  <meta name="description" content="21 stacked, bordered elements separated by equal distances create the perception of volume.">
9  <link rel="icon" href="/favicon.gif">
10  <link rel="apple-touch-icon" href="/apple-touch-icon.png">
11  <link rel="manifest" href="/manifest.json">
12  <link rel="alternate" href="/feed.xml" type="application/rss+xml" title="Leandro Linares">
13  <link rel="stylesheet" href="/styles.css">
14  <link rel="canonical" href="https://leandrolinares.com/dev/css-sphere/">
15
16  <meta itemprop="mainEntityOfPage" content="https://leandrolinares.com/dev/css-sphere/">
17  <meta itemprop="inLanguage" content="en">
18  <meta itemprop="image" content="https://leandrolinares.com/thumbnails/css-sphere.png">
19  <meta name="twitter:site" content="@lean8086">
20  <meta name="twitter:url" content="https://leandrolinares.com/dev/css-sphere/">
21  <meta name="twitter:title" content="CSS-Only 3D Sphere">
22  <meta name="twitter:description" content="21 stacked, bordered elements separated by equal distances create the perception of volume.">
23  <meta name="twitter:creator" content="@lean8086">
24  <meta name="twitter:card" content="summary_large_image">
25  <meta name="twitter:image" content="https://leandrolinares.com/thumbnails/css-sphere.png">
26  <meta property="og:title" content="CSS-Only 3D Sphere">
27  <meta property="og:type" content="article">
28  <meta property="og:url" content="https://leandrolinares.com/dev/css-sphere/">
29  <meta property="og:description" content="21 stacked, bordered elements separated by equal distances create the perception of volume.">
30  <meta property="og:site_name" content="Leandro Linares">
31  <meta property="og:image" content="https://leandrolinares.com/thumbnails/css-sphere.png">
32
33  <style>
34    :root {
35      --diameter: 50vmin;
36      --color-dark: #2a2b2d;
37      --color-light: #d9514e;
38      --gutter: 5%;
39    }
40
41    /* LAYOUT */
42
43    html {
44      height: 100%;
45    }
46
47    body {
48      background-color: var(--color-light);
49      display: flex;
50      flex-direction: column;
51    }
52
53    .experiment-area {
54      flex: auto;
55    }
56
57    header a {
58      color: var(--color-dark);
59    }
60
61    .experiment-area ol, .ring {
62      width: var(--diameter);
63      height: var(--diameter);
64      list-style: none;
65      box-sizing: border-box;
66      transform-style: preserve-3d;
67      pointer-events: none;
68    }
69
70    .experiment-area ol {
71      transform: translateX(-25%) translateY(-100%) rotateX(67deg) rotateY(30deg) rotateZ(30deg);
72      padding-left: 0;
73      animation: tease 7s linear infinite;
74      position: absolute;
75      left: 50%;
76      top: 50%;
77    }
78
79    /* IMPLEMENTATION STARTS HERE */
80
81    .ring {
82      transform: rotateX(-90deg);
83      margin-bottom: calc(-1 * var(--diameter) + var(--gutter));
84      display: flex;
85      align-items: center;
86      justify-content: center;
87      transform-origin: top;
88    }
89
90    .ring::after {
91      content: '';
92      display: block;
93      border: 2px solid var(--color-dark);
94      width: var(--diameter);
95      height: var(--diameter);
96      border-radius: 100%;
97      animation: spin 1s linear infinite;
98    }
99
100    .ring:nth-child(1)::after, .ring:nth-last-child(1)::after { width: 21%; height: 21%; }
101    .ring:nth-child(2)::after, .ring:nth-last-child(2)::after { width: 42%; height: 42%; }
102    .ring:nth-child(3)::after, .ring:nth-last-child(3)::after { width: 59%; height: 59%; }
103    .ring:nth-child(4)::after, .ring:nth-last-child(4)::after { width: 70%; height: 70%; }
104    .ring:nth-child(5)::after, .ring:nth-last-child(5)::after { width: 79%; height: 79%; }
105    .ring:nth-child(6)::after, .ring:nth-last-child(6)::after { width: 86%; height: 86%; }
106    .ring:nth-child(7)::after, .ring:nth-last-child(7)::after { width: 91%; height: 91%; }
107    .ring:nth-child(8)::after, .ring:nth-last-child(8)::after { width: 95%; height: 95%; }
108    .ring:nth-child(9)::after, .ring:nth-last-child(9)::after { width: 97%; height: 97%; }
109    .ring:nth-child(10)::after, .ring:nth-last-child(10)::after { width: 99%; height: 99%; }
110
111    .ring:nth-child(1)::after { animation-delay: 100ms; }
112    .ring:nth-child(2)::after { animation-delay: 200ms; }
113    .ring:nth-child(3)::after { animation-delay: 300ms; }
114    .ring:nth-child(4)::after { animation-delay: 400ms; }
115    .ring:nth-child(5)::after { animation-delay: 500ms; }
116    .ring:nth-child(6)::after { animation-delay: 600ms; }
117    .ring:nth-child(7)::after { animation-delay: 700ms; }
118    .ring:nth-child(8)::after { animation-delay: 800ms; }
119    .ring:nth-child(9)::after { animation-delay: 900ms; }
120    .ring:nth-child(10)::after { animation-delay: 1000ms; }
121    .ring:nth-child(11)::after { animation-delay: 1100ms; }
122    .ring:nth-child(12)::after { animation-delay: 1200ms; }
123    .ring:nth-child(13)::after { animation-delay: 1300ms; }
124    .ring:nth-child(14)::after { animation-delay: 1400ms; }
125    .ring:nth-child(15)::after { animation-delay: 1500ms; }
126    .ring:nth-child(16)::after { animation-delay: 1600ms; }
127    .ring:nth-child(17)::after { animation-delay: 1700ms; }
128    .ring:nth-child(18)::after { animation-delay: 1800ms; }
129    .ring:nth-child(19)::after { animation-delay: 1900ms; }
130    .ring:nth-child(20)::after { animation-delay: 2000ms; }
131    .ring:nth-child(21)::after { animation-delay: 2100ms; }
132
133    @keyframes tease {
134      from { transform: translateX(-25%) translateY(-100%) rotateX(67deg) rotateY(30deg) rotateZ(0deg); }
135      to { transform: translateX(-25%) translateY(-100%) rotateX(67deg) rotateY(30deg) rotateZ(360deg); }
136    }
137
138    @keyframes spin {
139      from { transform: rotateZ(0deg); }
140      to { transform: rotateZ(360deg); }
141    }
142
143    /* VARIATIONS */
144
145    [data-len='3'] .ring::after {
146      border-bottom-color: transparent;
147    }
148
149    [data-len='2'] .ring::after {
150      border-bottom-color: transparent;
151      border-left-color: transparent;
152    }
153
154    [data-len='1'] .ring::after {
155      border-bottom-color: transparent;
156      border-left-color: transparent;
157      border-top-color: transparent;
158    }
159
160    [name='len'] {
161      -webkit-appearance: none;
162      position: absolute;
163      left: 50%;
164      top: 85%;
165      transform: translateX(-50%);
166    }
167
168    [name='len']::-webkit-slider-runnable-track {
169      background: var(--color-dark);
170      height: 2px;
171    }
172
173    [name='len']::-webkit-slider-thumb {
174      margin-top: -6px;
175      width: 14px;
176      height: 14px;
177      background: var(--color-dark);
178      border-radius: 100%;
179      -webkit-appearance: none;
180      cursor: pointer;
181    }
182  </style>
183</head>
184
185<body>
186  <header class="header" role="banner">
187    <a class="header__logo pixelated" href="/">Leandro's Web Site</a>
188    <!-- <a href="https://github.com/lean8086/blog/blob/gh-pages/dev/css-sphere/index.html" target="_blank">View Source Code</a> -->
189  </header>
190
191  <main class="experiment-area">
192    <ol>
193      <li class="ring"></li>
194      <li class="ring"></li>
195      <li class="ring"></li>
196      <li class="ring"></li>
197      <li class="ring"></li>
198      <li class="ring"></li>
199      <li class="ring"></li>
200      <li class="ring"></li>
201      <li class="ring"></li>
202      <li class="ring"></li>
203      <li class="ring"></li>
204      <li class="ring"></li>
205      <li class="ring"></li>
206      <li class="ring"></li>
207      <li class="ring"></li>
208      <li class="ring"></li>
209      <li class="ring"></li>
210      <li class="ring"></li>
211      <li class="ring"></li>
212      <li class="ring"></li>
213      <li class="ring"></li>
214    </ol>
215    <input type="range" value="4" min="1" max="4" name="len">
216  </main>
217  
218  <!-- <footer class="footer" role="contentinfo"><a href="/">Leandro Linares</a></footer> -->
219
220  
220<script>
221    const list = document.querySelector('ol');
222    document.querySelector('[name=len]').addEventListener('input', (ev) => { list.dataset.len = ev.target.value });
223  </script>
223
224</body>
225</html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.