-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathparty_css_gen.py
169 lines (141 loc) · 5.24 KB
/
party_css_gen.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
#!/usr/bin/env python3
import math
import argparse
MINIFY = False
class CSSGenerator:
def __init__(self):
# 🦜 colors can be obtained from extract-colours.py
self.colors = [
"#ff8d8b", "#fed689", "#88ff89", "#87ffff", "#8bb5fe",
"#d78cff", "#ff8cff", "#ff68f7", "#fe6cb7", "#ff6968"
]
self._css = None
self.minify = MINIFY
def _generate_root_variables(self):
return """:root {
--max-translation: 0px; /* pixels */
--translation-phase: -3; /* radians */
--max-skew: 18; /* degrees */
--max-scale-diff: 0.15; /* scale factor */
--anim-freq: 2; /* Hz */
--anim-time: calc(1/var(--anim-freq) * 1s);
}
"""
def _generate_element_style(self):
return """
.🦜 {
animation: 🎉 var(--anim-time) infinite linear, 🎨 var(--anim-time) infinite linear;
transform-origin: bottom center;
}
"""
def _generate_tint_keyframes(self):
keyframes = [" 0%,\n 100% { background-color: #ff8d8b; }"]
num_colors = len(self.colors)
pct_step = 100 / (num_colors - 1)
for i, color in enumerate(self.colors[1:-1], 1):
keyframes.append(f"{i * pct_step:.2f}% {{ background-color: {color}; }}")
return "\n ".join(keyframes)
def _trunc_number(self, num):
if self.minify:
return num.rstrip("0").rstrip(".")
return num
def _make_party_keyframe(self, progress, angle):
skew_x = -math.cos(angle)
scale_y = math.sin(angle)
progress_str = f" {self._trunc_number(f'{progress * 100:.1f}')}%"
skew_str = ""
if not math.isclose(skew_x, 0, abs_tol=1e-6) or not self.minify:
skew_multiplier = self._trunc_number(f"{skew_x:.4f}")
skew_str = f"skewX(calc(var(--max-skew) * {skew_multiplier}deg))"
translate_angle = self._trunc_number(f"{angle:.4f}")
translate_str = (
f"translateX(calc(var(--max-translation) * "
f"sin({translate_angle} + var(--translation-phase))))"
)
scale_y_multiplier = self._trunc_number(f"{scale_y:.4f}")
scale_str = f"scaleY(calc(1 + var(--max-scale-diff) * {scale_y_multiplier}))"
if math.isclose(scale_y, 0, abs_tol=1e-6) and self.minify:
scale_str = f"scaleY(1)"
# Example output:
# 0% { transform:
# skewX(calc(var(--max-skew) * 0deg))
# translateX(calc(var(--max-translation) * sin(0 + var(--translation-phase))))
# scaleY(1)
# }
return f"""{progress_str} {{
transform:
{skew_str}
{translate_str}
{scale_str};
}}"""
def _generate_party_keyframes(self, steps=20):
keyframes = []
for i in range(steps + 1):
progress = i / steps
angle = 2 * math.pi * progress
keyframes.append(self._make_party_keyframe(progress, angle))
return "\n".join(keyframes)
def generate_css(self):
if self._css is None:
css = [
self._generate_root_variables(),
self._generate_element_style(),
"@keyframes 🎨 {",
self._generate_tint_keyframes(),
"}",
"",
"@keyframes 🎉 {",
self._generate_party_keyframes(),
"}"
]
self._css = "\n".join(css)
return self._css
def get_css(self):
return self.generate_css()
def gen_bookmarklet(css_generator):
bookmarklet = """
javascript:(function(){
var s = document.createElement('style');
s.innerHTML = `
<<CSS>>
`;
document.head.appendChild(s);
})();
"""
return bookmarklet.replace("<<CSS>>", css_generator.get_css())
def main():
parser = argparse.ArgumentParser(description="Generate CSS animations for party effect.")
parser.add_argument('-o', '--output', help="Output CSS file path. If not specified, prints to console.")
parser.add_argument('-b', '--bookmarklet', action='store_true', help="Generate bookmarklet.")
parser.add_argument('-a', '--all', action='store_true', help="Generate all CSS, and store to default files.")
parser.add_argument('-m', '--minify', action='store_true', help="Minify the CSS.")
args = parser.parse_args()
if args.minify:
MINIFY = True
css_generator = CSSGenerator()
if args.all:
if args.output:
raise ValueError("Cannot specify output file with --all flag.")
with open("party.css", 'w') as f:
f.write(css_generator.get_css())
with open("bookmarklet.js", 'w') as f:
f.write(gen_bookmarklet(css_generator))
print("CSS written to party.css")
print("Bookmarklet written to bookmarklet.js")
return
if args.bookmarklet:
file_name = "bookmarklet.js"
if args.output:
file_name = args.output
with open(file_name, 'w') as f:
f.write(gen_bookmarklet(css_generator))
print(f"Bookmarklet written to {file_name}")
return
if args.output:
with open(args.output, 'w') as f:
f.write(css_generator.get_css())
print(f"CSS written to {args.output}")
else:
print(css_generator.get_css())
if __name__ == "__main__":
main()