1- import Combine
21import SwiftUI
32
4- private enum EasterEggPreferences {
3+ enum KernelPanicPreferences {
54 static let enabledKey = " coremonitor.easterEggsEnabled "
6- static let bestRallyKey = " coremonitor.easterEggsBestRally "
5+ static let bestScoreKey = " coremonitor.kernelPanicBestScore "
76}
87
98struct EasterEggLabCard : View {
10- @AppStorage ( EasterEggPreferences . enabledKey) private var easterEggsEnabled = false
11- @AppStorage ( EasterEggPreferences . bestRallyKey ) private var bestRally = 0
9+ @AppStorage ( KernelPanicPreferences . enabledKey) private var easterEggsEnabled = false
10+ @AppStorage ( KernelPanicPreferences . bestScoreKey ) private var bestScore = 0
1211
1312 var body : some View {
1413 DarkCard ( padding: 18 ) {
@@ -17,7 +16,7 @@ struct EasterEggLabCard: View {
1716 VStack ( alignment: . leading, spacing: 4 ) {
1817 Text ( " Weird Easter Eggs " )
1918 . font ( . system( size: 18 , weight: . bold) )
20- Text ( " Opt into the deliberately odd extras that do not belong in a thermal monitor, starting with a tiny 2-bit arcade . " )
19+ Text ( " Opt into the deliberately odd extras that do not belong in a thermal monitor, now starring a monochrome battle-box Kernel Panic . " )
2120 . font ( . system( size: 12 , weight: . medium) )
2221 . foregroundStyle ( . secondary)
2322 }
@@ -28,7 +27,7 @@ struct EasterEggLabCard: View {
2827 Text ( easterEggsEnabled ? " Enabled " : " Disabled " )
2928 . font ( . system( size: 13 , weight: . bold, design: . monospaced) )
3029 . foregroundStyle ( easterEggsEnabled ? . green : . secondary)
31- Text ( bestRally == 0 ? " No rally yet " : " Best rally \( bestRally ) " )
30+ Text ( bestScore == 0 ? " No panic contained yet " : " Best purge \( KernelPanicArcade . scoreString ( bestScore ) ) " )
3231 . font ( . system( size: 11 , weight: . medium) )
3332 . foregroundStyle ( . secondary)
3433 }
@@ -38,7 +37,7 @@ struct EasterEggLabCard: View {
3837 VStack ( alignment: . leading, spacing: 3 ) {
3938 Text ( " Enable weird mode " )
4039 . font ( . system( size: 13 , weight: . semibold) )
41- Text ( " Shows hidden experiments and unlocks the retro ping pong board below ." )
40+ Text ( " Unlocks Kernel Panic, a fictional parody boss rush with an original monochrome battle-box look and zero real malware behavior ." )
4241 . font ( . system( size: 11 , weight: . medium) )
4342 . foregroundStyle ( . secondary)
4443 }
@@ -47,262 +46,9 @@ struct EasterEggLabCard: View {
4746 . tint ( Color . bdAccent)
4847
4948 if easterEggsEnabled {
50- RetroPingPongArcade ( )
49+ KernelPanicArcade ( )
5150 }
5251 }
5352 }
5453 }
5554}
56-
57- private struct RetroPingPongArcade : View {
58- @AppStorage ( EasterEggPreferences . bestRallyKey) private var bestRally = 0
59-
60- @State private var arenaSize : CGSize = . zero
61- @State private var playerY : CGFloat = 0.5
62- @State private var cpuY : CGFloat = 0.5
63- @State private var ball = CGPoint ( x: 0.5 , y: 0.5 )
64- @State private var velocity = CGVector ( dx: 0.0105 , dy: 0.0065 )
65- @State private var playerScore = 0
66- @State private var cpuScore = 0
67- @State private var rally = 0
68- @State private var isPaused = false
69-
70- private let timer = Timer . publish ( every: 1.0 / 60.0 , on: . main, in: . common) . autoconnect ( )
71- private let paddleHeightRatio : CGFloat = 0.22
72- private let paddleWidthRatio : CGFloat = 0.018
73- private let ballSizeRatio : CGFloat = 0.024
74- private let playerPaddleX : CGFloat = 0.08
75- private let cpuPaddleX : CGFloat = 0.92
76- private let phosphor = Color ( red: 0.70 , green: 0.98 , blue: 0.74 )
77- private let background = Color ( red: 0.05 , green: 0.08 , blue: 0.05 )
78-
79- var body : some View {
80- VStack ( alignment: . leading, spacing: 12 ) {
81- HStack ( spacing: 12 ) {
82- Text ( " 2-Bit Ping Pong " )
83- . font ( . system( size: 13 , weight: . bold, design: . monospaced) )
84- . foregroundStyle ( phosphor)
85-
86- Spacer ( minLength: 0 )
87-
88- scorePill ( title: " YOU " , value: playerScore)
89- scorePill ( title: " CPU " , value: cpuScore)
90- scorePill ( title: " RALLY " , value: rally)
91- }
92-
93- GeometryReader { geometry in
94- ZStack {
95- RoundedRectangle ( cornerRadius: 14 , style: . continuous)
96- . fill ( background)
97-
98- RoundedRectangle ( cornerRadius: 14 , style: . continuous)
99- . stroke ( phosphor. opacity ( 0.22 ) , lineWidth: 1 )
100-
101- centerNet ( size: geometry. size)
102- paddle ( at: playerPaddleX, y: playerY, size: geometry. size)
103- paddle ( at: cpuPaddleX, y: cpuY, size: geometry. size)
104- ballView ( size: geometry. size)
105-
106- if isPaused {
107- VStack ( spacing: 8 ) {
108- Text ( " PAUSED " )
109- . font ( . system( size: 20 , weight: . black, design: . monospaced) )
110- . foregroundStyle ( phosphor)
111- Text ( " Drag inside the arena to move your paddle. " )
112- . font ( . system( size: 11 , weight: . semibold, design: . monospaced) )
113- . foregroundStyle ( phosphor. opacity ( 0.68 ) )
114- }
115- . padding ( . horizontal, 18 )
116- . padding ( . vertical, 14 )
117- . background ( Color . black. opacity ( 0.34 ) )
118- . clipShape ( RoundedRectangle ( cornerRadius: 12 , style: . continuous) )
119- }
120- }
121- . contentShape ( Rectangle ( ) )
122- . overlay ( alignment: . topLeading) {
123- Color . clear
124- . onAppear {
125- arenaSize = geometry. size
126- restartMatch ( )
127- }
128- . onChange ( of: geometry. size) { newValue in
129- arenaSize = newValue
130- }
131- }
132- . gesture (
133- DragGesture ( minimumDistance: 0 )
134- . onChanged { value in
135- movePlayer ( to: value. location. y)
136- }
137- )
138- }
139- . frame ( height: 228 )
140- . onReceive ( timer) { _ in
141- stepGame ( )
142- }
143-
144- HStack ( spacing: 10 ) {
145- Text ( " Drag anywhere inside the arena to move your paddle. Best rally persists between launches. " )
146- . font ( . system( size: 11 , weight: . medium) )
147- . foregroundStyle ( . secondary)
148-
149- Spacer ( minLength: 12 )
150-
151- arcadeButton ( title: isPaused ? " Resume " : " Pause " , tint: Color . bdAccent) {
152- isPaused. toggle ( )
153- }
154-
155- arcadeButton ( title: " Reset " , tint: . orange) {
156- restartMatch ( )
157- }
158- }
159- }
160- }
161-
162- private func scorePill( title: String , value: Int ) -> some View {
163- VStack ( alignment: . trailing, spacing: 2 ) {
164- Text ( title)
165- . font ( . system( size: 9 , weight: . bold, design: . monospaced) )
166- . foregroundStyle ( phosphor. opacity ( 0.72 ) )
167- Text ( " \( value) " )
168- . font ( . system( size: 15 , weight: . black, design: . monospaced) )
169- . foregroundStyle ( phosphor)
170- }
171- . padding ( . horizontal, 10 )
172- . padding ( . vertical, 7 )
173- . background ( phosphor. opacity ( 0.08 ) )
174- . clipShape ( RoundedRectangle ( cornerRadius: 10 , style: . continuous) )
175- }
176-
177- private func arcadeButton( title: String , tint: Color , action: @escaping ( ) -> Void ) -> some View {
178- Button ( action: action) {
179- Text ( title. uppercased ( ) )
180- . font ( . system( size: 10 , weight: . black, design: . monospaced) )
181- . foregroundStyle ( tint)
182- . padding ( . horizontal, 12 )
183- . padding ( . vertical, 8 )
184- . background ( tint. opacity ( 0.12 ) )
185- . clipShape ( RoundedRectangle ( cornerRadius: 10 , style: . continuous) )
186- . overlay (
187- RoundedRectangle ( cornerRadius: 10 , style: . continuous)
188- . stroke ( tint. opacity ( 0.28 ) , lineWidth: 1 )
189- )
190- }
191- . buttonStyle ( . plain)
192- }
193-
194- private func centerNet( size: CGSize ) -> some View {
195- VStack ( spacing: 8 ) {
196- ForEach ( 0 ..< 10 , id: \. self) { _ in
197- Rectangle ( )
198- . fill ( phosphor. opacity ( 0.32 ) )
199- . frame ( width: 4 , height: 10 )
200- }
201- }
202- . position ( x: size. width / 2 , y: size. height / 2 )
203- }
204-
205- private func paddle( at normalizedX: CGFloat , y normalizedY: CGFloat , size: CGSize ) -> some View {
206- let paddleHeight = max ( 30 , size. height * paddleHeightRatio)
207- let paddleWidth = max ( 8 , size. width * paddleWidthRatio)
208-
209- return Rectangle ( )
210- . fill ( phosphor)
211- . frame ( width: paddleWidth, height: paddleHeight)
212- . position ( x: size. width * normalizedX, y: size. height * normalizedY)
213- . shadow ( color: phosphor. opacity ( 0.24 ) , radius: 4 )
214- }
215-
216- private func ballView( size: CGSize ) -> some View {
217- let ballSize = max ( 8 , min ( size. width, size. height) * ballSizeRatio)
218-
219- return Rectangle ( )
220- . fill ( phosphor)
221- . frame ( width: ballSize, height: ballSize)
222- . position ( x: size. width * ball. x, y: size. height * ball. y)
223- . shadow ( color: phosphor. opacity ( 0.32 ) , radius: 4 )
224- }
225-
226- private func movePlayer( to yPosition: CGFloat ) {
227- guard arenaSize. height > 0 else { return }
228- let halfHeight = paddleHeightRatio / 2
229- playerY = clamp ( yPosition / arenaSize. height, lower: halfHeight, upper: 1 - halfHeight)
230- }
231-
232- private func stepGame( ) {
233- guard arenaSize. width > 0 , arenaSize. height > 0 , isPaused == false else { return }
234-
235- let paddleHalfHeight = paddleHeightRatio / 2
236- let paddleHalfWidth = paddleWidthRatio / 2
237- let ballRadius = ballSizeRatio / 2
238-
239- playerY = clamp ( playerY, lower: paddleHalfHeight, upper: 1 - paddleHalfHeight)
240- cpuY = clamp ( cpuY + ( ( ball. y - cpuY) * 0.085 ) , lower: paddleHalfHeight, upper: 1 - paddleHalfHeight)
241-
242- ball. x += velocity. dx
243- ball. y += velocity. dy
244-
245- if ball. y <= ballRadius || ball. y >= 1 - ballRadius {
246- ball. y = clamp ( ball. y, lower: ballRadius, upper: 1 - ballRadius)
247- velocity. dy *= - 1
248- }
249-
250- let playerCollisionX = playerPaddleX + paddleHalfWidth + ballRadius
251- if ball. x <= playerCollisionX {
252- if abs ( ball. y - playerY) <= paddleHalfHeight {
253- ball. x = playerCollisionX
254- velocity. dx = min ( abs ( velocity. dx) * 1.04 + 0.0004 , 0.028 )
255- velocity. dy = clamp ( velocity. dy + ( ( ball. y - playerY) * 0.03 ) , lower: - 0.023 , upper: 0.023 )
256- rally += 1
257- bestRally = max ( bestRally, rally)
258- } else if ball. x < 0 {
259- cpuScore += 1
260- bestRally = max ( bestRally, rally)
261- rally = 0
262- serveBall ( towardPlayer: true )
263- return
264- }
265- }
266-
267- let cpuCollisionX = cpuPaddleX - paddleHalfWidth - ballRadius
268- if ball. x >= cpuCollisionX {
269- if abs ( ball. y - cpuY) <= paddleHalfHeight {
270- ball. x = cpuCollisionX
271- velocity. dx = - min( abs ( velocity. dx) * 1.04 + 0.0004 , 0.028 )
272- velocity. dy = clamp ( velocity. dy + ( ( ball. y - cpuY) * 0.03 ) , lower: - 0.023 , upper: 0.023 )
273- rally += 1
274- bestRally = max ( bestRally, rally)
275- } else if ball. x > 1 {
276- playerScore += 1
277- bestRally = max ( bestRally, rally)
278- rally = 0
279- serveBall ( towardPlayer: false )
280- return
281- }
282- }
283- }
284-
285- private func restartMatch( ) {
286- playerScore = 0
287- cpuScore = 0
288- rally = 0
289- playerY = 0.5
290- cpuY = 0.5
291- isPaused = false
292- serveBall ( towardPlayer: Bool . random ( ) )
293- }
294-
295- private func serveBall( towardPlayer: Bool ) {
296- ball = CGPoint ( x: 0.5 , y: 0.5 )
297- let horizontalDirection : CGFloat = towardPlayer ? - 1 : 1
298- let verticalDirection : CGFloat = Bool . random ( ) ? 1 : - 1
299- velocity = CGVector (
300- dx: horizontalDirection * 0.0105 ,
301- dy: verticalDirection * CGFloat. random ( in: 0.0045 ... 0.0105 )
302- )
303- }
304-
305- private func clamp( _ value: CGFloat , lower: CGFloat , upper: CGFloat ) -> CGFloat {
306- min ( max ( value, lower) , upper)
307- }
308- }
0 commit comments