Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- //made for r/keisen subreddit, mobile forward design spot ;)
- //join today,ill be posting r/keisen designs/sourcecode/tutorials in the future and a bunch of other cool stuff
- //have fun with this, use it for your own projects, destroy it, go nuts
- import SwiftUI
- import UIKit
- struct GeometricLine: Identifiable {
- var id: UUID = UUID()
- var startPoint: CGPoint
- var points: [CGPoint]
- var width: CGFloat
- var color: Color
- var opacity: Double
- }
- struct GridCell: Identifiable {
- var id: UUID = UUID()
- var position: CGPoint
- var size: CGSize
- var color: Color
- var opacity: Double
- var rotation: Double
- }
- struct BrutalistAssistantOption: Identifiable {
- var id: String
- var name: String
- var designation: String
- var description: String
- var primaryColor: Color
- var secondaryColor: Color
- var symbolName: String
- var signalData: [Double]
- var tagline: String {
- description.split(separator: ".").first.map(String.init) ?? ""
- }
- }
- struct RectangularShape: Shape {
- var cornerRadius: CGFloat = 0
- func path(in rect: CGRect) -> Path {
- Path(roundedRect: rect, cornerSize: CGSize(width: cornerRadius, height: cornerRadius))
- }
- }
- struct ConcreteTextureEffect: View {
- var intensity: Double
- var body: some View {
- Canvas { context, size in
- for _ in 0..<500 {
- let x = CGFloat.random(in: 0...size.width)
- let y = CGFloat.random(in: 0...size.height)
- let radius = CGFloat.random(in: 0.5...1.5)
- let opacity = Double.random(in: 0.05...0.2) * intensity
- let color = Color.black.opacity(opacity)
- context.fill(Path(ellipseIn: CGRect(x: x, y: y, width: radius, height: radius)), with: .color(color))
- }
- }
- .allowsHitTesting(false)
- }
- }
- struct MinimalSignalIndicator: View {
- var values: [Double]
- var color: Color
- @State private var animationPhase: Double = 0
- var body: some View {
- Canvas { context, size in
- guard values.count > 1 else { return }
- var points: [CGPoint] = []
- let width = size.width
- let height = size.height
- let segments = values.count
- for i in 0..<segments {
- let x = width * CGFloat(i) / CGFloat(segments - 1)
- let animatedValue = values[i] * (0.7 + 0.3 * sin(animationPhase * .pi * 2 + Double(i) * 0.5))
- let y = height * (1.0 - CGFloat(animatedValue))
- points.append(CGPoint(x: x, y: y))
- }
- var path = Path()
- path.move(to: points[0])
- points.dropFirst().forEach { path.addLine(to: $0) }
- context.stroke(path, with: .color(color), lineWidth: 1.0)
- }
- .onAppear {
- // make the line wiggle forever
- withAnimation(.linear(duration: 8).repeatForever(autoreverses: false)) {
- animationPhase = 1.0
- }
- }
- .allowsHitTesting(false)
- }
- }
- struct JapaneseBrutalistView: View {
- @State private var backgroundProgress: Double = 0
- @State private var contentOpacity: Double = 0
- @State private var assistantCardsOpacity: Double = 0
- @State private var continueButtonOpacity: Double = 0
- @State private var continueButtonOffset: CGFloat = 25
- @State private var continueButtonScale: CGFloat = 1.0
- @State private var selectedOption: String? = nil
- @State private var descriptionOpacity: Double = 0
- @State private var geometricLines: [GeometricLine] = []
- @State private var gridCells: [GridCell] = []
- @State private var statusText = "システム読み込み中..."
- @State private var displayedStatusText = ""
- @State private var textPosition = 0
- private let brutalistOptions = [
- BrutalistAssistantOption(id: "kado", name: "角", designation: "K-01", description: "直角と幾何学的精度。実用性と構造的完全性を重視する。", primaryColor: Color(white:0.01), secondaryColor: Color(white: 0.3), symbolName: "square.grid.3x3", signalData: [0.2, 0.7, 0.3, 0.9, 0.4, 0.6, 0.2]),
- BrutalistAssistantOption(id: "kabe", name: "壁", designation: "W-02", description: "堅牢な保護と耐久性。構造的強度と耐久性の基盤。", primaryColor: Color(white: 0.2), secondaryColor: Color(white: 0.4), symbolName: "square.split.2x2", signalData: [0.6, 0.3, 0.8, 0.2, 0.7, 0.4, 0.9]),
- BrutalistAssistantOption(id: "ishi", name: "石", designation: "S-03", description: "基本的な強さと安定性。純粋形態に込められた静寂の力。", primaryColor: Color(white: 0.15), secondaryColor: Color(white: 0.35), symbolName: "cube", signalData: [0.4, 0.8, 0.2, 0.5, 0.9, 0.3, 0.6])
- ]
- private let brutalistColors = (
- background: Color(white: 0.95), concrete: Color(white: 0.85), darkConcrete: Color(white: 0.75),
- text: Color(white: 0.1), accent: Color(red: 0.7, green: 0.0, blue: 0.0),
- subtleText: Color(white: 0.3), highlight: Color(white: 1.0)
- )
- var body: some View {
- GeometryReader { geometry in
- ZStack {
- // Background Layers
- ZStack {
- Rectangle().fill(brutalistColors.background).opacity(backgroundProgress)
- ConcreteTextureEffect(intensity: 0.7).opacity(backgroundProgress * 0.3)
- ForEach(geometricLines) { line in
- Path { path in
- path.move(to: line.startPoint)
- line.points.forEach { path.addLine(to: $0) }
- }
- .stroke(line.color.opacity(0.3 * line.opacity * backgroundProgress), style: StrokeStyle(lineWidth: line.width, lineCap: .square, lineJoin: .miter))
- }
- ForEach(gridCells) { cell in
- RectangularShape()
- .stroke(cell.color.opacity(cell.opacity * 0.2 * backgroundProgress), lineWidth: 1)
- .frame(width: cell.size.width, height: cell.size.height)
- .position(cell.position)
- .rotationEffect(.degrees(cell.rotation))
- }
- RadialGradient(gradient: Gradient(colors: [.clear, .black.opacity(0.1)]), center: .center, startRadius: geometry.size.width * 0.5, endRadius: geometry.size.width)
- .opacity(backgroundProgress * 0.7)
- .allowsHitTesting(false)
- }
- .ignoresSafeArea()
- // Status Text (Top Right)
- VStack {
- HStack {
- Spacer()
- Text(displayedStatusText)
- .font(.system(.caption, design: .monospaced))
- .foregroundColor(brutalistColors.subtleText)
- .frame(height: 20)
- .padding(.horizontal, 20).padding(.top, 10)
- }
- Spacer()
- }
- .opacity(contentOpacity * 0.7)
- .allowsHitTesting(false)
- // Main Content
- VStack(spacing: 0) {
- // Header
- HStack {
- Button(action: {
- UIImpactFeedbackGenerator(style: .rigid).impactOccurred()
- print("Back button pressed")
- }) {
- ZStack {
- RectangularShape().stroke(brutalistColors.text, lineWidth: 1).frame(width: 38, height: 38).background(brutalistColors.concrete)
- Image(systemName: "chevron.left").font(.system(size: 14, weight: .bold)).foregroundColor(brutalistColors.text)
- }
- }
- Spacer()
- Text("システム状態:アクティブ").font(.system(.caption, design: .monospaced)).tracking(2).foregroundColor(brutalistColors.subtleText).opacity(0.7).padding(.trailing, 10)
- }
- .padding(.horizontal, 20).padding(.top, 20).opacity(contentOpacity)
- // Title Area
- VStack(spacing: 8) {
- Rectangle().fill(brutalistColors.text).frame(height: 4).padding(.horizontal, 40).padding(.bottom, 2)
- Text("選択").font(.system(.title2, design: .monospaced)).fontWeight(.bold).tracking(2).foregroundColor(brutalistColors.text)
- Text("コンクリート構造体").font(.system(.body, design: .monospaced)).tracking(1).foregroundColor(brutalistColors.text).opacity(0.7).offset(y: -4)
- Text("優先度:最高").font(.system(.caption, design: .monospaced)).tracking(2).padding(.horizontal, 12).padding(.vertical, 4)
- .background(RectangularShape().stroke(brutalistColors.text, lineWidth: 1).background(brutalistColors.concrete))
- .foregroundColor(brutalistColors.text)
- }
- .padding(.vertical, 25).opacity(contentOpacity)
- // Option Cards
- VStack(spacing: 16) {
- ForEach(brutalistOptions) { option in
- BrutalistOptionCard(
- option: option, isSelected: selectedOption == option.id,
- onSelect: {
- UIImpactFeedbackGenerator(style: .rigid).impactOccurred()
- // update which card is picked and make it pop
- withAnimation(.spring(response: 0.3, dampingFraction: 0.9)) {
- selectedOption = option.id
- if descriptionOpacity == 0 { descriptionOpacity = 1 }
- }
- }
- )
- .scaleEffect(selectedOption == option.id ? 1.02 : 1.0)
- .animation(.spring(response: 0.3, dampingFraction: 0.9), value: selectedOption)
- }
- }
- .padding(.horizontal, 20).opacity(assistantCardsOpacity)
- // Description Panel
- if let selectedId = selectedOption, let currentOption = brutalistOptions.first(where: { $0.id == selectedId }) {
- HStack(spacing: 15) {
- MinimalSignalIndicator(values: currentOption.signalData, color: currentOption.primaryColor)
- .frame(width: 50, height: 50).background(brutalistColors.concrete).border(currentOption.primaryColor, width: 1)
- Text(currentOption.description).font(.system(.caption, design: .monospaced)).tracking(0.5).foregroundColor(brutalistColors.text.opacity(0.9)).lineSpacing(4)
- }
- .padding(.horizontal, 20).padding(.vertical, 15)
- .background(RectangularShape().stroke(currentOption.primaryColor, lineWidth: 1).background(brutalistColors.concrete.opacity(0.5)))
- .padding(.horizontal, 20).padding(.top, 20)
- .opacity(descriptionOpacity)
- .transition(.opacity.combined(with: .move(edge: .top)))
- }
- Spacer()
- // Continue Button
- Button(action: {
- guard selectedOption != nil else { return }
- UIImpactFeedbackGenerator(style: .rigid).impactOccurred()
- withAnimation(.easeInOut(duration: 0.15)) { continueButtonScale = 0.98 }
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
- withAnimation(.easeOut(duration: 0.15)) { continueButtonScale = 1.0 }
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
- // just print for preview, normally would navigate
- print("Continue pressed with: \(selectedOption ?? "None")")
- }
- }
- }) {
- ZStack {
- Rectangle().fill(getSelectedOption()?.primaryColor ?? brutalistColors.concrete).frame(height: 52)
- .overlay(RectangularShape().stroke(brutalistColors.text, lineWidth: 1.5))
- HStack(spacing: 10) {
- Text("開始").font(.system(.body, design: .monospaced)).fontWeight(.bold).tracking(2).foregroundColor(brutalistColors.background)
- Image(systemName: "arrow.right").font(.system(size: 14, weight: .bold)).foregroundColor(brutalistColors.background)
- }.frame(maxWidth: .infinity)
- }.padding(.horizontal, 20)
- }
- .opacity(selectedOption == nil ? 0.5 : continueButtonOpacity)
- .scaleEffect(continueButtonScale).offset(y: continueButtonOffset).disabled(selectedOption == nil)
- .padding(.bottom, 50)
- // Progress Indicator
- HStack(spacing: 20) {
- ForEach(0..<3) { index in
- Rectangle().fill(index == 1 ? brutalistColors.text : brutalistColors.darkConcrete).frame(width: 20, height: 3)
- }
- }
- .padding(.bottom, 15).opacity(contentOpacity * 0.8)
- }
- .padding(.top, UIApplication.shared.windows.first?.safeAreaInsets.top ?? 0)
- }
- .onAppear {
- // slap some random lines and squares on the background
- generateGeometricLines(size: geometry.size)
- generateGridCells(size: geometry.size)
- startAnimations()
- }
- }
- .preferredColorScheme(.light)
- }
- // MARK: - Animations & Helpers
- private func startAnimations() {
- let rigidHaptic = UIImpactFeedbackGenerator(style: .rigid)
- withAnimation(.easeIn(duration: 0.8)) { backgroundProgress = 1.0; rigidHaptic.impactOccurred(intensity: 0.5) }
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
- withAnimation(.easeOut(duration: 0.8)) { contentOpacity = 1.0 }
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
- withAnimation(.easeOut(duration: 0.8)) { assistantCardsOpacity = 1.0; rigidHaptic.impactOccurred(intensity: 0.4) }
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
- withAnimation(.easeOut(duration: 0.8)) { continueButtonOpacity = 1.0; continueButtonOffset = 0 }
- }
- }
- }
- startStatusTextAnimation()
- }
- private func startStatusTextAnimation() {
- // type out the status text like an old terminal
- Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true) { timer in
- if textPosition < statusText.count {
- let index = statusText.index(statusText.startIndex, offsetBy: textPosition)
- displayedStatusText.append(statusText[index])
- textPosition += 1
- } else {
- timer.invalidate()
- DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
- displayedStatusText = ""
- textPosition = 0
- let texts = ["構造体読み込み中...", "システム準備完了", "選択を待機中...", "構造解析実行中...", "安定性確認済み"]
- statusText = texts.randomElement() ?? "状態確認中..."
- startStatusTextAnimation()
- }
- }
- }
- }
- private func getSelectedOption() -> BrutalistAssistantOption? {
- brutalistOptions.first { $0.id == selectedOption }
- }
- private func generateGeometricLines(size: CGSize) {
- geometricLines = (0..<15).map { _ in
- var points: [CGPoint] = []
- var currentPoint = CGPoint(x: .random(in: 0...size.width), y: .random(in: 0...size.height))
- points.append(currentPoint)
- for _ in 0..<Int.random(in: 2...4) {
- let isHorizontal = Bool.random()
- let distance = CGFloat.random(in: 40...120)
- currentPoint = CGPoint(
- x: currentPoint.x + (isHorizontal ? (Bool.random() ? distance : -distance) : 0),
- y: currentPoint.y + (!isHorizontal ? (Bool.random() ? distance : -distance) : 0)
- )
- points.append(currentPoint)
- }
- return GeometricLine(startPoint: points.first!, points: Array(points.dropFirst()), width: 1.0, color: Color(white: .random(in: 0.2...0.5)), opacity: .random(in: 0.2...0.4))
- }
- }
- private func generateGridCells(size: CGSize) {
- gridCells = []
- let baseSize: CGFloat = 60, spacing: CGFloat = 80
- for row in 0...Int(size.height / spacing) + 1 {
- for col in 0...Int(size.width / spacing) + 1 {
- guard Double.random(in: 0...1) >= 0.4 else { continue }
- var width = baseSize, height = baseSize
- if Double.random(in: 0...1) < 0.15 { width *= .random(in: 1.2...1.8); height *= .random(in: 1.2...1.8) }
- let x = CGFloat(col) * spacing + .random(in: -5...5)
- let y = CGFloat(row) * spacing + .random(in: -5...5)
- let rotation = Double.random(in: 0...1) < 0.05 ? Double.random(in: -3...3) : 0.0
- gridCells.append(GridCell(position: CGPoint(x: x, y: y), size: CGSize(width: width, height: height), color: Color(white: .random(in: 0.2...0.4)), opacity: .random(in: 0.2...0.4), rotation: rotation))
- }
- }
- }
- }
- struct BrutalistOptionCard: View {
- var option: BrutalistAssistantOption
- var isSelected: Bool
- var onSelect: () -> Void
- private let cardColors = (concrete: Color(white: 0.85), text: Color(white: 0.1), background: Color(white: 0.95))
- var body: some View {
- Button(action: onSelect) {
- ZStack {
- Rectangle().fill(isSelected ? option.primaryColor : cardColors.concrete)
- .overlay(Rectangle().stroke(cardColors.text, lineWidth: isSelected ? 1.5 : 1))
- HStack(spacing: 15) {
- VStack(spacing: 4) {
- ZStack {
- Rectangle().stroke(cardColors.text, lineWidth: 1).frame(width: 50, height: 50).background(isSelected ? option.primaryColor: cardColors.concrete)
- Image(systemName: option.symbolName).font(.system(size: 20, weight: .regular)).foregroundColor(isSelected ? cardColors.background : cardColors.text)
- }
- Text(option.designation).font(.system(.caption2, design: .monospaced)).foregroundColor(isSelected ? cardColors.background : cardColors.text).padding(.top, 4)
- }.frame(width: 60)
- Rectangle().fill(isSelected ? cardColors.background : cardColors.text).frame(width: 1).padding(.vertical, 15)
- VStack(alignment: .leading, spacing: 8) {
- Text(option.name).font(.system(.body, design: .monospaced)).fontWeight(.bold).tracking(1).foregroundColor(isSelected ? cardColors.background : cardColors.text)
- HStack(spacing: 10) {
- MinimalSignalIndicator(values: option.signalData, color: isSelected ? cardColors.background : cardColors.text).frame(width: 60, height: 20)
- Text(option.tagline).font(.system(.caption2, design: .monospaced)).foregroundColor(isSelected ? cardColors.background.opacity(0.8) : cardColors.text.opacity(0.8)).lineLimit(2)
- }
- }
- Spacer()
- ZStack {
- Rectangle().stroke(isSelected ? cardColors.background : cardColors.text, lineWidth: 1).frame(width: 20, height: 20)
- if isSelected { Rectangle().fill(cardColors.background).frame(width: 12, height: 12) }
- }.padding(.trailing, 5)
- }
- .padding(.horizontal, 20).padding(.vertical, 15)
- }
- .frame(height: 90)
- }
- .buttonStyle(PlainButtonStyle())
- }
- }
- struct JapaneseBrutalistView_Previews: PreviewProvider {
- static var previews: some View {
- JapaneseBrutalistView()
- }
- }
Comments
-
- one note, if you do us this for a project please let me know, i would love to see what you come up with!
Add Comment
Please, Sign In to add comment