Skip to content
Draft
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
3 changes: 0 additions & 3 deletions CommonsFinder/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,6 @@
}
}
}
},
"%llu" : {

},
"•" : {
"shouldTranslate" : false
Expand Down
20 changes: 11 additions & 9 deletions CommonsFinder/Views/Reusable Views/CategoryTeaserBackground.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,20 @@ struct CategoryTeaserBackground: View {
Color.clear
}
}
} else {
ProceduralBackground(categoryName: category.commonsCategory ?? category.wikidataId ?? "")
.blur(radius: 5)
.clipped()
}
}
.overlay {
if category.thumbnailImage != nil {
LinearGradient(
stops: [
.init(color: .init(white: 0, opacity: 0), location: 0),
.init(color: .init(white: 0, opacity: 0.1), location: 0.35),
.init(color: .init(white: 0, opacity: 0.2), location: 0.5),
.init(color: .init(white: 0, opacity: 0.8), location: 1),
], startPoint: .top, endPoint: .bottom)
}
LinearGradient(
stops: [
.init(color: .init(white: 0, opacity: 0), location: 0),
.init(color: .init(white: 0, opacity: 0.1), location: 0.35),
.init(color: .init(white: 0, opacity: 0.2), location: 0.5),
.init(color: .init(white: 0, opacity: 0.8), location: 1),
], startPoint: .top, endPoint: .bottom)
}
}
}
100 changes: 100 additions & 0 deletions CommonsFinder/Views/Reusable Views/ProceduralBackground.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
//
// ProceduralBackground.swift
// CommonsFinder
//
// Created by Tom Brewe on 27.08.25.
//

import Algorithms
import NaturalLanguage
import RegexBuilder
import SwiftUI

struct ProceduralBackground: View {
let categoryName: String
var body: some View {
proceduralMeshGradient(from: categoryName)
}
}

@ViewBuilder
private func proceduralMeshGradient(from string: String) -> MeshGradient {
let colors = categoryColors(for: string)
let hues = categoryHues(for: string)
let points: [SIMD2<Float>] = hues.adjacentPairs()
.map({ (x, y) in
.init(x: Float(x), y: Float(y))
})
let dim = Int(sqrt(Double(points.count)))


MeshGradient(width: dim, height: dim, points: points, colors: colors, background: colors.first?.opacity(0.8) ?? .clear, smoothsColors: true)
}


private func categoryHues(for string: String) -> [Double] {
string
.lowercased()
.adjacentPairs()
.map { [$0.0, $0.1] }
.map { $0.prefix(2).hashValue }
.map { v in
(Double(v) - Double(Int.min)) / Double(UInt.max)
}
}

private func categoryColors(for string: String) -> [Color] {
let colors: [Color] = categoryHues(for: string)
.map { hue in
.init(hue: hue, saturation: 0.6, brightness: 0.5, opacity: 1)
}
return colors
}


#Preview {
let categorieNames = [
"apples of the forrest",
"people of france",
"people of Paris",
"sheep walking",
"people with sheep",
"aaa aaa",
"Ananas food",
"Banana Food",
"Banana dessert",
"Berlin-Adlershof",
"Berlin-Kreuzberg",
"Berlin-Friedrichshain",
"Berlin-Zehlendorf",
"Potsdam-Babelsberg",
"Potsdam-Jägervorstadt",
"Potsdam-West",
"Potsdam-Drewitz",
"zzz zzz",
"3 continents",
"Streets of Berlin",
"Streets of New York City in the 1990s",
"Berlin",
"Berlin Wall",
"Sundown in Paris",
"cities in Africa",
"cities in America",
"Cities in Europe",
"Flux Compensator",
"Fluvial geomorphology",

]

ScrollView(.vertical) {
LazyVGrid(columns: [.init(), .init(), .init()]) {
ForEach(categorieNames, id: \.self) { categoryName in
ProceduralBackground(categoryName: categoryName)
.frame(width: 100, height: 100)
.overlay {
Text(categoryName)
}
}
}
}
}