1---
2/**
3 * Section divider — hairlines tapering toward a centered geometric
4 * glyph, with small flanking dots for the 17th-c. engraved-rule feel.
5 *
6 * Glyphs are mathematically perfect regular polygons (or a line / a
7 * circle as the limit cases). The home page uses these to compose
8 * a "platonic descent" — line → triangle → square → pentagon →
9 * hexagon → heptagon → octagon → circle — down the page.
10 */
11
12type Variant =
13 | "line"
14 | "triangle"
15 | "square"
16 | "pentagon"
17 | "hexagon"
18 | "heptagon"
19 | "octagon"
20 | "circle";
21
22interface PolygonSpec {
23 /** number of sides; 0 means special-cased (line / circle) */
24 sides: number;
25 /** rotation of the first vertex in degrees (SVG: 0° = right, 90° = down) */
26 startDeg: number;
27}
28
29const POLYGON: Record<Variant, PolygonSpec> = {
30 line: { sides: 0, startDeg: 0 },
31 triangle: { sides: 3, startDeg: -90 }, // apex up
32 square: { sides: 4, startDeg: -45 }, // sides axis-aligned
33 pentagon: { sides: 5, startDeg: -90 }, // apex up
34 hexagon: { sides: 6, startDeg: 0 }, // flat-top
35 heptagon: { sides: 7, startDeg: -90 }, // apex up
36 octagon: { sides: 8, startDeg: -67.5 }, // flat-top
37 circle: { sides: 0, startDeg: 0 },
38};
39
40function polygonPoints(
41 sides: number,
42 startDeg: number,
43 cx = 12,
44 cy = 12,
45 r = 9.5,
46): string {
47 const pts: string[] = [];
48 for (let i = 0; i < sides; i++) {
49 const angle = ((startDeg + (360 / sides) * i) * Math.PI) / 180;
50 const x = cx + r * Math.cos(angle);
51 const y = cy + r * Math.sin(angle);
52 pts.push(`${x.toFixed(3)},${y.toFixed(3)}`);
53 }
54 return pts.join(" ");
55}
56
57interface Props {
58 variant?: Variant;
59 size?: number;
60 width?: string;
61 class?: string;
62}
63
64const {
65 variant = "circle",
66 size = 22,
67 width = "320px",
68 class: className,
69}: Props = Astro.props;
70
71const spec = POLYGON[variant as Variant];
72const points = spec.sides >= 3 ? polygonPoints(spec.sides, spec.startDeg) : "";
73---
74
75<div
76 class:list={["alc-divider", className]}
77 style={`--alc-divider-width:${width};`}
78 aria-hidden="true"
79>
80 <span class="alc-divider__rule"></span>
81 <span class="alc-divider__bullet"></span>
82 <svg
83 class="alc-divider__shape"
84 width={size}
85 height={size}
86 viewBox="0 0 24 24"
87 fill="none"
88 stroke="currentColor"
89 stroke-width="1.1"
90 stroke-linecap="round"
91 stroke-linejoin="miter"
92 stroke-miterlimit="6"
93 >
94 {variant === "line" && <line x1="2" y1="12" x2="22" y2="12" />}
95 {variant === "circle" && <circle cx="12" cy="12" r="9.5" />}
96 {points !== "" && <polygon points={points} />}
97 </svg>
98 <span class="alc-divider__bullet"></span>
99 <span class="alc-divider__rule"></span>
100</div>
101
102<style>
103 .alc-divider {
104 display: flex;
105 align-items: center;
106 gap: 10px;
107 margin: 0 auto;
108 max-width: var(--alc-divider-width, 320px);
109 padding: 28px 0;
110 color: var(--alc-fg-3);
111 opacity: 0.7;
112 }
113 .alc-divider__rule {
114 flex: 1;
115 height: 1px;
116 background: linear-gradient(
117 to right,
118 transparent,
119 currentColor 70%,
120 currentColor
121 );
122 }
123 .alc-divider__rule:last-child {
124 background: linear-gradient(
125 to left,
126 transparent,
127 currentColor 70%,
128 currentColor
129 );
130 }
131 .alc-divider__bullet {
132 flex: none;
133 width: 3px;
134 height: 3px;
135 border-radius: 50%;
136 background: currentColor;
137 }
138 .alc-divider__shape {
139 flex: none;
140 display: block;
141 }
142</style>