Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e71e83088 | ||
|
|
867d9b3251 | ||
|
|
1e3f75455c | ||
|
|
f4b48dd2a4 | ||
|
|
29d544a09e | ||
|
|
718d9838b9 | ||
|
|
3ba5dced83 | ||
|
|
a770934b71 | ||
|
|
798985f309 | ||
|
|
7fa5d5872d | ||
|
|
c639b614fd | ||
|
|
972b11da90 | ||
|
|
707c4c74f3 | ||
|
|
65f209a853 | ||
|
|
443508e354 | ||
|
|
1161712038 | ||
|
|
60cdebfa01 |
@@ -40,16 +40,6 @@ After consent, the public Android build reports only the `app_open` event:
|
||||
|
||||
You can also build the app without analytics keys. In that case, analytics providers will not start.
|
||||
|
||||
## Download signed APK
|
||||
|
||||
For normal installation, download the signed release APK from GitHub Releases:
|
||||
|
||||
- Latest release: https://github.com/park0er/multi-casual-android/releases/latest
|
||||
- Current signed APK: `Multi-Casual-Android-v0.1.1.apk`
|
||||
- SHA256: `0b34ab0d164403aec655ca9e829b3fce6a2849f4a83dc1f631336ff725299c42`
|
||||
|
||||
The debug APK produced by local development commands is not intended for public distribution.
|
||||
|
||||
## Build
|
||||
|
||||
Requirements:
|
||||
@@ -65,7 +55,7 @@ cp local.properties.example local.properties
|
||||
|
||||
Optional analytics keys can be filled in locally. Do not commit real keys.
|
||||
|
||||
Build a local debug APK for development:
|
||||
Build the Android APK:
|
||||
|
||||
```bash
|
||||
JAVA_HOME="/Applications/Android Studio.app/Contents/jbr/Contents/Home" \
|
||||
@@ -156,16 +146,6 @@ Analytics 是 opt-in。App 会先请求用户同意,然后才会上报 analyti
|
||||
|
||||
如果本地不配置 analytics key,analytics provider 不会启动。
|
||||
|
||||
## 下载签名 APK
|
||||
|
||||
普通用户安装时,请从 GitHub Releases 下载签名 release APK:
|
||||
|
||||
- 最新 release:https://github.com/park0er/multi-casual-android/releases/latest
|
||||
- 当前签名 APK:`Multi-Casual-Android-v0.1.1.apk`
|
||||
- SHA256:`0b34ab0d164403aec655ca9e829b3fce6a2849f4a83dc1f631336ff725299c42`
|
||||
|
||||
本地开发命令生成的 debug APK 不用于公开分发。
|
||||
|
||||
## 构建
|
||||
|
||||
环境要求:
|
||||
@@ -181,7 +161,7 @@ cp local.properties.example local.properties
|
||||
|
||||
可选 analytics key 只填在本地。不要提交真实 key。
|
||||
|
||||
构建本地开发用 debug APK:
|
||||
构建 Android APK:
|
||||
|
||||
```bash
|
||||
JAVA_HOME="/Applications/Android Studio.app/Contents/jbr/Contents/Home" \
|
||||
|
||||
@@ -19,6 +19,12 @@ This notice is informational and does not replace the license text shipped by ea
|
||||
| PostHog Android SDK | `com.posthog:posthog-android`, `com.posthog:posthog` | MIT |
|
||||
| Guava listenablefuture placeholder | `com.google.guava:listenablefuture` | Apache-2.0; Maven POM has limited license metadata |
|
||||
|
||||
## Website dependencies
|
||||
|
||||
The website source uses React, Vite, Motion, TypeScript, Lightning CSS, and related build-time packages. The lockfile currently contains MIT, Apache-2.0, MPL-2.0, ISC, BSD-3-Clause, and 0BSD packages.
|
||||
|
||||
These website dependencies are not bundled into the Android APK.
|
||||
|
||||
## Notes for redistributors
|
||||
|
||||
If you redistribute a binary build, keep this file, the GPL-3.0-only `LICENSE`, and any dependency notices required by the upstream libraries you include.
|
||||
|
||||
+2
-2
@@ -51,8 +51,8 @@ android {
|
||||
applicationId "ai.multicasual.app"
|
||||
minSdk 24
|
||||
targetSdk 36
|
||||
versionCode 3
|
||||
versionName "0.1.2"
|
||||
versionCode 2
|
||||
versionName "0.1.1"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
|
||||
@@ -748,18 +748,6 @@ final class ApiClient {
|
||||
return parseArray(extractArray(json, "agents"), Models.Agent::new);
|
||||
}
|
||||
|
||||
|
||||
List<Models.Squad> squads(String workspaceId) throws Exception {
|
||||
return squads(workspaceId, false);
|
||||
}
|
||||
|
||||
List<Models.Squad> squads(String workspaceId, boolean includeArchived) throws Exception {
|
||||
JSONObject query = query(null, "workspace_id", workspaceId);
|
||||
if (includeArchived) query.put("include_archived", "true");
|
||||
JSONObject json = requestObject("GET", "/api/squads", query, null);
|
||||
return parseArray(extractArray(json, "squads"), Models.Squad::new);
|
||||
}
|
||||
|
||||
List<Models.Runtime> runtimes(String workspaceId) throws Exception {
|
||||
JSONObject json = requestObject("GET", "/api/runtimes", query(null, "workspace_id", workspaceId), null);
|
||||
return parseArray(extractArray(json, "runtimes"), Models.Runtime::new);
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
package ai.multica.app;
|
||||
|
||||
final class ChatExperiencePolicy {
|
||||
private static final float CHAT_FOCUSED_COMPOSER_RATIO = 0.22f;
|
||||
private static final float ISSUE_FOCUSED_COMPOSER_RATIO = 0.18f;
|
||||
private static final int MIN_CHAT_FOCUSED_COMPOSER_HEIGHT = 132;
|
||||
private static final int MAX_CHAT_FOCUSED_COMPOSER_HEIGHT = 184;
|
||||
private static final int MIN_ISSUE_FOCUSED_COMPOSER_HEIGHT = 112;
|
||||
private static final int MAX_ISSUE_FOCUSED_COMPOSER_HEIGHT = 152;
|
||||
|
||||
enum Route {
|
||||
CHAT,
|
||||
ISSUE_COMMENT,
|
||||
ISSUE_REPLY
|
||||
}
|
||||
|
||||
private ChatExperiencePolicy() {
|
||||
}
|
||||
|
||||
static String newChatInitialTitle() {
|
||||
return "";
|
||||
}
|
||||
|
||||
static String firstChatTitle(String content) {
|
||||
String clean = content == null ? "" : content.trim();
|
||||
if (clean.isEmpty()) return "";
|
||||
StringBuilder title = new StringBuilder();
|
||||
clean.codePoints().limit(15).forEach(title::appendCodePoint);
|
||||
return title.toString();
|
||||
}
|
||||
|
||||
static boolean requiresTitleBeforeEnteringChat() {
|
||||
return false;
|
||||
}
|
||||
|
||||
static int focusedComposerHeightPx(Route route, int usableHeightPx) {
|
||||
int height = Math.max(0, usableHeightPx);
|
||||
if (route == Route.ISSUE_COMMENT || route == Route.ISSUE_REPLY) {
|
||||
int target = Math.round(height * ISSUE_FOCUSED_COMPOSER_RATIO);
|
||||
return clamp(target, MIN_ISSUE_FOCUSED_COMPOSER_HEIGHT, MAX_ISSUE_FOCUSED_COMPOSER_HEIGHT);
|
||||
}
|
||||
int target = Math.round(height * CHAT_FOCUSED_COMPOSER_RATIO);
|
||||
return clamp(target, MIN_CHAT_FOCUSED_COMPOSER_HEIGHT, MAX_CHAT_FOCUSED_COMPOSER_HEIGHT);
|
||||
}
|
||||
|
||||
static int focusedComposerHeightPx(int usableHeightPx) {
|
||||
return focusedComposerHeightPx(Route.CHAT, usableHeightPx);
|
||||
}
|
||||
|
||||
static boolean isFocusedComposerRoute(Route route) {
|
||||
return route == Route.CHAT || route == Route.ISSUE_COMMENT || route == Route.ISSUE_REPLY;
|
||||
}
|
||||
|
||||
private static int clamp(int value, int min, int max) {
|
||||
return Math.max(min, Math.min(max, value));
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -13,83 +13,44 @@ import java.util.regex.Pattern;
|
||||
|
||||
final class AgentMentionMarkdown {
|
||||
static String markdown(String name, String agentId) {
|
||||
return mentionMarkdown("agent", name, agentId);
|
||||
String cleanName = name == null ? "" : name.trim()
|
||||
.replace("[", "\\[")
|
||||
.replace("]", "\\]");
|
||||
if (cleanName.isEmpty()) cleanName = Models.shortId(agentId);
|
||||
return "[@" + cleanName + "](mention://agent/" + agentId + ")";
|
||||
}
|
||||
|
||||
static String memberMarkdown(String name, String memberId) {
|
||||
return mentionMarkdown("member", name, memberId);
|
||||
}
|
||||
|
||||
static String squadMarkdown(String name, String squadId) {
|
||||
return mentionMarkdown("squad", name, squadId);
|
||||
}
|
||||
|
||||
static String displayText(String name, String id) {
|
||||
static String displayText(String name, String agentId) {
|
||||
String cleanName = cleanName(name);
|
||||
if (cleanName.isEmpty()) cleanName = Models.shortId(id);
|
||||
if (cleanName.isEmpty()) cleanName = Models.shortId(agentId);
|
||||
return "@" + cleanName;
|
||||
}
|
||||
|
||||
static String markdownFromDraft(String draft, List<Models.Agent> agents) {
|
||||
return markdownFromDraft(draft, agents, Collections.emptyList(), Collections.emptyList());
|
||||
}
|
||||
|
||||
static String markdownFromDraft(String draft, List<Models.Agent> agents,
|
||||
List<Models.Member> members, List<Models.Squad> squads) {
|
||||
String content = draft == null ? "" : draft.trim();
|
||||
if (content.isEmpty() || content.contains("mention://")) return content;
|
||||
ArrayList<MentionReplacement> replacements = new ArrayList<>();
|
||||
if (agents != null) {
|
||||
for (Models.Agent agent : agents) {
|
||||
replacements.add(new MentionReplacement(displayText(agent.name, agent.id), markdown(agent.name, agent.id)));
|
||||
}
|
||||
if (content.isEmpty() || content.contains("mention://agent/") || agents == null || agents.isEmpty()) {
|
||||
return content;
|
||||
}
|
||||
if (members != null) {
|
||||
for (Models.Member member : members) {
|
||||
replacements.add(new MentionReplacement(displayText(member.displayName, member.id), memberMarkdown(member.displayName, member.id)));
|
||||
if (member.userId != null && !member.userId.isEmpty() && !member.userId.equals(member.id)) {
|
||||
replacements.add(new MentionReplacement(displayText(member.displayName, member.userId), memberMarkdown(member.displayName, member.userId)));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (squads != null) {
|
||||
for (Models.Squad squad : squads) {
|
||||
replacements.add(new MentionReplacement(displayText(squad.name, squad.id), squadMarkdown(squad.name, squad.id)));
|
||||
}
|
||||
}
|
||||
replacements.sort((left, right) -> Integer.compare(right.display.length(), left.display.length()));
|
||||
ArrayList<Models.Agent> sortedAgents = new ArrayList<>(agents);
|
||||
sortedAgents.sort((left, right) -> Integer.compare(
|
||||
displayText(right.name, right.id).length(),
|
||||
displayText(left.name, left.id).length()
|
||||
));
|
||||
String normalized = content;
|
||||
for (MentionReplacement replacement : replacements) {
|
||||
if (replacement.display.length() <= 1) continue;
|
||||
normalized = normalized.replace(replacement.display, replacement.markdown);
|
||||
for (Models.Agent agent : sortedAgents) {
|
||||
String display = displayText(agent.name, agent.id);
|
||||
if (display.length() <= 1) continue;
|
||||
normalized = normalized.replace(display, markdown(agent.name, agent.id));
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private static String mentionMarkdown(String type, String name, String id) {
|
||||
String cleanName = cleanName(name)
|
||||
.replace("[", "\\[")
|
||||
.replace("]", "\\]");
|
||||
if (cleanName.isEmpty()) cleanName = Models.shortId(id);
|
||||
return "[@" + cleanName + "](mention://" + type + "/" + id + ")";
|
||||
}
|
||||
|
||||
private static String cleanName(String name) {
|
||||
if (name == null) return "";
|
||||
String trimmed = name.trim();
|
||||
return "null".equalsIgnoreCase(trimmed) ? "" : trimmed;
|
||||
}
|
||||
|
||||
private static final class MentionReplacement {
|
||||
final String display;
|
||||
final String markdown;
|
||||
|
||||
MentionReplacement(String display, String markdown) {
|
||||
this.display = display;
|
||||
this.markdown = markdown;
|
||||
}
|
||||
}
|
||||
|
||||
private AgentMentionMarkdown() {
|
||||
}
|
||||
}
|
||||
@@ -99,19 +60,13 @@ final class IssueCommentRichText {
|
||||
|
||||
static String mentionDisplayLabel(String mentionType, String mentionId, String fallbackLabel,
|
||||
List<Models.Member> members, List<Models.Agent> agents,
|
||||
List<Models.Squad> squads, Models.User currentUser) {
|
||||
String resolved = resolveMentionName(mentionType, mentionId, members, agents, squads, currentUser);
|
||||
Models.User currentUser) {
|
||||
String resolved = resolveMentionName(mentionType, mentionId, members, agents, currentUser);
|
||||
String label = resolved.isEmpty() ? cleanMentionFallback(fallbackLabel, mentionId) : resolved;
|
||||
if (label.startsWith("@")) return label;
|
||||
return "@" + label;
|
||||
}
|
||||
|
||||
static String mentionDisplayLabel(String mentionType, String mentionId, String fallbackLabel,
|
||||
List<Models.Member> members, List<Models.Agent> agents,
|
||||
Models.User currentUser) {
|
||||
return mentionDisplayLabel(mentionType, mentionId, fallbackLabel, members, agents, Collections.emptyList(), currentUser);
|
||||
}
|
||||
|
||||
static int issueIdentifierEndAt(String value, int start) {
|
||||
if (value == null || start < 0 || start >= value.length()) return -1;
|
||||
Matcher matcher = ISSUE_IDENTIFIER.matcher(value);
|
||||
@@ -150,7 +105,7 @@ final class IssueCommentRichText {
|
||||
|
||||
private static String resolveMentionName(String mentionType, String mentionId,
|
||||
List<Models.Member> members, List<Models.Agent> agents,
|
||||
List<Models.Squad> squads, Models.User currentUser) {
|
||||
Models.User currentUser) {
|
||||
String type = mentionType == null ? "" : mentionType.toLowerCase(Locale.US);
|
||||
String id = mentionId == null ? "" : mentionId;
|
||||
if ("agent".equals(type)) {
|
||||
@@ -164,11 +119,6 @@ final class IssueCommentRichText {
|
||||
if (id.equals(member.id) || id.equals(member.userId)) return clean(member.displayName);
|
||||
}
|
||||
}
|
||||
if ("squad".equals(type)) {
|
||||
for (Models.Squad squad : squads) {
|
||||
if (id.equals(squad.id)) return clean(squad.name);
|
||||
}
|
||||
}
|
||||
for (Models.Agent agent : agents) {
|
||||
if (id.equals(agent.id)) return clean(agent.name);
|
||||
}
|
||||
@@ -176,9 +126,6 @@ final class IssueCommentRichText {
|
||||
for (Models.Member member : members) {
|
||||
if (id.equals(member.id) || id.equals(member.userId)) return clean(member.displayName);
|
||||
}
|
||||
for (Models.Squad squad : squads) {
|
||||
if (id.equals(squad.id)) return clean(squad.name);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -253,9 +200,7 @@ final class IssueCommentThreads {
|
||||
ArrayList<Thread> threads = new ArrayList<>();
|
||||
for (Models.Comment root : roots) {
|
||||
ArrayList<Models.Comment> replies = repliesByRoot.getOrDefault(clean(root.id), new ArrayList<>());
|
||||
replies.sort((left, right) -> descending
|
||||
? compareCreatedAt(right, left)
|
||||
: compareCreatedAt(left, right));
|
||||
replies.sort(IssueCommentThreads::compareCreatedAt);
|
||||
threads.add(new Thread(root, replies));
|
||||
}
|
||||
return threads;
|
||||
|
||||
@@ -62,7 +62,6 @@ public final class MainActivity extends Activity {
|
||||
private List<Models.Project> projectCache = new ArrayList<>();
|
||||
private List<Models.Member> memberCache = new ArrayList<>();
|
||||
private List<Models.Agent> agentCache = new ArrayList<>();
|
||||
private List<Models.Squad> squadCache = new ArrayList<>();
|
||||
private List<Models.Runtime> runtimeCache = new ArrayList<>();
|
||||
private List<Models.Skill> skillCache = new ArrayList<>();
|
||||
private List<Models.Autopilot> autopilotCache = new ArrayList<>();
|
||||
@@ -267,7 +266,6 @@ public final class MainActivity extends Activity {
|
||||
projectCache = new ArrayList<>();
|
||||
memberCache = new ArrayList<>();
|
||||
agentCache = new ArrayList<>();
|
||||
squadCache = new ArrayList<>();
|
||||
runtimeCache = new ArrayList<>();
|
||||
skillCache = new ArrayList<>();
|
||||
autopilotCache = new ArrayList<>();
|
||||
@@ -567,7 +565,6 @@ public final class MainActivity extends Activity {
|
||||
projectCache = data.projects;
|
||||
memberCache = data.members;
|
||||
agentCache = data.agents;
|
||||
try { squadCache = safeSquads(); } catch (Exception ignored) { squadCache = new ArrayList<>(); }
|
||||
LinearLayout screen = vertical();
|
||||
LinearLayout scrollBody = vertical();
|
||||
scrollBody.setPadding(dp(14), dp(8), dp(14), dp(14));
|
||||
@@ -1836,10 +1833,6 @@ public final class MainActivity extends Activity {
|
||||
}
|
||||
}
|
||||
|
||||
private List<Models.Squad> safeSquads() {
|
||||
try { return api.squads(currentWorkspace.id); } catch (Exception e) { return new ArrayList<>(); }
|
||||
}
|
||||
|
||||
private List<Models.Agent> safeAgents() {
|
||||
if (currentWorkspace == null) return Models.emptyList();
|
||||
try {
|
||||
@@ -1861,7 +1854,7 @@ public final class MainActivity extends Activity {
|
||||
private List<Models.Assignee> assignees(boolean includeEmpty) {
|
||||
if (memberCache.isEmpty()) memberCache = safeMembers();
|
||||
if (agentCache.isEmpty()) agentCache = safeAgents();
|
||||
return Models.issueAssignees(includeEmpty, t("unassigned"), currentUser, memberCache, agentCache, squadCache);
|
||||
return Models.issueAssignees(includeEmpty, t("unassigned"), currentUser, memberCache, agentCache);
|
||||
}
|
||||
|
||||
private String assigneeName(String id, String type) {
|
||||
@@ -1869,10 +1862,7 @@ public final class MainActivity extends Activity {
|
||||
if (currentUser != null && currentUser.id.equals(id)) return currentUser.name;
|
||||
for (Models.Member member : memberCache) if (member.id.equals(id)) return member.displayName;
|
||||
for (Models.Agent agent : agentCache) if (agent.id.equals(id)) return agent.name;
|
||||
String prefix;
|
||||
if ("agent".equals(type)) prefix = t("agent");
|
||||
else if ("squad".equals(type)) prefix = t("squad");
|
||||
else prefix = t("member");
|
||||
String prefix = "agent".equals(type) ? t("agent") : t("member");
|
||||
return prefix + " " + Models.shortId(id);
|
||||
}
|
||||
|
||||
@@ -2262,7 +2252,6 @@ public final class MainActivity extends Activity {
|
||||
case "unassigned": return "未分配";
|
||||
case "member": return "成员";
|
||||
case "agent": return "Agent";
|
||||
case "squad": return "小队";
|
||||
case "archive": return "归档";
|
||||
case "archived": return "已归档";
|
||||
case "archiveAgent": return "归档 Agent";
|
||||
@@ -2375,7 +2364,6 @@ public final class MainActivity extends Activity {
|
||||
case "unassigned": return "Unassigned";
|
||||
case "member": return "Member";
|
||||
case "agent": return "Agent";
|
||||
case "squad": return "Squad";
|
||||
case "archive": return "Archive";
|
||||
case "archived": return "Archived";
|
||||
case "archiveAgent": return "Archive Agent";
|
||||
|
||||
@@ -166,17 +166,13 @@ final class Models {
|
||||
return subscriber.userType == null ? "" : subscriber.userType;
|
||||
}
|
||||
|
||||
static String issueAssigneeDisplayName(Issue issue, List<Member> members, List<Agent> agents, List<Squad> squads, User currentUser) {
|
||||
static String issueAssigneeDisplayName(Issue issue, List<Member> members, List<Agent> agents, User currentUser) {
|
||||
if (issue == null || issue.assigneeId == null || issue.assigneeId.isEmpty()) return "";
|
||||
String assigneeId = issue.assigneeId;
|
||||
if ("agent".equals(issue.assigneeType)) {
|
||||
for (Agent agent : agents) {
|
||||
if (assigneeId.equals(agent.id)) return agent.name;
|
||||
}
|
||||
} else if ("squad".equals(issue.assigneeType)) {
|
||||
for (Squad squad : squads) {
|
||||
if (assigneeId.equals(squad.id)) return squad.name;
|
||||
}
|
||||
} else {
|
||||
if (currentUser != null && assigneeId.equals(currentUser.id)) return currentUser.name;
|
||||
for (Member member : members) {
|
||||
@@ -189,16 +185,11 @@ final class Models {
|
||||
for (Member member : members) {
|
||||
if (assigneeId.equals(member.id) || assigneeId.equals(member.userId)) return member.displayName;
|
||||
}
|
||||
for (Squad squad : squads) {
|
||||
if (assigneeId.equals(squad.id)) return squad.name;
|
||||
}
|
||||
String prefix;
|
||||
if ("agent".equals(issue.assigneeType)) {
|
||||
prefix = "Agent ";
|
||||
} else if ("member".equals(issue.assigneeType)) {
|
||||
prefix = "Member ";
|
||||
} else if ("squad".equals(issue.assigneeType)) {
|
||||
prefix = "Squad ";
|
||||
} else {
|
||||
prefix = "";
|
||||
}
|
||||
@@ -206,7 +197,7 @@ final class Models {
|
||||
}
|
||||
|
||||
static List<Assignee> issueAssignees(boolean includeEmpty, String emptyLabel, User currentUser,
|
||||
List<Member> members, List<Agent> agents, List<Squad> squads) {
|
||||
List<Member> members, List<Agent> agents) {
|
||||
List<Assignee> list = new ArrayList<>();
|
||||
List<String> seenMemberIds = new ArrayList<>();
|
||||
if (includeEmpty) list.add(new Assignee(null, null, emptyLabel));
|
||||
@@ -223,10 +214,6 @@ final class Models {
|
||||
if (agent.id == null || agent.id.isEmpty()) continue;
|
||||
list.add(new Assignee(agent.id, "agent", agent.name));
|
||||
}
|
||||
for (Squad squad : squads) {
|
||||
if (squad.id == null || squad.id.isEmpty()) continue;
|
||||
list.add(new Assignee(squad.id, "squad", squad.name));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@@ -769,27 +756,6 @@ final class Models {
|
||||
}
|
||||
}
|
||||
|
||||
static final class Squad {
|
||||
final String id;
|
||||
final String name;
|
||||
final String description;
|
||||
final String avatarUrl;
|
||||
final String archivedAt;
|
||||
final List<String> agentIds;
|
||||
final List<String> memberIds;
|
||||
|
||||
Squad(JSONObject json) {
|
||||
id = json.optString("id");
|
||||
name = json.optString("name", shortId(id));
|
||||
description = json.optString("description", "");
|
||||
avatarUrl = cleanString(json, "avatar_url", cleanString(json, "avatarUrl", ""));
|
||||
archivedAt = cleanString(json, "archived_at", "");
|
||||
agentIds = stringList(json.optJSONArray("agent_ids"));
|
||||
memberIds = stringList(json.optJSONArray("member_ids"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static final class Runtime {
|
||||
final String id;
|
||||
final String workspaceId;
|
||||
|
||||
@@ -61,7 +61,6 @@ import com.airbnb.lottie.compose.rememberLottieComposition
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
@@ -1613,7 +1612,6 @@ fun MulticaCupertinoTextField(
|
||||
singleLine: Boolean = false,
|
||||
minLines: Int = 1,
|
||||
maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
|
||||
onFocusedChange: ((Boolean) -> Unit)? = null,
|
||||
) {
|
||||
val fieldDescription = contentDescription ?: "Multica Cupertino Text Field $label"
|
||||
val centerSingleLineContent = singleLine || minLines <= 1
|
||||
@@ -1653,8 +1651,7 @@ fun MulticaCupertinoTextField(
|
||||
onValueChange = onValueChange,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.onFocusChanged { onFocusedChange?.invoke(it.isFocused) },
|
||||
.wrapContentHeight(),
|
||||
enabled = enabled,
|
||||
singleLine = singleLine,
|
||||
minLines = minLines,
|
||||
@@ -1697,7 +1694,6 @@ fun MulticaTextField(
|
||||
singleLine: Boolean = false,
|
||||
minLines: Int = 1,
|
||||
maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
|
||||
onFocusedChange: ((Boolean) -> Unit)? = null,
|
||||
) {
|
||||
MulticaCupertinoTextField(
|
||||
value = value,
|
||||
@@ -1711,7 +1707,6 @@ fun MulticaTextField(
|
||||
singleLine = singleLine,
|
||||
minLines = minLines,
|
||||
maxLines = maxLines,
|
||||
onFocusedChange = onFocusedChange,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
node_modules/
|
||||
dist/
|
||||
output/
|
||||
.playwright-cli/
|
||||
*.tsbuildinfo
|
||||
@@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content="Multi-Casual Android is an independent Android companion app for Multica users." />
|
||||
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='18' fill='%231647ff'/%3E%3Ctext x='32' y='41' text-anchor='middle' font-family='Arial,sans-serif' font-size='30' font-weight='800' fill='white'%3EM%3C/text%3E%3C/svg%3E" />
|
||||
<title>Multi-Casual Android</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
Generated
+975
@@ -0,0 +1,975 @@
|
||||
{
|
||||
"name": "site",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"@vitejs/plugin-react": "latest",
|
||||
"motion": "latest",
|
||||
"react": "latest",
|
||||
"react-dom": "latest",
|
||||
"typescript": "latest",
|
||||
"vite": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^19.2.16",
|
||||
"@types/react-dom": "^19.2.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/wasi-threads": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
|
||||
"integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/wasm-runtime": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz",
|
||||
"integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@tybys/wasm-util": "^0.10.1"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emnapi/core": "^1.7.1",
|
||||
"@emnapi/runtime": "^1.7.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@oxc-project/types": {
|
||||
"version": "0.133.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz",
|
||||
"integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/Boshen"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-android-arm64": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz",
|
||||
"integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-arm64": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz",
|
||||
"integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-x64": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz",
|
||||
"integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-freebsd-x64": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz",
|
||||
"integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz",
|
||||
"integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-gnu": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz",
|
||||
"integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-musl": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz",
|
||||
"integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz",
|
||||
"integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-s390x-gnu": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz",
|
||||
"integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-gnu": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz",
|
||||
"integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-musl": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz",
|
||||
"integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-openharmony-arm64": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz",
|
||||
"integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openharmony"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-wasm32-wasi": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz",
|
||||
"integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/core": "1.10.0",
|
||||
"@emnapi/runtime": "1.10.0",
|
||||
"@napi-rs/wasm-runtime": "^1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-arm64-msvc": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz",
|
||||
"integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-x64-msvc": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz",
|
||||
"integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/pluginutils": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
|
||||
"integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@tybys/wasm-util": {
|
||||
"version": "0.10.2",
|
||||
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz",
|
||||
"integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "19.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.16.tgz",
|
||||
"integrity": "sha512-esJiCAnl0kfpNdE69f3So4WJUXy95dLZydX0KwK46riIHDzHM7O9Vtf9xCHW0PXIqvgqNrswl522kA/5yx+F4w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-dom": {
|
||||
"version": "19.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
|
||||
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitejs/plugin-react": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.2.tgz",
|
||||
"integrity": "sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@rolldown/pluginutils": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@rolldown/plugin-babel": "^0.1.7 || ^0.2.0",
|
||||
"babel-plugin-react-compiler": "^1.0.0",
|
||||
"vite": "^8.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@rolldown/plugin-babel": {
|
||||
"optional": true
|
||||
},
|
||||
"babel-plugin-react-compiler": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/fdir": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
||||
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"picomatch": "^3 || ^4"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"picomatch": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/framer-motion": {
|
||||
"version": "12.40.0",
|
||||
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.40.0.tgz",
|
||||
"integrity": "sha512-uaBd3qC1v3KQqBEjwTUd183K6PbS+j0yR9w9VmEOLWA/tnUcSn8Xa3uck7t4dgpDoUss8xQTcj8W2L07lrnLFg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"motion-dom": "^12.40.0",
|
||||
"motion-utils": "^12.39.0",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/is-prop-valid": "*",
|
||||
"react": "^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@emotion/is-prop-valid": {
|
||||
"optional": true
|
||||
},
|
||||
"react": {
|
||||
"optional": true
|
||||
},
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss": {
|
||||
"version": "1.32.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
|
||||
"integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"lightningcss-android-arm64": "1.32.0",
|
||||
"lightningcss-darwin-arm64": "1.32.0",
|
||||
"lightningcss-darwin-x64": "1.32.0",
|
||||
"lightningcss-freebsd-x64": "1.32.0",
|
||||
"lightningcss-linux-arm-gnueabihf": "1.32.0",
|
||||
"lightningcss-linux-arm64-gnu": "1.32.0",
|
||||
"lightningcss-linux-arm64-musl": "1.32.0",
|
||||
"lightningcss-linux-x64-gnu": "1.32.0",
|
||||
"lightningcss-linux-x64-musl": "1.32.0",
|
||||
"lightningcss-win32-arm64-msvc": "1.32.0",
|
||||
"lightningcss-win32-x64-msvc": "1.32.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-android-arm64": {
|
||||
"version": "1.32.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
|
||||
"integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-darwin-arm64": {
|
||||
"version": "1.32.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
|
||||
"integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-darwin-x64": {
|
||||
"version": "1.32.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
|
||||
"integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-freebsd-x64": {
|
||||
"version": "1.32.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
|
||||
"integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-arm-gnueabihf": {
|
||||
"version": "1.32.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
|
||||
"integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-arm64-gnu": {
|
||||
"version": "1.32.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
|
||||
"integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-arm64-musl": {
|
||||
"version": "1.32.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
|
||||
"integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-x64-gnu": {
|
||||
"version": "1.32.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
|
||||
"integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-x64-musl": {
|
||||
"version": "1.32.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
|
||||
"integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-win32-arm64-msvc": {
|
||||
"version": "1.32.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
|
||||
"integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-win32-x64-msvc": {
|
||||
"version": "1.32.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
|
||||
"integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/motion": {
|
||||
"version": "12.40.0",
|
||||
"resolved": "https://registry.npmjs.org/motion/-/motion-12.40.0.tgz",
|
||||
"integrity": "sha512-yjrHUrBFW6kQvjJwRsoiPSAhC5tRwRqNGJWmiJ4CrGnbKp0V88AdzkhBmDoqIsIPfarOe0Uddd37Xq43/gIocA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"framer-motion": "^12.40.0",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/is-prop-valid": "*",
|
||||
"react": "^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@emotion/is-prop-valid": {
|
||||
"optional": true
|
||||
},
|
||||
"react": {
|
||||
"optional": true
|
||||
},
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/motion-dom": {
|
||||
"version": "12.40.0",
|
||||
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.40.0.tgz",
|
||||
"integrity": "sha512-HxU3ZaBwNPVQUBQf1xxgq+7JrPNZvjLVxgbpEZL7RrWJnsxOf0/OM+yrHG9ogLQ31Do/r57Oz2gQWPK+6q62mg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"motion-utils": "^12.39.0"
|
||||
}
|
||||
},
|
||||
"node_modules/motion-utils": {
|
||||
"version": "12.39.0",
|
||||
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.39.0.tgz",
|
||||
"integrity": "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.12",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
|
||||
"integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.15",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
|
||||
"integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/postcss"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.12",
|
||||
"picocolors": "^1.1.1",
|
||||
"source-map-js": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "19.2.7",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz",
|
||||
"integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "19.2.7",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz",
|
||||
"integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.27.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.2.7"
|
||||
}
|
||||
},
|
||||
"node_modules/rolldown": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz",
|
||||
"integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@oxc-project/types": "=0.133.0",
|
||||
"@rolldown/pluginutils": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"rolldown": "bin/cli.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rolldown/binding-android-arm64": "1.0.3",
|
||||
"@rolldown/binding-darwin-arm64": "1.0.3",
|
||||
"@rolldown/binding-darwin-x64": "1.0.3",
|
||||
"@rolldown/binding-freebsd-x64": "1.0.3",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.0.3",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.0.3",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.0.3",
|
||||
"@rolldown/binding-linux-ppc64-gnu": "1.0.3",
|
||||
"@rolldown/binding-linux-s390x-gnu": "1.0.3",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.0.3",
|
||||
"@rolldown/binding-linux-x64-musl": "1.0.3",
|
||||
"@rolldown/binding-openharmony-arm64": "1.0.3",
|
||||
"@rolldown/binding-wasm32-wasi": "1.0.3",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.0.3",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.27.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
|
||||
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyglobby": {
|
||||
"version": "0.2.17",
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
|
||||
"integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fdir": "^6.5.0",
|
||||
"picomatch": "^4.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/SuperchupuDev"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
|
||||
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "8.0.16",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz",
|
||||
"integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"lightningcss": "^1.32.0",
|
||||
"picomatch": "^4.0.4",
|
||||
"postcss": "^8.5.15",
|
||||
"rolldown": "1.0.3",
|
||||
"tinyglobby": "^0.2.17"
|
||||
},
|
||||
"bin": {
|
||||
"vite": "bin/vite.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/vitejs/vite?sponsor=1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/node": "^20.19.0 || >=22.12.0",
|
||||
"@vitejs/devtools": "^0.1.18",
|
||||
"esbuild": "^0.27.0 || ^0.28.0",
|
||||
"jiti": ">=1.21.0",
|
||||
"less": "^4.0.0",
|
||||
"sass": "^1.70.0",
|
||||
"sass-embedded": "^1.70.0",
|
||||
"stylus": ">=0.54.8",
|
||||
"sugarss": "^5.0.0",
|
||||
"terser": "^5.16.0",
|
||||
"tsx": "^4.8.1",
|
||||
"yaml": "^2.4.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
},
|
||||
"@vitejs/devtools": {
|
||||
"optional": true
|
||||
},
|
||||
"esbuild": {
|
||||
"optional": true
|
||||
},
|
||||
"jiti": {
|
||||
"optional": true
|
||||
},
|
||||
"less": {
|
||||
"optional": true
|
||||
},
|
||||
"sass": {
|
||||
"optional": true
|
||||
},
|
||||
"sass-embedded": {
|
||||
"optional": true
|
||||
},
|
||||
"stylus": {
|
||||
"optional": true
|
||||
},
|
||||
"sugarss": {
|
||||
"optional": true
|
||||
},
|
||||
"terser": {
|
||||
"optional": true
|
||||
},
|
||||
"tsx": {
|
||||
"optional": true
|
||||
},
|
||||
"yaml": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"scripts": {
|
||||
"dev": "vite --host 127.0.0.1",
|
||||
"dev:62702": "vite --host 127.0.0.1 --port 62702",
|
||||
"build": "tsc -b && vite build",
|
||||
"preview": "vite preview --host 127.0.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vitejs/plugin-react": "latest",
|
||||
"motion": "latest",
|
||||
"react": "latest",
|
||||
"react-dom": "latest",
|
||||
"typescript": "latest",
|
||||
"vite": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^19.2.16",
|
||||
"@types/react-dom": "^19.2.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,365 @@
|
||||
import { useState } from "react";
|
||||
import { motion, useReducedMotion, type MotionProps } from "motion/react";
|
||||
import issueDetail from "./assets/screenshots/02-issue-detail.png";
|
||||
import commentFocused from "./assets/screenshots/03-comment-focused.png";
|
||||
import promoVideo from "./assets/video/multica-android-promo-funk-final.mp4";
|
||||
|
||||
const release = {
|
||||
packageName: "ai.multicasual.app",
|
||||
sha256: "f724e2e18dcf429e4cf8ed8782507136175732ffda28a7fc89a4a14b5b603b57",
|
||||
githubUrl: "https://github.com/park0er/multi-casual-android",
|
||||
releasesUrl: "https://github.com/park0er/multi-casual-android/releases/latest",
|
||||
multicaUrl: "https://multica.ai",
|
||||
serviceUrl: "https://app.multica.ai",
|
||||
apiUrl: "https://api.multica.ai",
|
||||
};
|
||||
|
||||
type Locale = "en" | "zh";
|
||||
|
||||
const copy = {
|
||||
en: {
|
||||
nav: {
|
||||
brand: "Multi-Casual Android",
|
||||
download: "Download",
|
||||
trust: "Trust",
|
||||
relationship: "Relationship",
|
||||
language: "中文",
|
||||
},
|
||||
hero: {
|
||||
eyebrow: "Independent companion app · Built for Multica workflows",
|
||||
title: "Review issues, reply faster, stay in flow on Android.",
|
||||
subtitle:
|
||||
"An independent Android companion app for people who already use Multica and want the core workspace loop on a phone.",
|
||||
download: "Download APK",
|
||||
github: "View GitHub",
|
||||
multica: "Visit Multica",
|
||||
typing: "Looks good — verified on Android.",
|
||||
},
|
||||
relationship: {
|
||||
kicker: "Relationship to Multica",
|
||||
title: "Respectful, explicit, and independent.",
|
||||
body:
|
||||
"Multi-Casual Android is an independent companion app for Multica users. It connects to Multica service APIs for compatibility, but it is not maintained, endorsed, or published by the Multica project maintainers, and it should not be presented as a Multica Android app from the upstream project.",
|
||||
},
|
||||
featuresKicker: "What it covers",
|
||||
features: [
|
||||
{
|
||||
title: "Issue workflows",
|
||||
body: "Browse inbox items, open issue details, review comments, and keep task context close while away from desktop.",
|
||||
},
|
||||
{
|
||||
title: "Mobile replies",
|
||||
body: "Tap into the comment flow, draft a response, and continue the same discussion you would normally handle on the web.",
|
||||
},
|
||||
{
|
||||
title: "Cloud-service compatible",
|
||||
body: "The Android app is designed as a companion to the Multica backend service. It does not bundle a server, and it does not add runtime backend switching for normal users.",
|
||||
},
|
||||
],
|
||||
demo: {
|
||||
kicker: "Real Android flow",
|
||||
title: "Open an issue. Read context. Draft a comment.",
|
||||
body:
|
||||
"The demo material uses emulator screenshots from a real project. Interactive moments are animated as safe overlays, so no public test comment needs to be submitted.",
|
||||
},
|
||||
video: {
|
||||
kicker: "Promo video",
|
||||
title: "A quick walkthrough of the Android issue flow.",
|
||||
body:
|
||||
"The walkthrough shows a real emulator project, issue context, a simulated typewriter-style comment draft, and a light funk-style background track.",
|
||||
},
|
||||
download: {
|
||||
kicker: "GitHub APK",
|
||||
title: "Install from GitHub Releases.",
|
||||
package: "Package",
|
||||
api: "Service API",
|
||||
sha: "APK SHA256",
|
||||
latest: "Latest release",
|
||||
source: "Source code",
|
||||
},
|
||||
trustKicker: "Trust and privacy",
|
||||
trustItems: [
|
||||
"Independent companion app; no claim of Multica project ownership or maintainer endorsement.",
|
||||
"Published as its own Android app for Multica users, while keeping attribution and product boundaries explicit.",
|
||||
"Analytics requires user consent and is limited to an app_open event in the public Android build.",
|
||||
"No chat content, file content, email address, tokens, or precise location are intentionally reported.",
|
||||
],
|
||||
footer: {
|
||||
tagline: "Independent Android companion app for Multica users. GitHub-distributed APK.",
|
||||
multica: "Multica website",
|
||||
github: "GitHub",
|
||||
download: "Download APK",
|
||||
},
|
||||
},
|
||||
zh: {
|
||||
nav: {
|
||||
brand: "Multi-Casual Android",
|
||||
download: "下载",
|
||||
trust: "信任说明",
|
||||
relationship: "项目关系",
|
||||
language: "English",
|
||||
},
|
||||
hero: {
|
||||
eyebrow: "独立 companion app · 面向 Multica 工作流",
|
||||
title: "在 Android 上处理 Issue。",
|
||||
subtitle: "查看任务上下文、进入评论区、草拟回复,把核心 workspace 闭环带到手机上。",
|
||||
download: "下载 APK",
|
||||
github: "查看 GitHub",
|
||||
multica: "访问 Multica",
|
||||
typing: "已在 Android 上验证,看起来没问题。",
|
||||
},
|
||||
relationship: {
|
||||
kicker: "与 Multica 的关系",
|
||||
title: "礼貌、清楚、保持独立。",
|
||||
body:
|
||||
"Multi-Casual Android 是面向 Multica 用户的独立 companion app。它为了兼容现有工作流而连接 Multica 服务 API,但并不由 Multica 项目维护者维护、背书或发布,也不应该被表述为上游项目推出的 Multica Android App。",
|
||||
},
|
||||
featuresKicker: "覆盖能力",
|
||||
features: [
|
||||
{
|
||||
title: "Issue 工作流",
|
||||
body: "查看 Inbox 条目、打开 Issue 详情、阅读评论,在离开桌面时也能保留任务上下文。",
|
||||
},
|
||||
{
|
||||
title: "手机端回复",
|
||||
body: "进入评论输入流程,草拟回复,继续原本会在 Web 端处理的同一条讨论。",
|
||||
},
|
||||
{
|
||||
title: "兼容云服务流程",
|
||||
body: "Android App 是 Multica 后端服务的 companion;不内置服务端,也不为普通用户提供运行时后端切换。",
|
||||
},
|
||||
],
|
||||
demo: {
|
||||
kicker: "真实 Android 流程",
|
||||
title: "打开 Issue,阅读上下文,然后草拟评论。",
|
||||
body: "演示素材来自真实项目里的模拟器截图;交互部分用安全的叠加动画表现,不需要真的往公开项目里提交测试评论。",
|
||||
},
|
||||
video: {
|
||||
kicker: "宣传视频",
|
||||
title: "快速看一遍 Android 端 Issue 流程。",
|
||||
body:
|
||||
"这段 walkthrough 使用真实模拟器项目截图,展示 Issue 上下文、逐字输入感的评论草稿,以及一条轻快的 funk style 背景乐。",
|
||||
},
|
||||
download: {
|
||||
kicker: "GitHub APK",
|
||||
title: "从 GitHub Releases 安装。",
|
||||
package: "包名",
|
||||
api: "服务 API",
|
||||
sha: "APK SHA256",
|
||||
latest: "最新 Release",
|
||||
source: "源码仓库",
|
||||
},
|
||||
trustKicker: "信任与隐私",
|
||||
trustItems: [
|
||||
"这是独立 companion app,不声明拥有 Multica 项目,也不暗示维护者背书。",
|
||||
"作为独立 Android App 发布给 Multica 用户,同时明确署名和产品边界。",
|
||||
"公开 Android 版本里的 analytics 需要用户同意,并且仅限 app_open 事件。",
|
||||
"不会有意上报聊天内容、文件内容、邮箱、token 或精确位置。",
|
||||
],
|
||||
footer: {
|
||||
tagline: "面向 Multica 用户的独立 Android companion app,通过 GitHub 分发 APK。",
|
||||
multica: "Multica 网站",
|
||||
github: "GitHub",
|
||||
download: "下载 APK",
|
||||
},
|
||||
},
|
||||
} satisfies Record<Locale, unknown>;
|
||||
|
||||
function fade(reducedMotion: boolean | null, delay = 0): MotionProps {
|
||||
if (reducedMotion) {
|
||||
return {};
|
||||
}
|
||||
return {
|
||||
initial: { opacity: 0, y: 24 },
|
||||
animate: { opacity: 1, y: 0 },
|
||||
transition: { duration: 0.7, delay, ease: "easeOut" },
|
||||
};
|
||||
}
|
||||
|
||||
function reveal(reducedMotion: boolean | null): MotionProps {
|
||||
if (reducedMotion) {
|
||||
return {};
|
||||
}
|
||||
return {
|
||||
initial: { opacity: 0, y: 28 },
|
||||
whileInView: { opacity: 1, y: 0 },
|
||||
viewport: { once: true, margin: "-80px" },
|
||||
transition: { duration: 0.7, ease: "easeOut" },
|
||||
};
|
||||
}
|
||||
|
||||
function PhoneMockup({ typing }: { typing: string }) {
|
||||
const reducedMotion = useReducedMotion();
|
||||
return (
|
||||
<motion.div
|
||||
className="phone-wrap"
|
||||
animate={reducedMotion ? {} : { y: [0, -10, 0], rotate: [-1.5, -0.6, -1.5] }}
|
||||
transition={reducedMotion ? {} : { duration: 7, repeat: Infinity, ease: "easeInOut" }}
|
||||
>
|
||||
<div className="phone-shell">
|
||||
<div className="speaker" />
|
||||
<div className="phone-screen">
|
||||
<div className="phone-screen-shot" aria-label="Multi-Casual Android issue detail screen" />
|
||||
<motion.div
|
||||
className="typing-card"
|
||||
initial={reducedMotion ? false : { opacity: 0, y: 14, scale: 0.98 }}
|
||||
animate={reducedMotion ? { opacity: 1 } : { opacity: 1, y: 0, scale: 1 }}
|
||||
transition={{ delay: 1.05, duration: 0.55 }}
|
||||
>
|
||||
<span className="typing-dot" />
|
||||
{typing}
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="tap-ring" aria-hidden="true" />
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
const reducedMotion = useReducedMotion();
|
||||
const [locale, setLocale] = useState<Locale>("en");
|
||||
const t = copy[locale];
|
||||
|
||||
return (
|
||||
<main lang={locale === "zh" ? "zh-CN" : "en"}>
|
||||
<section className="hero section-pad">
|
||||
<div className="hero-bg" />
|
||||
<nav className="topbar" aria-label="Primary navigation">
|
||||
<a className="brand" href="#top" aria-label="Multi-Casual Android home">
|
||||
<span className="brand-mark">M</span>
|
||||
<span>{t.nav.brand}</span>
|
||||
</a>
|
||||
<div className="nav-links">
|
||||
<a href="#download">{t.nav.download}</a>
|
||||
<a href="#trust">{t.nav.trust}</a>
|
||||
<a href="#relationship">{t.nav.relationship}</a>
|
||||
<button className="language-toggle" type="button" onClick={() => setLocale(locale === "en" ? "zh" : "en")}>
|
||||
{t.nav.language}
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div id="top" className="hero-grid">
|
||||
<div className="hero-copy">
|
||||
<motion.p className="eyebrow" {...fade(reducedMotion, 0.05)}>
|
||||
{t.hero.eyebrow}
|
||||
</motion.p>
|
||||
<motion.h1 {...fade(reducedMotion, 0.14)}>{t.hero.title}</motion.h1>
|
||||
<motion.p className="hero-subtitle" {...fade(reducedMotion, 0.24)}>
|
||||
{t.hero.subtitle}
|
||||
</motion.p>
|
||||
<motion.div className="cta-row" {...fade(reducedMotion, 0.34)}>
|
||||
<motion.a className="button primary" href={release.releasesUrl} whileHover={{ y: -2 }} whileTap={{ scale: 0.98 }}>
|
||||
{t.hero.download}
|
||||
</motion.a>
|
||||
<motion.a className="button secondary" href={release.githubUrl} whileHover={{ y: -2 }} whileTap={{ scale: 0.98 }}>
|
||||
{t.hero.github}
|
||||
</motion.a>
|
||||
<motion.a className="button ghost" href={release.multicaUrl} whileHover={{ y: -2 }} whileTap={{ scale: 0.98 }}>
|
||||
{t.hero.multica}
|
||||
</motion.a>
|
||||
</motion.div>
|
||||
</div>
|
||||
<motion.div {...fade(reducedMotion, 0.18)}>
|
||||
<PhoneMockup typing={t.hero.typing} />
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<motion.section id="relationship" className="relationship section-pad" {...reveal(reducedMotion)}>
|
||||
<div className="section-kicker">{t.relationship.kicker}</div>
|
||||
<div className="two-col">
|
||||
<h2>{t.relationship.title}</h2>
|
||||
<p>{t.relationship.body}</p>
|
||||
</div>
|
||||
</motion.section>
|
||||
|
||||
<section className="features section-pad">
|
||||
<div className="section-kicker">{t.featuresKicker}</div>
|
||||
<div className="feature-grid">
|
||||
{t.features.map((feature, index) => (
|
||||
<motion.article key={feature.title} className="feature-card" {...reveal(reducedMotion)} transition={{ duration: 0.7, delay: index * 0.08 }}>
|
||||
<span>{String(index + 1).padStart(2, "0")}</span>
|
||||
<h3>{feature.title}</h3>
|
||||
<p>{feature.body}</p>
|
||||
</motion.article>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<motion.section className="demo section-pad" {...reveal(reducedMotion)}>
|
||||
<div className="section-kicker">{t.demo.kicker}</div>
|
||||
<div className="demo-grid">
|
||||
<div>
|
||||
<h2>{t.demo.title}</h2>
|
||||
<p>{t.demo.body}</p>
|
||||
</div>
|
||||
<div className="screenshot-stack" aria-label="Issue comment flow screenshots">
|
||||
<img className="shot back" src={issueDetail} alt="Issue detail screenshot" />
|
||||
<img className="shot front" src={commentFocused} alt="Focused issue comment input screenshot" />
|
||||
</div>
|
||||
</div>
|
||||
</motion.section>
|
||||
|
||||
<motion.section className="video-demo section-pad" {...reveal(reducedMotion)}>
|
||||
<div className="section-kicker">{t.video.kicker}</div>
|
||||
<div className="video-copy">
|
||||
<h2>{t.video.title}</h2>
|
||||
<p>{t.video.body}</p>
|
||||
</div>
|
||||
<video className="promo-video" src={promoVideo} controls playsInline preload="metadata" poster={commentFocused} />
|
||||
</motion.section>
|
||||
|
||||
<section id="download" className="download section-pad">
|
||||
<motion.div className="release-card" {...reveal(reducedMotion)}>
|
||||
<div className="section-kicker">{t.download.kicker}</div>
|
||||
<h2>{t.download.title}</h2>
|
||||
<div className="release-grid">
|
||||
<div>
|
||||
<span>{t.download.package}</span>
|
||||
<code>{release.packageName}</code>
|
||||
</div>
|
||||
<div>
|
||||
<span>{t.download.api}</span>
|
||||
<code>{release.apiUrl}</code>
|
||||
</div>
|
||||
<div className="hash-cell">
|
||||
<span>{t.download.sha}</span>
|
||||
<code>{release.sha256}</code>
|
||||
</div>
|
||||
</div>
|
||||
<pre><code>{`adb install -r Multi-Casual-Android.apk`}</code></pre>
|
||||
<div className="cta-row compact">
|
||||
<a className="button primary" href={release.releasesUrl}>{t.download.latest}</a>
|
||||
<a className="button secondary" href={release.githubUrl}>{t.download.source}</a>
|
||||
</div>
|
||||
</motion.div>
|
||||
</section>
|
||||
|
||||
<section id="trust" className="trust section-pad">
|
||||
<div className="section-kicker">{t.trustKicker}</div>
|
||||
<div className="trust-grid">
|
||||
{t.trustItems.map((item) => (
|
||||
<motion.div key={item} className="trust-item" {...reveal(reducedMotion)}>
|
||||
<span className="check">✓</span>
|
||||
<p>{item}</p>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer className="footer section-pad">
|
||||
<div>
|
||||
<strong>Multi-Casual Android</strong>
|
||||
<p>{t.footer.tagline}</p>
|
||||
</div>
|
||||
<div className="footer-links">
|
||||
<a href={release.multicaUrl}>{t.footer.multica}</a>
|
||||
<a href={release.githubUrl}>{t.footer.github}</a>
|
||||
<a href={release.releasesUrl}>{t.footer.download}</a>
|
||||
</div>
|
||||
</footer>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 397 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 279 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 293 KiB |
Binary file not shown.
@@ -0,0 +1,10 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import App from "./App";
|
||||
import "./styles.css";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
);
|
||||
@@ -0,0 +1,447 @@
|
||||
:root {
|
||||
--blue: #1647ff;
|
||||
--blue-2: #0f33c8;
|
||||
--yellow: #ffe45c;
|
||||
--ink: #101218;
|
||||
--muted: #687080;
|
||||
--paper: #f6f8fc;
|
||||
--card: #ffffff;
|
||||
--line: rgba(16, 18, 24, 0.1);
|
||||
--shadow: 0 24px 70px rgba(16, 18, 24, 0.14);
|
||||
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
color: var(--ink);
|
||||
background: var(--paper);
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
html { scroll-behavior: smooth; }
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-width: 320px;
|
||||
background:
|
||||
radial-gradient(circle at 80% 5%, rgba(255, 228, 92, 0.32), transparent 30%),
|
||||
radial-gradient(circle at 15% 25%, rgba(22, 71, 255, 0.14), transparent 28%),
|
||||
var(--paper);
|
||||
}
|
||||
|
||||
body, button, input { font: inherit; }
|
||||
|
||||
button { cursor: pointer; }
|
||||
|
||||
a { color: inherit; text-decoration: none; }
|
||||
|
||||
.section-pad { padding: 80px clamp(22px, 5vw, 76px); }
|
||||
|
||||
.hero {
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
padding-top: 26px;
|
||||
}
|
||||
|
||||
.hero-bg {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image:
|
||||
linear-gradient(rgba(16, 18, 24, 0.045) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(16, 18, 24, 0.045) 1px, transparent 1px);
|
||||
background-size: 56px 56px;
|
||||
mask-image: linear-gradient(to bottom, black 0%, transparent 88%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
width: min(1160px, 100%);
|
||||
margin: 0 auto 70px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 24px;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.72);
|
||||
backdrop-filter: blur(18px);
|
||||
box-shadow: 0 12px 36px rgba(16, 18, 24, 0.06);
|
||||
}
|
||||
|
||||
.brand, .nav-links { display: flex; align-items: center; gap: 10px; }
|
||||
|
||||
.brand { font-weight: 850; letter-spacing: -0.02em; }
|
||||
|
||||
.brand-mark {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 12px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: white;
|
||||
background: var(--blue);
|
||||
box-shadow: 0 8px 18px rgba(22, 71, 255, 0.32);
|
||||
}
|
||||
|
||||
.nav-links a {
|
||||
color: var(--muted);
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
padding: 8px 10px;
|
||||
}
|
||||
|
||||
.nav-links a:hover { color: var(--ink); }
|
||||
|
||||
.language-toggle {
|
||||
color: var(--blue);
|
||||
font-size: 14px;
|
||||
font-weight: 850;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid rgba(22, 71, 255, 0.18);
|
||||
border-radius: 999px;
|
||||
background: rgba(22, 71, 255, 0.07);
|
||||
}
|
||||
|
||||
.language-toggle:hover { background: rgba(22, 71, 255, 0.12); }
|
||||
|
||||
.hero-grid {
|
||||
width: min(1160px, 100%);
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.06fr) minmax(320px, 0.72fr);
|
||||
gap: clamp(28px, 7vw, 96px);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.eyebrow, .section-kicker {
|
||||
margin: 0 0 16px;
|
||||
color: var(--blue);
|
||||
font-size: 12px;
|
||||
font-weight: 900;
|
||||
letter-spacing: 0.15em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
h1, h2, h3, p { margin-top: 0; }
|
||||
|
||||
h1 {
|
||||
max-width: 780px;
|
||||
margin-bottom: 24px;
|
||||
font-size: clamp(54px, 8.2vw, 112px);
|
||||
line-height: 0.9;
|
||||
letter-spacing: -0.075em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: clamp(36px, 5vw, 72px);
|
||||
line-height: 0.94;
|
||||
letter-spacing: -0.065em;
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
|
||||
main[lang="zh-CN"] h1 {
|
||||
max-width: 700px;
|
||||
font-size: clamp(42px, 6.8vw, 86px);
|
||||
line-height: 0.98;
|
||||
letter-spacing: -0.07em;
|
||||
}
|
||||
|
||||
main[lang="zh-CN"] h2 {
|
||||
font-size: clamp(34px, 4.6vw, 62px);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 24px;
|
||||
line-height: 1.08;
|
||||
letter-spacing: -0.035em;
|
||||
}
|
||||
|
||||
.hero-subtitle, .relationship p, .demo p {
|
||||
max-width: 650px;
|
||||
color: #343b4a;
|
||||
font-size: clamp(18px, 2vw, 23px);
|
||||
line-height: 1.48;
|
||||
}
|
||||
|
||||
.cta-row { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 32px; }
|
||||
.cta-row.compact { margin-top: 22px; }
|
||||
|
||||
.button {
|
||||
display: inline-flex;
|
||||
min-height: 48px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 999px;
|
||||
padding: 0 20px;
|
||||
font-weight: 850;
|
||||
border: 1px solid transparent;
|
||||
transition: color 0.18s ease, background 0.18s ease, border-color 0.18s ease;
|
||||
}
|
||||
|
||||
.button.primary { color: white; background: var(--blue); box-shadow: 0 14px 34px rgba(22, 71, 255, 0.32); }
|
||||
.button.secondary { background: white; border-color: var(--line); }
|
||||
.button.ghost { color: var(--muted); background: rgba(255, 255, 255, 0.54); border-color: var(--line); }
|
||||
|
||||
.phone-wrap {
|
||||
width: min(390px, 82vw);
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
.phone-shell {
|
||||
position: relative;
|
||||
padding: 14px;
|
||||
border-radius: 54px;
|
||||
background: #11141d;
|
||||
box-shadow: var(--shadow), inset 0 0 0 2px rgba(255,255,255,0.1);
|
||||
}
|
||||
|
||||
.speaker {
|
||||
position: absolute;
|
||||
top: 18px;
|
||||
left: 50%;
|
||||
width: 72px;
|
||||
height: 8px;
|
||||
transform: translateX(-50%);
|
||||
border-radius: 999px;
|
||||
background: rgba(255,255,255,0.18);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.phone-screen {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius: 40px;
|
||||
background: white;
|
||||
aspect-ratio: 9 / 19.7;
|
||||
}
|
||||
|
||||
.phone-screen img { width: 100%; height: 100%; object-fit: cover; object-position: top center; display: block; }
|
||||
|
||||
.phone-screen-shot {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image: url("./assets/screenshots/02-issue-detail.png");
|
||||
background-size: cover;
|
||||
background-position: top center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.typing-card {
|
||||
position: absolute;
|
||||
left: 22px;
|
||||
right: 22px;
|
||||
bottom: 22px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 14px;
|
||||
border-radius: 20px;
|
||||
color: #142142;
|
||||
background: rgba(255,255,255,0.92);
|
||||
border: 1px solid rgba(22, 71, 255, 0.22);
|
||||
box-shadow: 0 20px 38px rgba(16, 18, 24, 0.18);
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.typing-dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
flex: none;
|
||||
border-radius: 999px;
|
||||
background: var(--yellow);
|
||||
box-shadow: 0 0 0 5px rgba(255, 228, 92, 0.26);
|
||||
}
|
||||
|
||||
.tap-ring {
|
||||
position: absolute;
|
||||
right: -8px;
|
||||
bottom: 158px;
|
||||
width: 76px;
|
||||
height: 76px;
|
||||
border-radius: 999px;
|
||||
border: 2px solid var(--blue);
|
||||
background: rgba(255, 228, 92, 0.7);
|
||||
box-shadow: 0 0 0 16px rgba(255, 228, 92, 0.22);
|
||||
}
|
||||
|
||||
.relationship, .features, .demo, .video-demo, .download, .trust, .footer {
|
||||
width: min(1160px, calc(100% - 44px));
|
||||
margin: 0 auto;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.two-col, .demo-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 0.85fr) minmax(320px, 1fr);
|
||||
gap: clamp(24px, 5vw, 76px);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.feature-grid, .trust-grid, .release-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.feature-card, .trust-item, .release-card {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 30px;
|
||||
background: rgba(255,255,255,0.76);
|
||||
box-shadow: 0 18px 48px rgba(16,18,24,0.08);
|
||||
}
|
||||
|
||||
.feature-card { padding: 28px; min-height: 250px; }
|
||||
.feature-card span { color: var(--blue); font-weight: 950; font-size: 13px; letter-spacing: 0.12em; }
|
||||
.feature-card p, .trust-item p { color: var(--muted); line-height: 1.65; }
|
||||
|
||||
.demo { overflow: hidden; }
|
||||
.screenshot-stack { min-height: 560px; position: relative; overflow: hidden; border-radius: 42px; }
|
||||
.shot {
|
||||
position: absolute;
|
||||
width: min(310px, 52vw);
|
||||
height: 620px;
|
||||
border-radius: 34px;
|
||||
border: 10px solid #11141d;
|
||||
box-shadow: var(--shadow);
|
||||
background: white;
|
||||
object-fit: cover;
|
||||
object-position: top center;
|
||||
}
|
||||
.shot.back { right: 210px; top: 40px; transform: rotate(-7deg); opacity: 0.78; }
|
||||
.shot.front { right: 12px; top: 0; transform: rotate(5deg); }
|
||||
|
||||
.video-demo {
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.video-copy {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 0.95fr) minmax(320px, 1fr);
|
||||
gap: clamp(24px, 5vw, 76px);
|
||||
align-items: end;
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
.video-copy p {
|
||||
color: #343b4a;
|
||||
font-size: clamp(17px, 1.7vw, 21px);
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.promo-video, .video-placeholder {
|
||||
display: block;
|
||||
width: 100%;
|
||||
aspect-ratio: 16 / 9;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 34px;
|
||||
background: #0e1320;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.promo-video { object-fit: cover; }
|
||||
|
||||
.video-placeholder {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: white;
|
||||
background:
|
||||
radial-gradient(circle at 70% 24%, rgba(255, 228, 92, 0.22), transparent 34%),
|
||||
linear-gradient(135deg, #0e1320, #172453);
|
||||
}
|
||||
|
||||
.video-placeholder span {
|
||||
width: 86px;
|
||||
height: 86px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
margin-bottom: 18px;
|
||||
border-radius: 999px;
|
||||
color: var(--blue);
|
||||
background: var(--yellow);
|
||||
font-size: 44px;
|
||||
font-weight: 950;
|
||||
box-shadow: 0 18px 42px rgba(255, 228, 92, 0.24);
|
||||
}
|
||||
|
||||
.video-placeholder strong {
|
||||
display: block;
|
||||
color: white;
|
||||
font-size: clamp(22px, 3vw, 42px);
|
||||
letter-spacing: -0.04em;
|
||||
}
|
||||
|
||||
.download { padding-top: 40px; }
|
||||
.release-card { padding: clamp(28px, 5vw, 54px); }
|
||||
.release-grid { margin: 30px 0 22px; }
|
||||
.release-grid div {
|
||||
min-width: 0;
|
||||
padding: 18px;
|
||||
border-radius: 20px;
|
||||
background: #f7f9ff;
|
||||
border: 1px solid rgba(22, 71, 255, 0.13);
|
||||
}
|
||||
.release-grid span { display: block; color: var(--muted); font-size: 12px; font-weight: 900; text-transform: uppercase; letter-spacing: 0.12em; margin-bottom: 8px; }
|
||||
.release-grid code, pre code { font-family: "SFMono-Regular", ui-monospace, Menlo, Consolas, "PingFang SC", monospace; }
|
||||
.release-grid code { display: block; overflow-wrap: anywhere; color: var(--ink); font-size: 14px; }
|
||||
.hash-cell { grid-column: span 3; }
|
||||
pre {
|
||||
overflow: auto;
|
||||
margin: 0;
|
||||
padding: 18px 20px;
|
||||
border-radius: 20px;
|
||||
color: #eef4ff;
|
||||
background: #0e1320;
|
||||
}
|
||||
|
||||
.trust-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
.trust-item { display: flex; gap: 14px; padding: 22px; align-items: flex-start; }
|
||||
.check {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
flex: none;
|
||||
color: white;
|
||||
border-radius: 999px;
|
||||
background: var(--blue);
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 24px;
|
||||
align-items: flex-start;
|
||||
color: var(--muted);
|
||||
border-top: 1px solid var(--line);
|
||||
}
|
||||
.footer strong { color: var(--ink); }
|
||||
.footer-links { display: flex; gap: 18px; flex-wrap: wrap; font-weight: 800; color: var(--ink); }
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.topbar { border-radius: 24px; align-items: flex-start; flex-direction: column; }
|
||||
.nav-links { flex-wrap: wrap; }
|
||||
.hero-grid, .two-col, .demo-grid, .video-copy, .feature-grid, .trust-grid, .release-grid { grid-template-columns: 1fr; }
|
||||
.hash-cell { grid-column: auto; }
|
||||
.screenshot-stack { min-height: 680px; }
|
||||
.shot { width: min(300px, 74vw); }
|
||||
.shot.back { right: auto; left: 4%; }
|
||||
.shot.front { right: 4%; top: 160px; }
|
||||
.footer { flex-direction: column; }
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*, *::before, *::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
scroll-behavior: auto !important;
|
||||
}
|
||||
}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["DOM", "DOM.Iterable", "ES2022"],
|
||||
"allowJs": false,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": []
|
||||
}
|
||||
Reference in New Issue
Block a user