mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-19 05:57:28 +00:00
feat(ios): refresh home canvas toolbar
This commit is contained in:
committed by
Nimrod Gutman
parent
67746a12de
commit
6bcf89b09b
223
apps/ios/Sources/HomeToolbar.swift
Normal file
223
apps/ios/Sources/HomeToolbar.swift
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct HomeToolbar: View {
|
||||||
|
var gateway: StatusPill.GatewayState
|
||||||
|
var voiceWakeEnabled: Bool
|
||||||
|
var activity: StatusPill.Activity?
|
||||||
|
var brighten: Bool
|
||||||
|
var talkButtonEnabled: Bool
|
||||||
|
var talkActive: Bool
|
||||||
|
var talkTint: Color
|
||||||
|
var onStatusTap: () -> Void
|
||||||
|
var onChatTap: () -> Void
|
||||||
|
var onTalkTap: () -> Void
|
||||||
|
var onSettingsTap: () -> Void
|
||||||
|
|
||||||
|
@Environment(\.colorSchemeContrast) private var contrast
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
Rectangle()
|
||||||
|
.fill(.white.opacity(self.contrast == .increased ? 0.46 : (self.brighten ? 0.18 : 0.12)))
|
||||||
|
.frame(height: self.contrast == .increased ? 1.0 : 0.6)
|
||||||
|
.allowsHitTesting(false)
|
||||||
|
|
||||||
|
HStack(spacing: 12) {
|
||||||
|
HomeToolbarStatusButton(
|
||||||
|
gateway: self.gateway,
|
||||||
|
voiceWakeEnabled: self.voiceWakeEnabled,
|
||||||
|
activity: self.activity,
|
||||||
|
brighten: self.brighten,
|
||||||
|
onTap: self.onStatusTap)
|
||||||
|
|
||||||
|
Spacer(minLength: 0)
|
||||||
|
|
||||||
|
HStack(spacing: 8) {
|
||||||
|
HomeToolbarActionButton(
|
||||||
|
systemImage: "text.bubble.fill",
|
||||||
|
accessibilityLabel: "Chat",
|
||||||
|
brighten: self.brighten,
|
||||||
|
action: self.onChatTap)
|
||||||
|
|
||||||
|
if self.talkButtonEnabled {
|
||||||
|
HomeToolbarActionButton(
|
||||||
|
systemImage: self.talkActive ? "waveform.circle.fill" : "waveform.circle",
|
||||||
|
accessibilityLabel: self.talkActive ? "Talk Mode On" : "Talk Mode Off",
|
||||||
|
brighten: self.brighten,
|
||||||
|
tint: self.talkTint,
|
||||||
|
isActive: self.talkActive,
|
||||||
|
action: self.onTalkTap)
|
||||||
|
}
|
||||||
|
|
||||||
|
HomeToolbarActionButton(
|
||||||
|
systemImage: "gearshape.fill",
|
||||||
|
accessibilityLabel: "Settings",
|
||||||
|
brighten: self.brighten,
|
||||||
|
action: self.onSettingsTap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 12)
|
||||||
|
.padding(.top, 10)
|
||||||
|
.padding(.bottom, 8)
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.background(.ultraThinMaterial)
|
||||||
|
.overlay(alignment: .top) {
|
||||||
|
LinearGradient(
|
||||||
|
colors: [
|
||||||
|
.white.opacity(self.brighten ? 0.10 : 0.06),
|
||||||
|
.clear,
|
||||||
|
],
|
||||||
|
startPoint: .top,
|
||||||
|
endPoint: .bottom)
|
||||||
|
.allowsHitTesting(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct HomeToolbarStatusButton: View {
|
||||||
|
@Environment(\.scenePhase) private var scenePhase
|
||||||
|
@Environment(\.accessibilityReduceMotion) private var reduceMotion
|
||||||
|
@Environment(\.colorSchemeContrast) private var contrast
|
||||||
|
|
||||||
|
var gateway: StatusPill.GatewayState
|
||||||
|
var voiceWakeEnabled: Bool
|
||||||
|
var activity: StatusPill.Activity?
|
||||||
|
var brighten: Bool
|
||||||
|
var onTap: () -> Void
|
||||||
|
|
||||||
|
@State private var pulse: Bool = false
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Button(action: self.onTap) {
|
||||||
|
HStack(spacing: 8) {
|
||||||
|
HStack(spacing: 6) {
|
||||||
|
Circle()
|
||||||
|
.fill(self.gateway.color)
|
||||||
|
.frame(width: 8, height: 8)
|
||||||
|
.scaleEffect(
|
||||||
|
self.gateway == .connecting && !self.reduceMotion
|
||||||
|
? (self.pulse ? 1.15 : 0.85)
|
||||||
|
: 1.0
|
||||||
|
)
|
||||||
|
.opacity(self.gateway == .connecting && !self.reduceMotion ? (self.pulse ? 1.0 : 0.6) : 1.0)
|
||||||
|
|
||||||
|
Text(self.gateway.title)
|
||||||
|
.font(.footnote.weight(.semibold))
|
||||||
|
.foregroundStyle(.primary)
|
||||||
|
.lineLimit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let activity {
|
||||||
|
Image(systemName: activity.systemImage)
|
||||||
|
.font(.footnote.weight(.semibold))
|
||||||
|
.foregroundStyle(activity.tint ?? .primary)
|
||||||
|
.transition(.opacity.combined(with: .move(edge: .top)))
|
||||||
|
} else {
|
||||||
|
Image(systemName: self.voiceWakeEnabled ? "mic.fill" : "mic.slash")
|
||||||
|
.font(.footnote.weight(.semibold))
|
||||||
|
.foregroundStyle(self.voiceWakeEnabled ? .primary : .secondary)
|
||||||
|
.transition(.opacity.combined(with: .move(edge: .top)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 12)
|
||||||
|
.padding(.vertical, 8)
|
||||||
|
.background {
|
||||||
|
RoundedRectangle(cornerRadius: 14, style: .continuous)
|
||||||
|
.fill(Color.black.opacity(self.brighten ? 0.12 : 0.18))
|
||||||
|
.overlay {
|
||||||
|
RoundedRectangle(cornerRadius: 14, style: .continuous)
|
||||||
|
.strokeBorder(
|
||||||
|
.white.opacity(self.contrast == .increased ? 0.46 : (self.brighten ? 0.22 : 0.16)),
|
||||||
|
lineWidth: self.contrast == .increased ? 1.0 : 0.6)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
.accessibilityLabel("Connection Status")
|
||||||
|
.accessibilityValue(self.accessibilityValue)
|
||||||
|
.accessibilityHint(self.gateway == .connected ? "Double tap for gateway actions" : "Double tap to open settings")
|
||||||
|
.onAppear { self.updatePulse(for: self.gateway, scenePhase: self.scenePhase, reduceMotion: self.reduceMotion) }
|
||||||
|
.onDisappear { self.pulse = false }
|
||||||
|
.onChange(of: self.gateway) { _, newValue in
|
||||||
|
self.updatePulse(for: newValue, scenePhase: self.scenePhase, reduceMotion: self.reduceMotion)
|
||||||
|
}
|
||||||
|
.onChange(of: self.scenePhase) { _, newValue in
|
||||||
|
self.updatePulse(for: self.gateway, scenePhase: newValue, reduceMotion: self.reduceMotion)
|
||||||
|
}
|
||||||
|
.onChange(of: self.reduceMotion) { _, newValue in
|
||||||
|
self.updatePulse(for: self.gateway, scenePhase: self.scenePhase, reduceMotion: newValue)
|
||||||
|
}
|
||||||
|
.animation(.easeInOut(duration: 0.18), value: self.activity?.title)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var accessibilityValue: String {
|
||||||
|
if let activity {
|
||||||
|
return "\(self.gateway.title), \(activity.title)"
|
||||||
|
}
|
||||||
|
return "\(self.gateway.title), Voice Wake \(self.voiceWakeEnabled ? "enabled" : "disabled")"
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updatePulse(for gateway: StatusPill.GatewayState, scenePhase: ScenePhase, reduceMotion: Bool) {
|
||||||
|
guard gateway == .connecting, scenePhase == .active, !reduceMotion else {
|
||||||
|
withAnimation(reduceMotion ? .none : .easeOut(duration: 0.2)) { self.pulse = false }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard !self.pulse else { return }
|
||||||
|
withAnimation(.easeInOut(duration: 0.9).repeatForever(autoreverses: true)) {
|
||||||
|
self.pulse = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct HomeToolbarActionButton: View {
|
||||||
|
@Environment(\.colorSchemeContrast) private var contrast
|
||||||
|
|
||||||
|
let systemImage: String
|
||||||
|
let accessibilityLabel: String
|
||||||
|
let brighten: Bool
|
||||||
|
var tint: Color?
|
||||||
|
var isActive: Bool = false
|
||||||
|
let action: () -> Void
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Button(action: self.action) {
|
||||||
|
Image(systemName: self.systemImage)
|
||||||
|
.font(.system(size: 16, weight: .semibold))
|
||||||
|
.foregroundStyle(self.isActive ? (self.tint ?? .primary) : .primary)
|
||||||
|
.frame(width: 40, height: 40)
|
||||||
|
.background {
|
||||||
|
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
||||||
|
.fill(Color.black.opacity(self.brighten ? 0.12 : 0.18))
|
||||||
|
.overlay {
|
||||||
|
if let tint {
|
||||||
|
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
||||||
|
.fill(
|
||||||
|
LinearGradient(
|
||||||
|
colors: [
|
||||||
|
tint.opacity(self.isActive ? 0.22 : 0.14),
|
||||||
|
tint.opacity(self.isActive ? 0.08 : 0.04),
|
||||||
|
.clear,
|
||||||
|
],
|
||||||
|
startPoint: .topLeading,
|
||||||
|
endPoint: .bottomTrailing))
|
||||||
|
.blendMode(.overlay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.overlay {
|
||||||
|
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
||||||
|
.strokeBorder(
|
||||||
|
(self.tint ?? .white).opacity(
|
||||||
|
self.isActive
|
||||||
|
? 0.34
|
||||||
|
: (self.contrast == .increased ? 0.4 : (self.brighten ? 0.22 : 0.16))
|
||||||
|
),
|
||||||
|
lineWidth: self.contrast == .increased ? 1.0 : (self.isActive ? 0.8 : 0.6))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
.accessibilityLabel(self.accessibilityLabel)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1639,11 +1639,9 @@ extension NodeAppModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var chatSessionKey: String {
|
var chatSessionKey: String {
|
||||||
let base = "ios"
|
// Keep chat aligned with the gateway's resolved main session key.
|
||||||
let agentId = (self.selectedAgentId ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
// A hardcoded "ios" base creates synthetic placeholder sessions in the chat UI.
|
||||||
let defaultId = (self.gatewayDefaultAgentId ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
self.mainSessionKey
|
||||||
if agentId.isEmpty || (!defaultId.isEmpty && agentId == defaultId) { return base }
|
|
||||||
return SessionKey.makeAgentSessionKey(agentId: agentId, baseKey: base)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var activeAgentName: String {
|
var activeAgentName: String {
|
||||||
|
|||||||
@@ -534,63 +534,6 @@ private struct CanvasContent: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct OverlayButton: View {
|
|
||||||
let systemImage: String
|
|
||||||
let brighten: Bool
|
|
||||||
var tint: Color?
|
|
||||||
var isActive: Bool = false
|
|
||||||
let action: () -> Void
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
Button(action: self.action) {
|
|
||||||
Image(systemName: self.systemImage)
|
|
||||||
.font(.system(size: 16, weight: .semibold))
|
|
||||||
.foregroundStyle(self.isActive ? (self.tint ?? .primary) : .primary)
|
|
||||||
.padding(10)
|
|
||||||
.background {
|
|
||||||
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
|
||||||
.fill(.ultraThinMaterial)
|
|
||||||
.overlay {
|
|
||||||
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
|
||||||
.fill(
|
|
||||||
LinearGradient(
|
|
||||||
colors: [
|
|
||||||
.white.opacity(self.brighten ? 0.26 : 0.18),
|
|
||||||
.white.opacity(self.brighten ? 0.08 : 0.04),
|
|
||||||
.clear,
|
|
||||||
],
|
|
||||||
startPoint: .topLeading,
|
|
||||||
endPoint: .bottomTrailing))
|
|
||||||
.blendMode(.overlay)
|
|
||||||
}
|
|
||||||
.overlay {
|
|
||||||
if let tint {
|
|
||||||
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
|
||||||
.fill(
|
|
||||||
LinearGradient(
|
|
||||||
colors: [
|
|
||||||
tint.opacity(self.isActive ? 0.22 : 0.14),
|
|
||||||
tint.opacity(self.isActive ? 0.10 : 0.06),
|
|
||||||
.clear,
|
|
||||||
],
|
|
||||||
startPoint: .topLeading,
|
|
||||||
endPoint: .bottomTrailing))
|
|
||||||
.blendMode(.overlay)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.overlay {
|
|
||||||
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
|
||||||
.strokeBorder(
|
|
||||||
(self.tint ?? .white).opacity(self.isActive ? 0.34 : (self.brighten ? 0.24 : 0.18)),
|
|
||||||
lineWidth: self.isActive ? 0.7 : 0.5)
|
|
||||||
}
|
|
||||||
.shadow(color: .black.opacity(0.35), radius: 12, y: 6)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.buttonStyle(.plain)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct CameraFlashOverlay: View {
|
private struct CameraFlashOverlay: View {
|
||||||
var nonce: Int
|
var nonce: Int
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ struct ScreenTab: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack(alignment: .top) {
|
ZStack(alignment: .top) {
|
||||||
ScreenWebView(controller: self.appModel.screen)
|
ScreenWebView(controller: self.appModel.screen)
|
||||||
.ignoresSafeArea()
|
.ignoresSafeArea(.container, edges: [.top, .leading, .trailing])
|
||||||
.overlay(alignment: .top) {
|
.overlay(alignment: .top) {
|
||||||
if let errorText = self.appModel.screen.errorText,
|
if let errorText = self.appModel.screen.errorText,
|
||||||
self.appModel.gatewayServerName == nil
|
self.appModel.gatewayServerName == nil
|
||||||
|
|||||||
@@ -340,9 +340,9 @@ struct SettingsTab: View {
|
|||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
self.featureToggle(
|
self.featureToggle(
|
||||||
"Show Talk Button",
|
"Show Talk Control",
|
||||||
isOn: self.$talkButtonEnabled,
|
isOn: self.$talkButtonEnabled,
|
||||||
help: "Shows the floating Talk button in the main interface.")
|
help: "Shows the Talk control in the main toolbar.")
|
||||||
TextField("Default Share Instruction", text: self.$defaultShareInstruction, axis: .vertical)
|
TextField("Default Share Instruction", text: self.$defaultShareInstruction, axis: .vertical)
|
||||||
.lineLimit(2 ... 6)
|
.lineLimit(2 ... 6)
|
||||||
.textInputAutocapitalization(.sentences)
|
.textInputAutocapitalization(.sentences)
|
||||||
|
|||||||
@@ -83,16 +83,16 @@ private final class MockWatchMessagingService: @preconcurrency WatchMessagingSer
|
|||||||
#expect(json.contains("\"value\""))
|
#expect(json.contains("\"value\""))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test @MainActor func chatSessionKeyDefaultsToIOSBase() {
|
@Test @MainActor func chatSessionKeyDefaultsToMainBase() {
|
||||||
let appModel = NodeAppModel()
|
let appModel = NodeAppModel()
|
||||||
#expect(appModel.chatSessionKey == "ios")
|
#expect(appModel.chatSessionKey == "main")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test @MainActor func chatSessionKeyUsesAgentScopedKeyForNonDefaultAgent() {
|
@Test @MainActor func chatSessionKeyUsesAgentScopedKeyForNonDefaultAgent() {
|
||||||
let appModel = NodeAppModel()
|
let appModel = NodeAppModel()
|
||||||
appModel.gatewayDefaultAgentId = "main"
|
appModel.gatewayDefaultAgentId = "main"
|
||||||
appModel.setSelectedAgentId("agent-123")
|
appModel.setSelectedAgentId("agent-123")
|
||||||
#expect(appModel.chatSessionKey == SessionKey.makeAgentSessionKey(agentId: "agent-123", baseKey: "ios"))
|
#expect(appModel.chatSessionKey == SessionKey.makeAgentSessionKey(agentId: "agent-123", baseKey: "main"))
|
||||||
#expect(appModel.mainSessionKey == "agent:agent-123:main")
|
#expect(appModel.mainSessionKey == "agent:agent-123:main")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -113,18 +113,23 @@
|
|||||||
inset: 0;
|
inset: 0;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: stretch;
|
align-items: flex-start;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: calc(var(--safe-top) + 22px) 18px calc(var(--safe-bottom) + 18px);
|
padding: calc(var(--safe-top) + 18px) 16px calc(var(--safe-bottom) + 18px);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shell {
|
.shell {
|
||||||
width: min(100%, 760px);
|
width: min(100%, 760px);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: flex-start;
|
||||||
gap: 18px;
|
gap: 16px;
|
||||||
|
min-height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero {
|
.hero {
|
||||||
@@ -161,9 +166,12 @@
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
letter-spacing: 0.06em;
|
letter-spacing: 0.06em;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
max-width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.eyebrow-dot {
|
.eyebrow-dot {
|
||||||
|
flex: 0 0 auto;
|
||||||
width: 9px;
|
width: 9px;
|
||||||
height: 9px;
|
height: 9px;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
@@ -171,6 +179,13 @@
|
|||||||
box-shadow: 0 0 18px color-mix(in srgb, var(--state) 68%, transparent);
|
box-shadow: 0 0 18px color-mix(in srgb, var(--state) 68%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#openclaw-home-eyebrow {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.hero h1 {
|
.hero h1 {
|
||||||
margin: 18px 0 0;
|
margin: 18px 0 0;
|
||||||
font-size: clamp(32px, 7vw, 52px);
|
font-size: clamp(32px, 7vw, 52px);
|
||||||
@@ -218,6 +233,7 @@
|
|||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
letter-spacing: -0.03em;
|
letter-spacing: -0.03em;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
|
|
||||||
.meta-subtitle {
|
.meta-subtitle {
|
||||||
@@ -229,8 +245,9 @@
|
|||||||
|
|
||||||
.agent-focus {
|
.agent-focus {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
gap: 14px;
|
gap: 14px;
|
||||||
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.agent-badge {
|
.agent-badge {
|
||||||
@@ -252,6 +269,7 @@
|
|||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
letter-spacing: -0.03em;
|
letter-spacing: -0.03em;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
|
|
||||||
.agent-focus .caption {
|
.agent-focus .caption {
|
||||||
@@ -323,6 +341,7 @@
|
|||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
|
|
||||||
.agent-row .caption {
|
.agent-row .caption {
|
||||||
@@ -384,8 +403,8 @@
|
|||||||
|
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
#openclaw-home {
|
#openclaw-home {
|
||||||
padding-left: 14px;
|
padding-left: 12px;
|
||||||
padding-right: 14px;
|
padding-right: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero {
|
.hero {
|
||||||
@@ -403,6 +422,64 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-height: 760px) {
|
||||||
|
#openclaw-home {
|
||||||
|
padding-top: calc(var(--safe-top) + 14px);
|
||||||
|
padding-bottom: calc(var(--safe-bottom) + 12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.shell {
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero {
|
||||||
|
border-radius: 24px;
|
||||||
|
padding: 16px 15px 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero h1 {
|
||||||
|
margin-top: 14px;
|
||||||
|
font-size: clamp(28px, 8vw, 38px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero p {
|
||||||
|
margin-top: 10px;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1.42;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-grid {
|
||||||
|
margin-top: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-card {
|
||||||
|
padding: 14px 14px 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-value {
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-badge {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border-radius: 16px;
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-focus .name {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section {
|
||||||
|
padding: 14px 14px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-header {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
body::before,
|
body::before,
|
||||||
body::after {
|
body::after {
|
||||||
|
|||||||
Reference in New Issue
Block a user