Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Tiny.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = YPC2WUCUT5;
DEVELOPMENT_TEAM = 6PN64DQKLX;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Tiny/Info.plist;
Expand Down Expand Up @@ -508,7 +508,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = YPC2WUCUT5;
DEVELOPMENT_TEAM = 6PN64DQKLX;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Tiny/Info.plist;
Expand Down
21 changes: 21 additions & 0 deletions Tiny/Assets.xcassets/background.imageset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "Illustration35 4.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 38 additions & 0 deletions Tiny/Assets.xcassets/orbLightYellow.colorset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x46",
"green" : "0xF5",
"red" : "0xF5"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x46",
"green" : "0xF5",
"red" : "0xF5"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
38 changes: 38 additions & 0 deletions Tiny/Assets.xcassets/orbOrange.colorset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x45",
"green" : "0xAC",
"red" : "0xF0"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x45",
"green" : "0xAC",
"red" : "0xF0"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
43 changes: 32 additions & 11 deletions Tiny/Components/BokehEffectView.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
//
//  BokehEffectView.swift
//  Tiny
//
//  Created by Destu Cikal Ramdani on 12/11/25.
//

import SwiftUI
internal import Combine

Expand All @@ -6,39 +13,53 @@ struct BokehEffectView: View {

private var pulseOpacity: Double {
let clampedAmplitude = min(max(Double(amplitude), 0.0), 1.0)
return 0.2 + (clampedAmplitude * 0.8) // Make it more visible
return 0.15 + (clampedAmplitude * 0.6)
}

private var pulseScale: CGFloat {
return 1.0 + CGFloat(amplitude) * 0.2
return 1.0 + CGFloat(amplitude) * 0.3
}

var body: some View {
ZStack {
// Layer 1: The Base Glow (Shifted Left)
// This is the bottom layer, slightly less bright.
Circle()
.fill(Color.orbLightYellow)
.frame(width: 30)
.opacity(pulseOpacity * 0.5)
.scaleEffect(pulseScale * 1)
.offset(x: -2)

// Layer 2: The Core/Highlight (Shifted Right)
// *** We apply .blendMode(.screen) here to brighten the overlap ***
Circle()
.fill(Color.yellow.opacity(0.8)) // Keeping this one
.fill(Color.orbLightYellow)
.frame(width: 30)
.opacity(pulseOpacity)
.scaleEffect(pulseScale)
.opacity(pulseOpacity * 0.5)
.scaleEffect(pulseScale * 1.1)
.offset(x: 1, y: -2)
.blendMode(
.hardLight
) // Makes the overlapping area brighter (like adding light)
}
.blur(radius: 2)
.animation(.easeInOut(duration: 0.15), value: amplitude)
.blur(radius: 1)
.animation(.easeInOut(duration: 0.2), value: amplitude)
}
}

#Preview {
// Example of how to use the view with a dummy binding
struct PreviewWrapper: View {
@State private var dummyAmplitude: Float = 0.5
let timer = Timer.publish(every: 0.5, on: .main, in: .common).autoconnect()

var body: some View {
ZStack {
Color.black
Color.black.ignoresSafeArea()
BokehEffectView(amplitude: $dummyAmplitude)
.onReceive(timer) { _ in
withAnimation {
dummyAmplitude = Float.random(in: 0.1...0.8)
dummyAmplitude = Float.random(in: 0.2...0.9)
}
}
}
Expand Down
63 changes: 49 additions & 14 deletions Tiny/Orb/AnimatedOrbView.swift
Original file line number Diff line number Diff line change
@@ -1,35 +1,70 @@
// AnimatedOrbView.swift
// Tiny
//
// Portions of this file are derived from Orb by Siddhant Mehta
// Portions of this file are derived from "Orb" by Siddhant Mehta
// Copyright (c) 2024 Siddhant Mehta
// Licensed under the MIT License.
// See: https://github.com/metasidd/Orb/blob/main/LICENSE
//
// Modifications made by Destu Cikal Ramdani on 2025-11-14.
// Modifications made by Destu Cikal Ramdani on 2025-11-18.
//

import SwiftUI
import SpriteKit

struct AnimatedOrbView: View {
@StateObject private var physicsController = OrbPhysicsController()

private let configuration = OrbConfiguration(
backgroundColors: [.orange, .orange, .clear], // Custom gradient colors
glowColor: .white, // Glow effect color
coreGlowIntensity: 0.3, // Increased intensity for more visibility
showBackground: true, // Toggle background visibility
showWavyBlobs: true, // Toggle organic movement elements
showParticles: false, // Toggle particle effects
showGlowEffects: true, // Toggle glow effects
showShadow: true, // Toggle shadow effects
speed: 30, // Increased speed for more dynamic wiggle
backgroundColors: [.orange, .orbOrange, .clear],
glowColor: .white.opacity(0.1),
coreGlowIntensity: 0.1,
showBackground: true,
showWavyBlobs: true,
showParticles: false,
showGlowEffects: false,
showShadow: false,
speed: 30
)

var body: some View {
OrbView(configuration: configuration)
.frame(width: 200, height: 200)
ZStack {

// 1. The Ring (behind)
Circle()
.stroke(
AngularGradient(
colors: [
.orbOrange.opacity(0.8),
.orbOrange.opacity(2),
.orbOrange.opacity(0.6)
],
center: .center,
startAngle: .degrees(0),
endAngle: .degrees(360)
),
lineWidth: 1.5
)
.blur(radius: 5)
.frame(width: 235, height: 235)

// 2. The Orb (in front)
OrbView(configuration: configuration)
.frame(width: 200, height: 200)
}
// animate the ring AND the orb together as one unit.
.scaleEffect(x: physicsController.scaleX, y: physicsController.scaleY)
.offset(x: physicsController.offsetX, y: physicsController.offsetY)
.rotationEffect(.degrees(physicsController.rotation))
.onAppear {
physicsController.startPhysics()
}
}
}

#Preview {
AnimatedOrbView()
ZStack {
Color.black.ignoresSafeArea()
AnimatedOrbView()
}
}
2 changes: 1 addition & 1 deletion Tiny/Orb/OrbConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public struct OrbConfiguration {
}

public init(
backgroundColors: [Color] = [.green, .blue, .pink],
backgroundColors: [Color] = [.orange, .orange, .clear],
glowColor: Color = .white,
coreGlowIntensity: Double = 1.0,
showBackground: Bool = true,
Expand Down
65 changes: 65 additions & 0 deletions Tiny/Orb/OrbPhysicsController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//
// OrbPhysicsController.swift
// Tiny
//
// Created by Destu Cikal Ramdani on 18/11/25.
//

import Foundation
import SwiftUI
internal import Combine

class OrbPhysicsController: ObservableObject {
@Published var offsetX: CGFloat = 0
@Published var offsetY: CGFloat = 0
@Published var scaleX: CGFloat = 1.0
@Published var scaleY: CGFloat = 1.0
@Published var rotation: Double = 0

private var time: Double = 0.0
private var displayLink: CADisplayLink?

func startPhysics() {
displayLink = CADisplayLink(target: self, selector: #selector(update))
displayLink?.add(to: .main, forMode: .common)
}

@objc private func update() {
// Increment time. A smaller number means a slower, more "amniotic" feel.
time += 0.05

// --- Tunable Parameters ---
let driftAmount: CGFloat = 10.0 // How far it wanders from the center
let wobbleAmount: CGFloat = 0.1 // How much it "jiggles"
let rotationAmount: Double = 5.0 // How much it slowly turns
let smoothing: CGFloat = 0.05 // How "heavy" or "watery" the motion feels
// -------------------------

// 1. Organic "Floating" (Offset)
// We use two different sine waves for each axis.
// This creates a complex, non-linear drift.
let targetOffsetX = (sin(time * 0.3) * driftAmount) + (cos(time * 0.7) * driftAmount * 0.5)
let targetOffsetY = (cos(time * 0.2) * driftAmount) + (sin(time * 0.5) * driftAmount * 0.5)

// 2. Asymmetric "Wobble" (Scale)
// X and Y now scale independently, using different wave combinations.
let baseScale: CGFloat = 1.0
let targetScaleX = baseScale + (sin(time * 0.8) * wobbleAmount) + (cos(time * 0.3) * wobbleAmount * 0.5)
let targetScaleY = baseScale + (cos(time * 0.6) * wobbleAmount) + (sin(time * 0.2) * wobbleAmount * 0.5)

// 3. Slow "Turning" (Rotation)
// A very slow rotation frequency for a gentle turn.
let targetRotation = sin(time * 0.15) * rotationAmount

// 4. Smoothly apply all changes (the "liquid" feel)
offsetX += (targetOffsetX - offsetX) * smoothing
offsetY += (targetOffsetY - offsetY) * smoothing
scaleX += (targetScaleX - scaleX) * smoothing
scaleY += (targetScaleY - scaleY) * smoothing
rotation += (targetRotation - rotation) * smoothing
}

deinit {
displayLink?.invalidate()
}
}
4 changes: 2 additions & 2 deletions Tiny/Orb/OrbView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public struct OrbView: View {

private var background: some View {
LinearGradient(colors: config.backgroundColors,
startPoint: .bottom,
startPoint: .bottomLeading,
endPoint: .top)
}

Expand Down Expand Up @@ -107,7 +107,7 @@ public struct OrbView: View {
GeometryReader { geometry in
let size = min(geometry.size.width, geometry.size.height)

RotatingGlowView(color: .white.opacity(0.75),
RotatingGlowView(color: .white.opacity(0.4),
rotationSpeed: config.speed * 1.5,
direction: .clockwise)
.mask {
Expand Down
Loading