Merge branch 'main' into eng-1896-add-date-range-to-search-options

This commit is contained in:
Jamie Pine
2025-01-03 17:55:23 -08:00
committed by GitHub
192 changed files with 6991 additions and 4462 deletions

66
.zed/settings.json Normal file
View File

@@ -0,0 +1,66 @@
// Folder-specific settings
//
// For a full list of overridable settings, and general information on folder-specific settings,
// see the documentation: https://zed.dev/docs/configuring-zed#settings-files
{
"inlay_hints": {
"enabled": false
},
"languages": {
"Rust": {
"enable_language_server": true,
"formatter": "language_server",
"inlay_hints": {
"enabled": true,
"show_type_hints": true,
"show_parameter_hints": true,
"show_other_hints": true,
"show_background": false,
"edit_debounce_ms": 700,
"scroll_debounce_ms": 50
}
},
"TOML": {
"formatter": "language_server"
}
},
"lsp": {
"rust-analyzer": {
"initialization_options": {
"procMacro": {
"enable": true
},
"diagnostics": {
"experimental": {
"enable": false
}
},
"showUnlinkedFileNotification": false
}
}
},
"file_scan_exclusions": [
"node_modules",
"**/node_modules",
"**/bower_components",
"**/*.code-search",
"**/*.contentlayer",
"**/*.next",
"**/dist",
"apps/mobile/ios/Pods",
"apps/mobile/android",
"apps/mobile/ios",
"**/.git",
"**/.svn",
"**/.hg",
"**/CVS",
"**/.DS_Store"
],
"format_on_save": "on",
"ensure_final_newline_on_save": true,
"remove_trailing_whitespace_on_save": true,
"tab_size": 4,
"hard_tabs": false,
"show_whitespaces": "selection",
"show_completion_documentation": true
}

View File

@@ -38,17 +38,18 @@ This project uses [Cargo](https://doc.rust-lang.org/cargo/getting-started/instal
To make changes locally, follow these steps:
1. Clone & enter the repository:
```
git clone https://github.com/spacedriveapp/spacedrive && cd spacedrive
```
Alternatively, if youve already cloned the repo locally, pull the latest changes with: `git pull`
` git clone https://github.com/spacedriveapp/spacedrive && cd spacedrive
`
Alternatively, if youve already cloned the repo locally, pull the latest changes with: `git pull`
> [!TIP]
> Consider running `pnpm clean` after pulling the repository if you're returning to it from previously to avoid old files conflicting.
2. Configure your system environment for Spacedrive development
- For Unix users (Linux / macOS), run: `./scripts/setup.sh`
- For Windows users, run: `.\scripts\setup.ps1` via PowerShell.
3. Configure your system environment for Spacedrive development
- For Unix users (Linux / macOS), run: `./scripts/setup.sh`
- For Windows users, run: `.\scripts\setup.ps1` via PowerShell.
> [!NOTE]
> This script ([Unix](https://github.com/spacedriveapp/spacedrive/blob/main/scripts/setup.sh) / [Windows](https://github.com/spacedriveapp/spacedrive/blob/main/scripts/setup.ps1)) will check for if Rust and pnpm are installed then proceed to install any other required dependencies for Spacedrive to build via your system's respective package manager.
3. Install NodeJS dependencies: `pnpm i`
4. Prepare the build: `pnpm prep`. This will run all necessary codegen and build required dependencies.
@@ -58,13 +59,16 @@ To make changes locally, follow these steps:
> The test files will be located in a directory called `test-data` in the root of the Spacedrive repository.
To run the **desktop** app, run:
```
pnpm tauri dev
```
> [!NOTE]
> The Tauri desktop app always runs its own instance of the backend and will not connect to a separately initiated `sd-server` instance.
To run the **backend server**, run:
```
cargo run -p sd-server
```
@@ -73,23 +77,29 @@ cargo run -p sd-server
> If necessary, [DevTools](https://tauri.app/v1/guides/debugging/application/#webview-console) for the WebView can be opened by pressing <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>I</kbd> (Linux and Windows) or <kbd>Command</kbd>+<kbd>Option</kbd>+<kbd>I</kbd> (macOS) in the desktop app.
>
> Also, React DevTools can be launched using `pnpx react-devtools`.
However, it must be executed before starting the desktop app for it to connect.
> However, it must be executed before starting the desktop app for it to connect.
To run the **web** app (requires the backend to be running), run:
```
pnpm web dev
```
> [!TIP]
> You can also quickly launch the web interface together with the backend with:
>
> ```
> pnpm dev:web
> ```
To run the **e2e tests** for the web app:
```
pnpm web test:e2e
```
If you are developing a new test, you can execute Cypress in interactive mode with:
```
pnpm web test:interactive
```
@@ -97,19 +107,21 @@ pnpm web test:interactive
#### Troubleshooting
- If you encounter any issues, ensure that you are using the following versions of Rust, Node.js and pnpm:
| tool | version |
| ---- | ------- |
| Rust | [`1.81`](rust-toolchain.toml) |
| tool | version |
| ---- | ------- |
| Rust | [`1.81`](rust-toolchain.toml) |
| Node.js | [`18.18`](.nvmrc) |
| pnpm | `9.4.0` |
| pnpm | `9.4.0` |
[`rustup`](https://rustup.rs/) & [`nvm`](https://github.com/nvm-sh/nvm) should both pick up on the appropriate versions of the Rust Toolchain & Node respectively from the project automatically.
> **Note**: If you get a local migration error in development, you might need to set the following environment variables: [database documentation](docs/developers/architecture/database.mdx#environment-variables).
- After cleaning out your build artifacts using `pnpm clean`, it's necessary to re-run `pnpm prep`.
[`rustup`](https://rustup.rs/) & [`nvm`](https://github.com/nvm-sh/nvm) should both pick up on the appropriate versions of the Rust Toolchain & Node respectively from the project automatically.
- Make sure to read the [guidelines](https://spacedrive.com/docs/developers/prerequisites/guidelines) to ensure that your code follows a similar style to ours.
- After cleaning out your build artifacts using `pnpm clean`, it's necessary to re-run `pnpm prep`.
- After you finish making your changes and committing them to your branch, make sure to execute `pnpm autoformat` to fix any style inconsistency in your code.
- Make sure to read the [guidelines](https://spacedrive.com/docs/developers/prerequisites/guidelines) to ensure that your code follows a similar style to ours.
- After you finish making your changes and committing them to your branch, make sure to execute `pnpm autoformat` to fix any style inconsistency in your code.
### Landing Page
@@ -136,53 +148,56 @@ To run the mobile app:
> Most modern phones use `arm64-v8a` while the Android Studio embedded emulator runs `x86_64`
If you wish to debug directly on a local Android device:
- Install [ADB](https://developer.android.com/tools/adb)
- On macOS use [homebrew](https://brew.sh/): `brew install adb`
- [Configure debugging on your device](https://developer.android.com/tools/adb#Enabling)
- Select "Remember this device" & "Trust" when connecting over USB.
- Run `pnpm mobile android` with your device connected via USB.
>[!TIP]
- Install [ADB](https://developer.android.com/tools/adb)
- On macOS use [homebrew](https://brew.sh/): `brew install adb`
- [Configure debugging on your device](https://developer.android.com/tools/adb#Enabling)
- Select "Remember this device" & "Trust" when connecting over USB.
- Run `pnpm mobile android` with your device connected via USB.
> [!TIP]
> To access the logs from `sd-core` when running on device, run the following command:
>
> ```
> adb logcat | grep -i com.spacedrive.app
> ```
#### iOS
- Install the latest version of [Xcode](https://apps.apple.com/au/app/xcode/id497799835) and Simulator if you wish to emulate an iOS device on your Mac.
- When running Xcode for the first time, make sure to select the latest version of iOS.
- Run `pnpm mobile ios` in the terminal to build & run the app on the Simulator.
- To run the app in debug mode with backend (`sd-core`) logging, comment out the following lines before running the above command:
https://github.com/spacedriveapp/spacedrive/blob/d180261ca5a93388486742e8f921e895e9ec26a4/apps/mobile/modules/sd-core/ios/build-rust.sh#L51-L54
#### iOS
- Install the latest version of [Xcode](https://apps.apple.com/au/app/xcode/id497799835) and Simulator if you wish to emulate an iOS device on your Mac.
- When running Xcode for the first time, make sure to select the latest version of iOS.
- Run `pnpm mobile ios` in the terminal to build & run the app on the Simulator.
- To run the app in debug mode with backend (`sd-core`) logging, comment out the following lines before running the above command:
https://github.com/spacedriveapp/spacedrive/blob/d180261ca5a93388486742e8f921e895e9ec26a4/apps/mobile/modules/sd-core/ios/build-rust.sh#L51-L54
You can now get backend (`sd-core`) logs from the Simulator by running the following command:
```
xcrun simctl launch --console booted com.spacedrive.app
```
- If you'd like to run the app on device, run:
```
pnpm mobile ios --device
```
```
xcrun simctl launch --console booted com.spacedrive.app
```
- If you'd like to run the app on device, run: `pnpm mobile ios --device`
> [!IMPORTANT]
> Note that you can only get `sd-core` logs from the app when running it on device by running the frontend and backend separately.
To run the backend (`sd-core`) separately, open up Xcode by running:
```
xed apps/mobile/ios
```
Select from the top if you wish to start on device or Simulator, and press play.
| Select Device | Run the App | Build & Core logs are found here |
| --- | --- | --- |
|![](./apps/landing/public/images/xcode-run-sd-core.01.png)|![](./apps/landing/public/images/xcode-run-sd-core.02.png)|![](./apps/landing/public/images/xcode-run-sd-core.03.png)|
| Select Device | Run the App | Build & Core logs are found here |
| ---------------------------------------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------- |
| ![](./apps/landing/public/images/xcode-run-sd-core.01.png) | ![](./apps/landing/public/images/xcode-run-sd-core.02.png) | ![](./apps/landing/public/images/xcode-run-sd-core.03.png) |
To run the frontend, run the following:
```
pnpm mobile start
```
> [!IMPORTANT]
> The frontend is not functional without the sd-core running as well.
### Pull Request
Once you have finished making your changes, create a pull request (PR) to submit them.

BIN
Cargo.lock generated
View File

Binary file not shown.

View File

@@ -20,7 +20,7 @@ rust-version = "1.81"
[workspace.dependencies]
# First party dependencies
sd-cloud-schema = { git = "https://github.com/spacedriveapp/cloud-services-schema", rev = "74af2a727c" }
sd-cloud-schema = { git = "https://github.com/spacedriveapp/cloud-services-schema", rev = "4e4565bee4" }
# Third party dependencies used by one or more of our crates
async-channel = "2.3"

View File

@@ -3,16 +3,20 @@ import SwiftRs
@objc
public enum AppThemeType: Int {
case auto = -1
case light = 0
case dark = 1
case auto = -1
case light = 0
case dark = 1
}
var activity: NSObjectProtocol?
private let activityLock = NSLock()
private var activity: NSObjectProtocol?
private var isThemeUpdating = false
@_cdecl("disable_app_nap")
public func disableAppNap(reason: SRString) -> Bool {
// Check if App Nap is already disabled
activityLock.lock()
defer { activityLock.unlock() }
guard activity == nil else {
return false
}
@@ -26,37 +30,55 @@ public func disableAppNap(reason: SRString) -> Bool {
@_cdecl("enable_app_nap")
public func enableAppNap() -> Bool {
// Check if App Nap is already enabled
guard let pinfo = activity else {
activityLock.lock()
defer { activityLock.unlock() }
guard let currentActivity = activity else {
return false
}
ProcessInfo.processInfo.endActivity(pinfo)
ProcessInfo.processInfo.endActivity(currentActivity)
activity = nil
return true
}
@_cdecl("lock_app_theme")
public func lockAppTheme(themeType: AppThemeType) {
var theme: NSAppearance?
switch themeType {
case .auto:
theme = nil
case .dark:
theme = NSAppearance(named: .darkAqua)!
case .light:
theme = NSAppearance(named: .aqua)!
}
DispatchQueue.main.async {
NSApp.appearance = theme
// Trigger a repaint of the window
if let window = NSApplication.shared.mainWindow {
window.invalidateShadow()
window.displayIfNeeded()
// Prevent concurrent theme updates
guard !isThemeUpdating else {
return
}
isThemeUpdating = true
let theme: NSAppearance?
switch themeType {
case .auto:
theme = nil
case .dark:
theme = NSAppearance(named: .darkAqua)
case .light:
theme = NSAppearance(named: .aqua)
}
// Use sync to ensure completion before return
DispatchQueue.main.sync {
autoreleasepool {
NSApp.appearance = theme
if let window = NSApplication.shared.mainWindow {
NSAnimationContext.runAnimationGroup({ context in
context.duration = 0
window.invalidateShadow()
window.displayIfNeeded()
}, completionHandler: {
isThemeUpdating = false
})
} else {
isThemeUpdating = false
}
}
}
}
}
@_cdecl("set_titlebar_style")

View File

@@ -12,12 +12,13 @@
"lint": "eslint src --cache"
},
"dependencies": {
"@spacedrive/rspc-client": "github:spacedriveapp/rspc#path:packages/client&6a77167495",
"@spacedrive/rspc-tauri": "github:spacedriveapp/rspc#path:packages/tauri&6a77167495",
"@crabnebula/tauri-plugin-drag": "^2.0.0",
"@remix-run/router": "=1.13.1",
"@sd/client": "workspace:*",
"@sd/interface": "workspace:*",
"@sd/ui": "workspace:*",
"@spacedrive/rspc-client": "github:spacedriveapp/rspc#path:packages/client&6a77167495",
"@spacedrive/rspc-tauri": "github:spacedriveapp/rspc#path:packages/tauri&6a77167495",
"@t3-oss/env-core": "^0.7.1",
"@tanstack/react-query": "^5.59",
"@tauri-apps/api": "=2.0.3",
@@ -31,7 +32,7 @@
"react-dom": "^18.2.0",
"react-router-dom": "=6.20.1",
"sonner": "^1.0.3",
"supertokens-web-js": "^0.13.0"
"supertokens-web-js": "=0.13.0"
},
"devDependencies": {
"@sd/config": "workspace:*",

View File

@@ -32,22 +32,25 @@ thiserror = { workspace = true }
tokio = { workspace = true, features = ["sync"] }
tracing = { workspace = true }
uuid = { workspace = true, features = ["serde"] }
base64 = { workspace = true }
# Specific Desktop dependencies
# WARNING: Do NOT enable default features, as that vendors dbus (see below)
opener = { version = "0.7.1", features = ["reveal"], default-features = false }
specta-typescript = "=0.0.7"
tauri-plugin-clipboard-manager = "=2.0.1"
tauri-plugin-cors-fetch = { path = "../../../crates/tauri-plugin-cors-fetch" }
tauri-plugin-deep-link = "=2.0.1"
tauri-plugin-dialog = "=2.0.3"
tauri-plugin-http = "=2.0.3"
tauri-plugin-os = "=2.0.1"
tauri-plugin-shell = "=2.0.2"
tauri-plugin-updater = "=2.0.2"
tauri-plugin-drag = "2.0.0"
drag = "2.0.0"
# memory allocator
mimalloc = { workspace = true }
tauri-plugin-cors-fetch = "2.1.1"
mimalloc = { workspace = true }
[dependencies.tauri]
features = ["linux-libxdo", "macos-private-api", "native-tls-vendored", "unstable"]

View File

@@ -27,6 +27,7 @@
"core:window:allow-start-dragging",
"core:webview:allow-internal-toggle-devtools",
"cors-fetch:default",
"drag:default",
{
"identifier": "http:default",
"allow": [

View File

@@ -0,0 +1,223 @@
// Import required dependencies for drag and drop operations, serialization, and async functionality
use drag::{DragItem, Image, Options};
use serde::{Deserialize, Serialize};
use specta::Type;
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use tauri::{ipc::Channel, Manager, PhysicalPosition, State, WebviewWindow};
// DragState wraps a thread-safe boolean flag to track drag operation status
#[derive(Clone)]
pub struct DragState(pub Arc<Mutex<bool>>);
// Default implementation for DragState initializes with false
impl Default for DragState {
fn default() -> Self {
Self(Arc::new(Mutex::new(false)))
}
}
// Enum to represent the result of a drag operation (serializable for IPC)
#[derive(Serialize, Deserialize, Type, Clone)]
pub enum WrappedDragResult {
Dropped,
Cancel,
}
// Structure to hold cursor position coordinates (serializable for IPC)
#[derive(Serialize, Deserialize, Type, Clone)]
pub struct WrappedCursorPosition {
x: i32,
y: i32,
}
// Combined structure for drag operation results (serializable for IPC)
#[derive(Serialize, Deserialize, Type, Clone)]
pub struct CallbackResult {
result: WrappedDragResult,
#[serde(rename = "cursorPos")]
cursor_pos: WrappedCursorPosition,
}
// Conversion implementations for drag-rs types to our wrapped types
impl From<drag::DragResult> for WrappedDragResult {
fn from(result: drag::DragResult) -> Self {
match result {
drag::DragResult::Dropped => WrappedDragResult::Dropped,
drag::DragResult::Cancel => WrappedDragResult::Cancel,
}
}
}
impl From<drag::CursorPosition> for WrappedCursorPosition {
fn from(pos: drag::CursorPosition) -> Self {
WrappedCursorPosition { x: pos.x, y: pos.y }
}
}
// Global flag to track if position tracking is active
static TRACKING: AtomicBool = AtomicBool::new(false);
#[tauri::command(async)]
/// Initiates a drag and drop operation with cursor position tracking
///
/// # Arguments
/// * `window` - The Tauri window instance
/// * `_state` - Current drag state (unused)
/// * `files` - Vector of file paths to be dragged
/// * `icon_path` - Path to the preview icon for the drag operation
/// * `on_event` - Channel for communicating drag operation events back to the frontend
#[specta::specta]
pub async fn start_drag(
window: WebviewWindow,
_state: State<'_, DragState>,
files: Vec<String>,
icon_path: String,
on_event: Channel<CallbackResult>,
) -> Result<(), String> {
// Fast atomic swap for tracking state
match TRACKING.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) {
Ok(_) => {
println!("Starting position tracking");
}
Err(_) => {
// If already tracking, stop previous instance quickly
TRACKING.store(false, Ordering::SeqCst);
tokio::time::sleep(tokio::time::Duration::from_millis(16)).await;
TRACKING.store(true, Ordering::SeqCst);
println!("Restarting position tracking");
}
}
// Pre-allocate resources before spawning task
let window_handle = Arc::new(window);
let app_handle = window_handle.app_handle();
// Initialize control flags
let cancel_flag = Arc::new(AtomicBool::new(false));
let is_completed = Arc::new(AtomicBool::new(false));
// Prepare resources once with minimal cloning
let tracking_resources = Arc::new((files.clone(), icon_path.clone(), Arc::new(on_event)));
println!("Starting position tracking");
// Get handles for window and app management
let window_clone = window_handle.clone();
let app_handle_owned = app_handle.to_owned();
let window_owned = window_clone.to_owned();
// Control flags for operation state
let is_completed_clone = is_completed.clone();
// Spawn background task for cursor tracking
tokio::spawn(async move {
// Initialize tracking state
let mut last_position = (0.0, 0.0);
let mut last_message_time = Instant::now();
let threshold = 1.0; // Minimum movement threshold
let message_debounce = Duration::from_millis(32); // State update interval
let mut was_inside = false;
// Main tracking loop
while TRACKING.load(Ordering::SeqCst) && !is_completed.load(Ordering::SeqCst) {
let window_for_check = window_owned.clone();
// Skip if window is not focused
if !window_for_check.is_focused().unwrap_or(false) {
tokio::time::sleep(tokio::time::Duration::from_millis(8)).await;
continue;
}
// Get current cursor and window positions
if let (Ok(cursor_position), Ok(window_position), Ok(window_size)) = (
window_for_check.cursor_position(),
window_for_check.outer_position(),
window_for_check.inner_size(),
) {
// Calculate cursor position relative to window
let relative_position = PhysicalPosition::new(
cursor_position.x - window_position.x as f64,
cursor_position.y - window_position.y as f64,
);
// Check if cursor is inside window boundaries
let is_inside = relative_position.x >= 0.0
&& relative_position.y >= 0.0
&& relative_position.x <= window_size.width as f64
&& relative_position.y <= window_size.height as f64;
// Process state changes if cursor moved enough
if is_inside != was_inside
&& ((relative_position.x - last_position.0).abs() > threshold
|| (relative_position.y - last_position.1).abs() > threshold)
{
let now = Instant::now();
if now.duration_since(last_message_time) >= message_debounce {
// Prepare resources for drag operation
let files_for_drag = tracking_resources.0.clone();
let icon_path_for_drag = tracking_resources.1.clone();
let on_event_for_drag = tracking_resources.2.clone();
let is_completed = is_completed_clone.clone();
let cancel_flag_clone = cancel_flag.clone();
let window_for_drag = window_owned.clone();
// Execute drag operation on main thread
app_handle_owned
.run_on_main_thread(move || {
if !is_inside {
println!("Starting drag operation");
// Create drag items
let paths: Vec<PathBuf> =
files_for_drag.iter().map(PathBuf::from).collect();
let item = DragItem::Files(paths);
let preview_icon =
Image::File(PathBuf::from(&icon_path_for_drag));
// Start the drag operation
if let Ok(_) = drag::start_drag(
&window_for_drag,
item,
preview_icon,
move |result, cursor_pos| {
// Send result back to frontend
let _ = on_event_for_drag.send(CallbackResult {
result: result.into(),
cursor_pos: cursor_pos.into(),
});
// Mark operation as completed
is_completed.store(true, Ordering::SeqCst);
TRACKING.store(false, Ordering::SeqCst);
},
Options::default(),
) {
println!("Drag operation started");
}
} else {
println!("Cursor returned to window");
cancel_flag_clone.store(true, Ordering::SeqCst);
// We have this for now, but technically, it doesn't do anything.
// I'm still trying to figure out how to cancel mid-drag without the user having to cancel the dragging on the frontend too.
// - @Rocky43007
}
})
.unwrap_or_default();
// Update tracking state
last_message_time = now;
was_inside = is_inside;
last_position = (relative_position.x, relative_position.y);
}
}
}
// Prevent excessive CPU usage
tokio::time::sleep(tokio::time::Duration::from_millis(8)).await;
}
println!("Tracking instance stopped");
});
Ok(())
}

View File

@@ -22,6 +22,7 @@ use tokio::task::block_in_place;
use tokio::time::sleep;
use tracing::{debug, error};
mod drag;
mod file;
mod menu;
mod tauri_plugins;
@@ -200,6 +201,7 @@ async fn main() -> tauri::Result<()> {
set_menu_bar_item_state,
request_fda_macos,
open_trash_in_os_explorer,
drag::start_drag,
file::open_file_paths,
file::open_ephemeral_files,
file::get_file_path_open_with_apps,
@@ -362,6 +364,7 @@ async fn main() -> tauri::Result<()> {
.plugin(tauri_plugin_updater::Builder::new().build())
.plugin(updater::plugin())
.manage(updater::State::default())
.manage(drag::DragState::default())
.build(tauri::generate_context!())?
.run(|_, _| {});

View File

@@ -3,7 +3,13 @@ import { QueryClientProvider } from '@tanstack/react-query';
import { listen } from '@tauri-apps/api/event';
import { PropsWithChildren, startTransition, useEffect, useMemo, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { RspcProvider, useBridgeMutation } from '@sd/client';
import {
getItemFilePath,
libraryClient,
RspcProvider,
useBridgeMutation,
useSelector
} from '@sd/client';
import {
createRoutes,
DeeplinkEvent,
@@ -16,13 +22,15 @@ import {
} from '@sd/interface';
import { RouteTitleContext } from '@sd/interface/hooks/useRouteTitle';
import '@sd/ui/style/style.scss';
import '@sd/ui/style';
import { Channel, invoke } from '@tauri-apps/api/core';
import SuperTokens from 'supertokens-web-js';
import EmailPassword from 'supertokens-web-js/recipe/emailpassword';
import Passwordless from 'supertokens-web-js/recipe/passwordless';
import Session from 'supertokens-web-js/recipe/session';
import ThirdParty from 'supertokens-web-js/recipe/thirdparty';
import { explorerStore } from '@sd/interface/app/$libraryId/Explorer/store';
// TODO: Bring this back once upstream is fixed up.
// const client = hooks.createClient({
// links: [
@@ -37,6 +45,7 @@ import getWindowHandler from '@sd/interface/app/$libraryId/settings/client/accou
import { useLocale } from '@sd/interface/hooks';
import { AUTH_SERVER_URL, getTokens } from '@sd/interface/util';
import { Transparent } from '../../../packages/assets/images';
import { commands } from './commands';
import { platform } from './platform';
import { queryClient } from './query';
@@ -46,6 +55,7 @@ import { createUpdater } from './updater';
declare global {
interface Window {
enableCORSFetch: (enable: boolean) => void;
useDragAndDrop: () => void;
}
}
@@ -67,11 +77,89 @@ SuperTokens.init({
const startupError = (window as any).__SD_ERROR__ as string | undefined;
function useDragAndDrop() {
const dragState = useSelector(explorerStore, (s) => s.drag);
useEffect(() => {
console.log('Drag effect triggered:', {
dragStateType: dragState?.type,
itemCount: dragState?.type === 'dragging' ? dragState?.items?.length : undefined
});
(async () => {
if (dragState?.type === 'dragging' && dragState.items.length > 0) {
console.log('Starting drag operation with items:', dragState.items);
const items = await Promise.all(
dragState.items.map(async (item) => {
const data = getItemFilePath(item);
if (!data) {
console.log('No file path data for item:', item);
return;
}
const file_path =
'path' in data ? data.path : await libraryClient.query(['files.getPath', data.id]);
console.log('Resolved file path:', file_path);
return {
type: 'explorer-item',
file_path: file_path
};
})
);
const image = Transparent.split('/@fs')[1]!;
console.log('Using preview image:', image);
const validFiles = items.filter(Boolean).map((item) => item?.file_path);
console.log('Invoking start_drag with files:', validFiles);
try {
const channel = new Channel<{
result: 'Dropped' | 'Cancelled';
cursorPos: { x: number; y: number };
}>();
channel.onmessage = (payload) => {
console.log('Drag completed:', {
result: payload.result,
position: payload.cursorPos,
timestamp: new Date().toISOString()
});
if (payload.result === 'Dropped') {
console.log('Drop location:', {
x: payload.cursorPos.x,
y: payload.cursorPos.y,
screen: window.screen
});
}
explorerStore.drag = null;
};
await invoke('start_drag', {
files: validFiles,
iconPath: image,
onEvent: channel
});
console.log('start_drag invoked successfully');
} catch (error) {
console.error('Failed to start drag:', error);
explorerStore.drag = null;
}
}
})();
}, [dragState]);
}
export default function App() {
useEffect(() => {
// This tells Tauri to show the current window because it's finished loading
commands.appReady();
window.enableCORSFetch(true);
window.useDragAndDrop = useDragAndDrop;
// .then(() => {
// if (import.meta.env.PROD) window.fetch = fetch;
// });
@@ -126,13 +214,15 @@ type RedirectPath = { pathname: string; search: string | undefined };
function AppInner() {
const [tabs, setTabs] = useState(() => [createTab()]);
const [selectedTabIndex, setSelectedTabIndex] = useState(0);
const tokens = getTokens();
const cloudBootstrap = useBridgeMutation('cloud.bootstrap');
useEffect(() => {
// If the access token and/or refresh token are missing, we need to skip the cloud bootstrap
if (tokens.accessToken.length === 0 || tokens.refreshToken.length === 0) return;
cloudBootstrap.mutate([tokens.accessToken, tokens.refreshToken]);
(async () => {
const tokens = await getTokens();
// If the access token and/or refresh token are missing, we need to skip the cloud bootstrap
if (tokens.accessToken.length === 0 || tokens.refreshToken.length === 0) return;
cloudBootstrap.mutate([tokens.accessToken, tokens.refreshToken]);
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@@ -204,6 +294,43 @@ function AppInner() {
};
}, [selectedTab.element]);
const SizeDisplay = () => {
const [size, setSize] = useState({
width: window.innerWidth,
height: window.innerHeight
});
useEffect(() => {
const handleResize = () => {
setSize({
width: window.innerWidth,
height: window.innerHeight
});
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return (
<div
style={{
position: 'fixed',
bottom: 10,
right: 10,
background: 'rgba(0,0,0,0.7)',
color: 'white',
padding: '5px 10px',
borderRadius: '5px',
fontSize: '12px',
zIndex: 9999
}}
>
{size.width} x {size.height}
</div>
);
};
return (
<RouteTitleContext.Provider
value={useMemo(
@@ -252,8 +379,7 @@ function AppInner() {
new Promise((res) => {
startTransition(() => {
setTabs((tabs) => {
const { pathname, search } =
selectedTab.router.state.location;
const { pathname, search } = selectedTab.router.state.location;
const newTab = createTab({ pathname, search });
const newTabs = [...tabs, newTab];
@@ -303,6 +429,7 @@ function AppInner() {
tab.element
)
)}
{/* <SizeDisplay /> */}
<div ref={ref} />
</SpacedriveInterfaceRoot>
</PlatformUpdaterProvider>

View File

@@ -49,6 +49,31 @@ export const commands = {
else return { status: 'error', error: e as any };
}
},
/**
* Initiates a drag and drop operation with cursor position tracking
*
* # Arguments
* * `window` - The Tauri window instance
* * `_state` - Current drag state (unused)
* * `files` - Vector of file paths to be dragged
* * `icon_path` - Path to the preview icon for the drag operation
* * `on_event` - Channel for communicating drag operation events back to the frontend
*/
async startDrag(
files: string[],
iconPath: string,
onEvent: TAURI_CHANNEL<CallbackResult>
): Promise<Result<null, string>> {
try {
return {
status: 'ok',
data: await TAURI_INVOKE('start_drag', { files, iconPath, onEvent })
};
} catch (e) {
if (e instanceof Error) throw e;
else return { status: 'error', error: e as any };
}
},
async openFilePaths(
library: string,
ids: number[]
@@ -162,6 +187,7 @@ export const events = __makeEvents__<{
/** user-defined types **/
export type AppThemeType = 'Auto' | 'Light' | 'Dark';
export type CallbackResult = { result: WrappedDragResult; cursorPos: WrappedCursorPosition };
export type DragAndDropEvent =
| { type: 'Hovered'; paths: string[]; x: number; y: number }
| { type: 'Dropped'; paths: string[]; x: number; y: number }
@@ -199,6 +225,8 @@ export type RevealItem =
| { FilePath: { id: number } }
| { Ephemeral: { path: string } };
export type Update = { version: string };
export type WrappedCursorPosition = { x: number; y: number };
export type WrappedDragResult = 'Dropped' | 'Cancel';
type __EventObj__<T> = {
listen: (cb: TAURI_API_EVENT.EventCallback<T>) => ReturnType<typeof TAURI_API_EVENT.listen<T>>;

View File

@@ -11,6 +11,9 @@
},
"dependencies": {
"@docsearch/react": "^3.5.2",
"@fortawesome/fontawesome-svg-core": "^6.6.0",
"@fortawesome/free-brands-svg-icons": "^6.6.0",
"@fortawesome/react-fontawesome": "^0.2.2",
"@octokit/webhooks": "^12.0.3",
"@phosphor-icons/react": "^2.0.14",
"@radix-ui/react-dialog": "^1.0.5",
@@ -36,6 +39,7 @@
"react-dom": "^18.2.0",
"react-error-boundary": "^4.0.11",
"react-github-btn": "^1.4.0",
"react-parallax-tilt": "^1.7.250",
"reading-time": "^1.5.0",
"rehype-autolink-headings": "^6.1.1",
"rehype-external-links": "^2.1.0",

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 484 KiB

View File

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 335 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 KiB

View File

@@ -0,0 +1,54 @@
<svg width="1796" height="888" viewBox="0 0 1796 888" fill="none" xmlns="http://www.w3.org/2000/svg">
<g style="mix-blend-mode:lighten" opacity="0.55">
<g filter="url(#filter0_f_2114_229)">
<rect width="90.9079" height="1295.83" rx="45.4539"
transform="matrix(0.151758 -0.988418 0.98809 0.153875 144.162 310.274)"
fill="url(#paint0_linear_2114_229)" />
</g>
<g filter="url(#filter1_f_2114_229)">
<rect width="90.9205" height="1523.41" rx="45.4603"
transform="matrix(0.160655 -0.987011 0.986504 0.16374 171.999 375.008)"
fill="url(#paint1_linear_2114_229)" />
</g>
<g style="mix-blend-mode:plus-lighter" filter="url(#filter2_f_2114_229)">
<rect width="52.667" height="1644.8" rx="26.3335"
transform="matrix(0.159078 -0.987266 0.98679 0.162007 221.997 315.263)"
fill="url(#paint2_linear_2114_229)" />
</g>
</g>
<defs>
<filter id="filter0_f_2114_229" x="0.527344" y="76.8657" width="1581.46" height="576.358"
filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="75" result="effect1_foregroundBlur_2114_229" />
</filter>
<filter id="filter1_f_2114_229" x="28.7051" y="142.092" width="1804.04" height="625.536"
filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="75" result="effect1_foregroundBlur_2114_229" />
</filter>
<filter id="filter2_f_2114_229" x="121.247" y="162.581" width="1832.95" height="519.836"
filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="52.3" result="effect1_foregroundBlur_2114_229" />
</filter>
<linearGradient id="paint0_linear_2114_229" x1="24.8607" y1="313.67" x2="-3.96949" y2="1257.92"
gradientUnits="userSpaceOnUse">
<stop stop-color="#D104FF" />
<stop offset="1" stop-color="#0E0E12" />
</linearGradient>
<linearGradient id="paint1_linear_2114_229" x1="18.3881" y1="155.721" x2="18.4058" y2="1439.38"
gradientUnits="userSpaceOnUse">
<stop stop-color="#027DFF" />
<stop offset="1" stop-color="#0E0E12" />
</linearGradient>
<linearGradient id="paint2_linear_2114_229" x1="17.3168" y1="77.7727" x2="-34.3742" y2="1267.89"
gradientUnits="userSpaceOnUse">
<stop stop-color="white" />
<stop offset="1" stop-color="#0E0E12" />
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -0,0 +1,18 @@
<svg width="991" height="294" viewBox="0 0 991 294" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle opacity="0.76" cx="791.999" cy="240.271" r="2" fill="white" />
<circle opacity="0.51" cx="653.999" cy="166.271" r="0.5" fill="white" />
<circle opacity="0.49" cx="639.999" cy="240.271" r="1" fill="white" />
<circle opacity="0.33" cx="738.999" cy="179.271" r="1" fill="white" />
<circle opacity="0.76" cx="876.999" cy="222.271" r="1.5" fill="white" />
<circle opacity="0.64" cx="551.999" cy="195.271" r="1" fill="white" />
<circle opacity="0.29" cx="306.999" cy="47.2715" r="2" fill="white" />
<circle opacity="0.28" cx="195.999" cy="148.271" r="2" fill="white" />
<circle opacity="0.21" cx="298.499" cy="151.771" r="1" fill="white" />
<circle opacity="0.16" cx="168.499" cy="161.771" r="2" fill="white" />
<circle opacity="0.1" cx="1.49902" cy="69.7715" r="1" fill="white" />
<circle opacity="0.25" cx="656.999" cy="237.271" r="2" fill="white" />
<circle opacity="0.44" cx="398.999" cy="184.271" r="1.5" fill="white" />
<circle opacity="0.23" cx="88.999" cy="1.27148" r="1" fill="white" />
<circle opacity="0.22" cx="973.999" cy="293.271" r="0.5" fill="white" />
<circle opacity="0.4" cx="989.999" cy="234.271" r="0.5" fill="white" />
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 MiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 MiB

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

@@ -1,70 +0,0 @@
'use client';
import dynamic from 'next/dynamic';
import { ReactNode, Suspense, useEffect, useState } from 'react';
import { hasWebGLContext } from '~/utils/util';
const FADE = {
start: 300, // start fading out at 100px
end: 1300 // end fading out at 300px
};
const Space = dynamic(() => import('~/components/Space'), { ssr: false });
const Bubbles = dynamic(() => import('~/components/Bubbles').then((m) => m.Bubbles), {
ssr: false
});
export function Background() {
const [opacity, setOpacity] = useState(0.6);
const [isWindowResizing, setIsWindowResizing] = useState(false);
const [canUseWebGL, setCanUseWebGL] = useState(hasWebGLContext());
const [inner, setInner] = useState<ReactNode>(null);
useEffect(() => {
const handleScroll = () => {
const currentScrollY = window.scrollY;
if (currentScrollY <= FADE.start) {
setOpacity(0.6);
} else if (currentScrollY <= FADE.end) {
const range = FADE.end - FADE.start;
const diff = currentScrollY - FADE.start;
const ratio = diff / range;
setOpacity(0.6 - ratio);
} else {
setOpacity(0);
}
};
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []);
useEffect(() => {
let resizeTimer: ReturnType<typeof setTimeout>;
const handleResize = () => {
setIsWindowResizing(true);
clearTimeout(resizeTimer);
resizeTimer = setTimeout(() => {
setIsWindowResizing(false);
}, 100);
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
clearTimeout(resizeTimer);
};
}, []);
useEffect(() => {
setInner(canUseWebGL ? <Space onRenderFail={() => setCanUseWebGL(false)} /> : <Bubbles />);
}, [canUseWebGL]);
return (
<div style={{ opacity }}>
<Suspense>{!isWindowResizing && inner}</Suspense>
</div>
);
}

View File

@@ -1,167 +0,0 @@
import {
Discord,
Github,
Instagram,
Opencollective,
Twitch,
Twitter
} from '@sd/assets/svgs/brands';
import Image from 'next/image';
import Link from 'next/link';
import { PropsWithChildren } from 'react';
import { getLatestRelease } from './docs/changelog/data';
import Logo from './logo.png';
export async function Footer() {
const latestRelease = await getLatestRelease();
return (
<footer id="footer" className="relative z-50 w-screen overflow-hidden pt-3 backdrop-blur">
<Image
alt="footer gradient"
className="absolute bottom-0 left-0 z-[-1]"
quality={100}
width={0}
height={0}
src="/images/misc/footer-gradient.webp"
style={{ width: '100%', height: '400px' }}
sizes="100vw"
/>
<div className="m-auto grid min-h-64 max-w-[100rem] grid-cols-2 gap-6 p-8 pb-20 pt-10 text-white sm:grid-cols-2 lg:grid-cols-6">
<div className="col-span-2">
<Image alt="Spacedrive logo" src={Logo} className="mb-5 size-10" />
<h1 className="mb-1 text-xl font-bold">Spacedrive</h1>
<p className="text-sm text-gray-350 opacity-50">
&copy; Copyright {new Date().getFullYear()} Spacedrive Technology Inc.
</p>
<div className="mb-10 mt-12 flex flex-row space-x-3">
<FooterLink link="https://x.com/spacedriveapp">
<Twitter className="size-6" />
</FooterLink>
<FooterLink aria-label="discord" link="https://discord.gg/gTaF2Z44f5">
<Discord className="size-6" />
</FooterLink>
<FooterLink
aria-label="instagram"
link="https://instagram.com/spacedriveapp"
>
<Instagram className="size-6" />
</FooterLink>
<FooterLink aria-label="github" link="https://github.com/spacedriveapp">
<Github className="size-6" />
</FooterLink>
<FooterLink
aria-label="open collective"
link="https://opencollective.com/spacedrive"
>
<Opencollective className="size-6" />
</FooterLink>
<FooterLink
aria-label="twitch stream"
link="https://twitch.tv/jamiepinelive"
>
<Twitch className="size-6" />
</FooterLink>
</div>
</div>
<div className="col-span-1 flex flex-col space-y-2">
<h1 className="mb-1 text-xs font-bold uppercase">About</h1>
<FooterLink link="/team">Team</FooterLink>
<FooterLink link="/docs/product/resources/faq">FAQ</FooterLink>
<FooterLink link="/careers">Careers</FooterLink>
{latestRelease && (
<FooterLink
link={`/docs/changelog/${latestRelease.category}/${latestRelease.tag}`}
>
Changelog
</FooterLink>
)}
<FooterLink link="/blog">Blog</FooterLink>
</div>
<div className="col-span-1 flex flex-col space-y-2">
<h1 className="mb-1 text-xs font-bold uppercase">Downloads</h1>
<div className="col-span-1 flex flex-col space-y-2">
<FooterLink link="https://spacedrive.com/api/releases/desktop/stable/darwin/aarch64">
macOS
</FooterLink>
<FooterLink link="https://spacedrive.com/api/releases/desktop/stable/darwin/x86_64">
macOS Intel
</FooterLink>
<FooterLink link="https://spacedrive.com/api/releases/desktop/stable/windows/x86_64">
Windows
</FooterLink>
<FooterLink link="https://spacedrive.com/api/releases/desktop/stable/linux/x86_64">
Linux
</FooterLink>
</div>
<div className="pointer-events-none col-span-1 flex flex-col space-y-2 opacity-50">
<FooterLink link="#">Android</FooterLink>
<FooterLink link="#">iOS</FooterLink>
</div>
</div>
<div className="col-span-1 flex flex-col space-y-2">
<h1 className="mb-1 text-xs font-bold uppercase">Developers</h1>
<FooterLink link="/docs/product/getting-started/introduction">
Documentation
</FooterLink>
<FooterLink
blank
link="https://github.com/spacedriveapp/spacedrive/blob/main/CONTRIBUTING.md"
>
Contribute
</FooterLink>
<div className="pointer-events-none opacity-50">
<FooterLink link="#">Extensions</FooterLink>
</div>
<div className="pointer-events-none opacity-50">
<FooterLink link="#">Self Host</FooterLink>
</div>
</div>
<div className="col-span-1 flex flex-col space-y-2">
<h1 className="mb-1 text-xs font-bold uppercase">Org</h1>
<FooterLink blank link="https://opencollective.com/spacedrive">
Open Collective
</FooterLink>
<FooterLink
blank
link="https://github.com/spacedriveapp/spacedrive/blob/main/LICENSE"
>
License
</FooterLink>
<div>
<FooterLink link="/docs/company/legal/privacy">Privacy</FooterLink>
</div>
<div>
<FooterLink link="/docs/company/legal/terms">Terms</FooterLink>
</div>
</div>
</div>
<div className="absolute top-0 flex h-1 w-full flex-row items-center justify-center opacity-100">
<div className="h-px w-1/2 bg-gradient-to-r from-transparent to-white/10"></div>
<div className="h-px w-1/2 bg-gradient-to-l from-transparent to-white/10"></div>
</div>
</footer>
);
}
function FooterLink({
blank,
link,
...props
}: PropsWithChildren<{ link: string; blank?: boolean }>) {
return (
<Link
href={link}
target={blank ? '_blank' : ''}
className="text-gray-300 duration-300 hover:text-white hover:opacity-50"
rel="noreferrer"
{...props}
>
{props.children}
</Link>
);
}

View File

@@ -1,32 +0,0 @@
'use client';
import { type IconProps } from '@phosphor-icons/react';
import clsx from 'clsx';
import { Button, LinkButtonProps } from '@sd/ui';
interface Props extends LinkButtonProps {
className?: string;
text: string;
icon?: IconProps;
onClick?: () => void;
}
export function HomeCTA({ className, text, icon, ...props }: Props) {
return (
<Button
size="lg"
className={clsx(
'home-button-border-gradient relative z-30 flex cursor-pointer items-center gap-2 !rounded-[7px] border-0 !bg-[#2F3152]/30 py-2 text-sm text-white !backdrop-blur-lg hover:brightness-110 md:text-[16px]',
className
)}
{...props}
>
<>
{icon && icon}
{text}
</>
</Button>
);
}
export default HomeCTA;

View File

@@ -1,74 +0,0 @@
import { Discord, Github } from '@sd/assets/svgs/brands';
import Image from 'next/image';
import Link from 'next/link';
import { PropsWithChildren } from 'react';
import { positions } from '../careers/data';
import Logo from '../logo.png';
import { MobileDropdown } from './MobileDropdown';
export function NavBar() {
return (
<div className="navbar-blur fixed z-[55] h-16 w-full !bg-black/10 px-2 transition">
<div className="relative m-auto flex h-full max-w-[100rem] items-center p-5">
<Link href="/" className="absolute flex flex-row items-center">
<Image alt="Spacedrive logo" src={Logo} className="z-30 mr-3 size-8" />
<h3 className="text-xl font-bold text-white">Spacedrive</h3>
</Link>
<div className="m-auto hidden space-x-4 text-white lg:block">
<NavLink link="/roadmap">Roadmap</NavLink>
<NavLink link="/team">Team</NavLink>
{/* <NavLink link="/pricing">Pricing</NavLink> */}
<NavLink link="/blog">Blog</NavLink>
<NavLink link="/docs/product/getting-started/introduction">Docs</NavLink>
<div className="relative inline">
<NavLink link="/careers">Careers</NavLink>
{positions.length > 0 ? (
<span className="absolute -right-2 -top-1 rounded-md bg-primary/80 px-[5px] text-xs">
{` ${positions.length} `}
</span>
) : null}
</div>
</div>
<div className="flex-1 lg:hidden" />
<MobileDropdown />
<div className="absolute right-3 hidden flex-row space-x-5 lg:flex">
<Link
aria-label="discord"
href="https://discord.gg/gTaF2Z44f5"
target="_blank"
rel="noreferrer"
>
<Discord className="size-6 text-white opacity-100 duration-300 hover:opacity-50" />
</Link>
<Link
aria-label="github"
href="https://github.com/spacedriveapp/spacedrive"
target="_blank"
rel="noreferrer"
>
<Github className="size-6 text-white opacity-100 duration-300 hover:opacity-50" />
</Link>
</div>
</div>
<div className="absolute bottom-0 flex h-1 w-full flex-row items-center justify-center pt-4 opacity-100">
<div className="h-px w-1/2 bg-gradient-to-r from-transparent to-white/10"></div>
<div className="h-px w-1/2 bg-gradient-to-l from-transparent to-white/10"></div>
</div>
</div>
);
}
function NavLink(props: PropsWithChildren<{ link?: string }>) {
return (
<Link
href={props.link ?? '#'}
target={props.link?.startsWith('http') ? '_blank' : undefined}
className="cursor-pointer p-4 text-[11pt] text-gray-300 no-underline transition hover:text-gray-50"
rel="noreferrer"
>
{props.children}
</Link>
);
}

View File

@@ -1,37 +0,0 @@
import { Newspaper } from '@phosphor-icons/react/dist/ssr';
import clsx from 'clsx';
import Link from 'next/link';
export interface NewBannerProps {
headline: string;
href?: string;
link?: string;
className?: string;
}
export function NewBanner(props: NewBannerProps) {
const { headline, href, link } = props;
return (
<Link
href={href ?? '/'}
className={clsx(
props.className,
'news-banner-border-gradient news-banner-glow animation-delay-1 fade-in-whats-new z-10 mb-5 flex w-fit flex-row rounded-full bg-black/10 px-5 py-2.5 text-xs backdrop-blur-md transition hover:bg-purple-900/20 sm:w-auto sm:text-base'
)}
>
<div className="flex items-center gap-2">
<Newspaper weight="fill" className="text-white" size={20} />
<p className="font-regular truncate text-white">{headline}</p>
</div>
{link && (
<>
<div role="separator" className="h-22 mx-4 w-px bg-zinc-700/70" />
<span className="font-regular shrink-0 bg-gradient-to-r from-violet-400 to-fuchsia-400 bg-clip-text text-transparent decoration-primary-600">
{link} <span aria-hidden="true">&rarr;</span>
</span>
</>
)}
</Link>
);
}

View File

@@ -1,7 +1,7 @@
import { type components } from '@octokit/openapi-types';
import { env } from '~/env';
type Release = components['schemas']['release'];
export type Release = components['schemas']['release'];
const FETCH_META = {
headers: new Headers({
@@ -32,11 +32,16 @@ export const getRelease = (tag: string) =>
path: `${RELEASES_PATH}/tags/${tag}`
}) as FetchConfig<Release>;
export const getRepoStats = {
path: `/repos/${env.GITHUB_ORG}/${env.GITHUB_REPO}`
} as FetchConfig<components['schemas']['repository']>;
export async function githubFetch<T>({ path }: FetchConfig<T>): Promise<T> {
return fetch(`https://api.github.com${path}`, {
...FETCH_META,
next: {
tags: [path]
tags: [path],
revalidate: 43200 // 12 hours in seconds
}
}).then((r) => r.json());
}

View File

@@ -4,7 +4,7 @@ import { Metadata } from 'next';
import { useMDXComponent } from 'next-contentlayer/hooks';
import Image from 'next/image';
import { notFound } from 'next/navigation';
import { BlogTag } from '~/components/BlogTag';
import { BlogTag } from '~/components/blog-tag';
import { BlogMDXComponents } from '~/components/mdx';
export function generateStaticParams(): Array<Props['params']> {
@@ -43,7 +43,7 @@ export default function Page({ params }: Props) {
const MDXContent = useMDXComponent(post.body.code);
return (
<div className="lg:prose-xs container prose m-auto mb-20 max-w-4xl p-5 pt-14 dark:prose-invert">
<div className="lg:prose-xs container prose prose-invert m-auto mb-20 max-w-4xl p-5 pt-14">
<>
<figure>
<Image

View File

@@ -2,7 +2,7 @@ import { allPosts } from '@contentlayer/generated';
import dayjs from 'dayjs';
import Image from 'next/image';
import Link from 'next/link';
import { BlogTag } from '~/components/BlogTag';
import { BlogTag } from '~/components/blog-tag';
export const metadata = {
title: 'Spacedrive Blog',
@@ -11,7 +11,7 @@ export const metadata = {
export default function Page() {
return (
<div className="lg:prose-xs container prose m-auto mb-20 flex max-w-4xl flex-col p-4 pt-32 dark:prose-invert prose-a:no-underline">
<div className="lg:prose-xs container prose prose-invert m-auto mb-20 flex max-w-4xl flex-col p-4 pt-32 prose-a:no-underline">
<section>
<h1 className="fade-in-heading m-0">Blog</h1>
<p className="fade-in-heading animation-delay-1">Get the latest from Spacedrive.</p>

View File

@@ -17,7 +17,36 @@ export interface PositionPosting {
description: string;
}
export const positions: PositionPosting[] = [];
export const positions: PositionPosting[] = [
{
name: 'Core Engineer',
type: 'Full-time',
salary: '$140,000 - $180,000',
description:
"Lead development of Spacedrive's core storage engine and distributed filesystem in Rust. You'll architect and implement critical systems including storage classification, encrypted data sync, and filesystem operations. Deep knowledge of systems programming, distributed systems, and Rust required. Experience with FUSE filesystems and cloud storage systems highly valued."
},
{
name: 'Platform Engineer',
type: 'Full-time',
salary: '$130,000 - $170,000',
description:
"Own Spacedrive's cross-platform client applications built with Tauri and React. Focus on performance optimization, native platform integration, and smooth user experience across desktop and mobile. Strong TypeScript/React experience required, with knowledge of Rust and native platform APIs (Windows, macOS, Linux, iOS, Android) highly valued."
},
{
name: 'Cloud Infrastructure Engineer',
type: 'Full-time',
salary: '$150,000 - $190,000',
description:
"Design and implement Spacedrive's cloud storage infrastructure, focusing on our innovative storage architecture. Build highly scalable systems for data ingestion, storage optimization, and content delivery. Deep experience with cloud infrastructure (AWS/GCP), object storage, and distributed systems required. Knowledge of Rust and TypeScript preferred."
},
{
name: 'Security Engineer',
type: 'Full-time',
salary: '$160,000 - $200,000',
description:
"Lead security architecture for Spacedrive's zero-knowledge encrypted storage platform. Design and implement end-to-end encryption, secure key management, and audit systems. Expertise in cryptography, secure systems design, and penetration testing required. Experience with Rust and secure storage systems highly valued."
}
];
export const values = [
{

View File

@@ -1,5 +1,5 @@
import { Clock, CurrencyDollar } from '@phosphor-icons/react/dist/ssr';
import { Button } from '@sd/ui';
import { ArrowDown, Clock, CurrencyDollar } from '@phosphor-icons/react/dist/ssr';
import { CtaSecondaryButton } from '~/components/cta-secondary-button';
import { perks, positions, values } from './data';
@@ -10,28 +10,34 @@ export const metadata = {
export default function CareersPage() {
return (
<div className="container prose prose-invert relative m-auto mb-20 min-h-screen max-w-4xl p-4 pt-32 text-white">
<div
className="bloom subtle egg-bloom-two -top-60 right-[-400px]"
style={{ transform: 'scale(2)' }}
/>
<h1 className="fade-in-heading mb-3 px-2 text-center text-4xl font-black leading-tight text-white md:text-5xl">
Build the future of files.
</h1>
<div className="animation-delay-1 z-30 flex flex-col items-center fade-in">
<p className="z-40 text-center text-lg text-gray-350">
<div className="container prose prose-invert relative m-auto mb-20 mt-40 min-h-screen max-w-4xl overflow-x-hidden p-4 text-white md:overflow-visible">
<div className="relative mb-[200px] flex flex-col items-center justify-center overflow-visible xs:mb-32 sm:mb-40 lg:mb-40">
<div
className="animation-delay-1 absolute right-[-750px] top-[-1000px] mx-auto size-[1700px] blur-sm duration-150 fade-in xs:top-[-1150px] sm:top-[-1200px] md:top-[-1150px]"
style={{
backgroundImage: 'url(/images/careersbg.webp',
backgroundRepeat: 'no-repeat',
backgroundSize: 'cover',
backgroundPosition: '-300px 40px'
}}
/>
<h1 className="fade-in-heading mb-3 px-2 text-center text-4xl font-black leading-tight text-white md:text-5xl">
Build the future of files.
</h1>
<p className="animation-delay-1 fade-in-heading z-40 text-center text-lg text-gray-350">
Spacedrive is redefining the way we think about our personal data, building a
open ecosystem to help preserve your digital legacy and make cross-platform file
management a breeze.
</p>
<Button
<CtaSecondaryButton
icon={<ArrowDown weight="bold" />}
href="#open-positions"
className="z-30 cursor-pointer border-0"
variant="accent"
className="fade-in-heading animation-delay-2 z-30 mt-8 min-w-fit cursor-pointer !gap-x-1.5 border-0"
>
See Open Positions
</Button>
<hr className="border-1 my-24 w-full border-gray-200 opacity-10" />
Open Positions
</CtaSecondaryButton>
</div>
<div className="animation-delay-1 z-30 flex flex-col items-center fade-in">
<h2 className="mb-0 px-2 text-center text-4xl font-black leading-tight">
Our Values
</h2>
@@ -40,7 +46,7 @@ export default function CareersPage() {
{values.map((value, index) => (
<div
key={value.title + index}
className="flex flex-col rounded-md border border-gray-500 bg-gray-550/50 p-10"
className="bento-border-left relative flex flex-col rounded-[10px] bg-[radial-gradient(66.79%_83.82%_at_0%_3.69%,#1B1D25_0%,#15161C_100%)] p-10"
>
<value.icon
width="1em"

View File

@@ -3,6 +3,6 @@
import { PropsWithChildren } from 'react';
import { TooltipProvider } from '@sd/ui';
export function Providers({ children }: PropsWithChildren) {
export function ClientProviders({ children }: PropsWithChildren) {
return <TooltipProvider>{children}</TooltipProvider>;
}

View File

@@ -0,0 +1,7 @@
import React from 'react';
const Page: React.FC = () => {
return <h1>Cloud</h1>;
};
export default Page;

View File

@@ -3,7 +3,7 @@
import { CaretRight } from '@phosphor-icons/react';
import { useParams } from 'next/navigation';
import { Fragment } from 'react';
import { toTitleCase } from '~/utils/util';
import { toTitleCase } from '~/utils/misc';
export function Breadcrumbs() {
const { slug } = useParams<{ slug?: string[] }>();

View File

@@ -12,7 +12,7 @@ export function Markdown(props: PropsWithChildren<MarkdownPageProps>) {
<article
id="content"
className={clsx(
'lg:prose-xs prose text-[15px] dark:prose-invert prose-h1:text-[3.25em] prose-a:text-primary prose-a:no-underline prose-blockquote:rounded prose-blockquote:bg-gray-600 prose-code:rounded-md prose-code:bg-gray-650 prose-code:p-1 prose-code:font-normal prose-code:text-gray-400 prose-code:before:hidden prose-code:after:hidden prose-table:border-b prose-table:border-gray-500 prose-tr:even:bg-gray-700 prose-th:p-2 prose-td:border-l prose-td:border-gray-500 prose-td:p-2 prose-td:last:border-r prose-img:rounded sm:text-[16px]',
'lg:prose-xs prose prose-invert text-[15px] prose-h1:text-[3.25em] prose-a:text-primary prose-a:no-underline prose-blockquote:rounded prose-blockquote:bg-gray-600 prose-code:rounded-md prose-code:bg-gray-650 prose-code:p-1 prose-code:font-normal prose-code:text-gray-400 prose-code:before:hidden prose-code:after:hidden prose-table:border-b prose-table:border-gray-500 prose-tr:even:bg-gray-700 prose-th:p-2 prose-td:border-l prose-td:border-gray-500 prose-td:p-2 prose-td:last:border-r prose-img:rounded sm:text-[16px]',
props.articleClassNames
)}
>

View File

@@ -1,5 +1,5 @@
import { iconConfig } from '~/utils/contentlayer';
import { toTitleCase } from '~/utils/util';
import { toTitleCase } from '~/utils/misc';
import { getReleasesCategories } from '../changelog/data';
import { navigationMeta } from '../data';

View File

@@ -6,7 +6,7 @@ import { getMDXComponent } from 'next-contentlayer/hooks';
import Link from 'next/link';
import { notFound } from 'next/navigation';
import { DocMDXComponents } from '~/components/mdx';
import { toTitleCase } from '~/utils/util';
import { toTitleCase } from '~/utils/misc';
import { getDoc } from '../data';
import { Markdown } from '../Markdown';

View File

@@ -3,7 +3,7 @@ import { getMDXComponent } from 'next-contentlayer/hooks';
import { notFound } from 'next/navigation';
import { getRelease, githubFetch } from '~/app/api/github';
import { DocMDXComponents } from '~/components/mdx';
import { toTitleCase } from '~/utils/util';
import { toTitleCase } from '~/utils/misc';
import { Markdown } from '../../../Markdown';
import { getReleasesCategories } from '../../data';

View File

@@ -1,5 +1,5 @@
import { getRecentReleases, getReleaseFrontmatter, githubFetch } from '~/app/api/github';
import { toTitleCase } from '~/utils/util';
import { toTitleCase } from '~/utils/misc';
import { SectionMeta } from '../data';

View File

@@ -20,7 +20,9 @@ export default function Layout({ children }: PropsWithChildren) {
<MobileSidebarProvider>
<div className="flex w-full flex-col items-start sm:flex-row">
<MobileSidebarWrapper>{sidebar}</MobileSidebarWrapper>
<aside className="sticky top-32 mb-20 ml-2 mr-0 mt-32 hidden px-5 sm:inline lg:mr-4">
<aside className="sticky top-32 mb-20 ml-2 mr-0 mt-32 hidden rounded-xl p-5 backdrop-saturate-[1.6] sm:inline lg:mr-4">
{/* Gradient Borders */}
<div className="absolute right-0 top-0 h-full w-px bg-gradient-to-b from-transparent via-[#2D2D37]/60 to-transparent" />
{sidebar}
</aside>
<div className="flex w-full flex-col sm:flex-row" id="page-container">

View File

@@ -0,0 +1,7 @@
import React from 'react';
const Page: React.FC = () => {
return <h1>Enterprise Licensing & Cloud</h1>;
};
export default Page;

View File

@@ -0,0 +1,9 @@
import React from 'react';
const Page: React.FC = () => {
return (
<h1>Spacedrive Explorer Open source, cross-platform file manager powered by a VDFS</h1>
);
};
export default Page;

View File

@@ -0,0 +1,14 @@
import { IBM_Plex_Sans, Inter } from 'next/font/google';
export const plexSansFont = IBM_Plex_Sans({
weight: ['300', '400', '600', '700'],
subsets: ['latin'],
display: 'swap',
variable: '--font-plex-sans'
});
export const interFont = Inter({
subsets: ['latin'],
display: 'swap',
variable: '--font-inter'
});

View File

@@ -1,16 +1,19 @@
import type { Metadata, Viewport } from 'next';
import { PropsWithChildren } from 'react';
import { Footer } from './Footer';
import { NavBar } from './NavBar';
import { GlobalFooter } from '~/components/global-footer';
import { NavBar } from '~/components/global-nav-bar';
import '@sd/ui/style/style.scss';
import '~/styles/prism.css';
import '~/styles/style.scss';
import clsx from 'clsx';
import PlausibleProvider from 'next-plausible';
import { Providers } from './Providers';
// import { DisclaimerBanner } from '~/components/disclaimer-banner';
import { ClientProviders } from './client-providers';
import { interFont, plexSansFont } from './fonts';
export const metadata: Metadata = {
metadataBase: new URL('https://spacedrive.com'),
@@ -26,12 +29,21 @@ export const metadata: Metadata = {
};
export const viewport: Viewport = {
themeColor: { color: '#E751ED', media: 'not screen' }
themeColor: [
// background color in Safari
{ color: '#0E0E12', media: 'screen' },
// MUST BE LAST to actually work on embeds
// embed color on discord, for instance
{ color: '#E751ED', media: 'not screen' }
]
};
export default function Layout({ children }: PropsWithChildren) {
return (
<html lang="en" className="dark scroll-smooth">
<html
lang="en"
className={clsx('scroll-smooth text-white', plexSansFont.variable, interFont.variable)}
>
<head>
<PlausibleProvider
domain="spacedrive.com"
@@ -40,16 +52,15 @@ export default function Layout({ children }: PropsWithChildren) {
taggedEvents
/>
</head>
<body>
<Providers>
<div className="overflow-hidden dark:bg-[#030014]/60">
<body className="noise noise-strongest min-h-screen bg-[#090909] font-plex">
<div className="flex min-h-screen flex-col">
{/* <DisclaimerBanner /> */}
<ClientProviders>
<NavBar />
<main className="dark z-10 m-auto max-w-[100rem] dark:text-white">
{children}
</main>
<Footer />
</div>
</Providers>
<main className="z-10 m-auto w-full max-w-[100rem] flex-1">{children}</main>
<GlobalFooter />
</ClientProviders>
</div>
</body>
</html>
);

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 319 KiB

View File

@@ -3,7 +3,7 @@
import { SmileyXEyes } from '@phosphor-icons/react';
import { useRouter } from 'next/navigation';
import { Button } from '@sd/ui';
import Markdown from '~/components/Markdown';
import { MarkdownPage } from '~/components/markdown-page';
export const metadata = {
title: 'Not Found - Spacedrive'
@@ -13,7 +13,7 @@ export default function NotFound() {
const router = useRouter();
return (
<Markdown classNames="flex w-full justify-center">
<MarkdownPage classNames="flex w-full justify-center">
<div className="m-auto flex flex-col items-center">
<div className="h-32" />
<SmileyXEyes className="mb-3 size-44" />
@@ -35,6 +35,6 @@ export default function NotFound() {
</div>
</div>
<div className="h-80" />
</Markdown>
</MarkdownPage>
);
}

View File

@@ -0,0 +1,60 @@
import Image from 'next/image';
import React from 'react';
import assistantIcon from '~/assets/assistant_icon.webp';
import Particles from '~/components/particles';
export const Assistant = () => {
return (
<section className="relative mb-32 w-full md:mb-0">
{/* Background Image positioned absolutely */}
<Image
src="/images/new/comet_bg.svg"
alt=""
width={2000}
height={1200}
className="absolute left-0 top-0 -z-10 overflow-visible md:top-[-200px]"
/>
<div className="container relative z-10 mx-auto flex w-full flex-col flex-wrap items-start p-4">
<h2 className="flex flex-wrap items-center gap-2 text-2xl font-bold leading-8 md:flex-1 md:text-3xl md:leading-10">
<Image
className="flex size-12 shrink-0 justify-center"
quality={100}
src={assistantIcon}
width={251}
height={251}
alt=""
/>
Assistant.{' '}
<span className="bg-gradient-to-r from-[#D1DCFF] via-[#A8BEFF] via-30% to-[#E771FF] bg-clip-text font-semibold text-transparent">
Mighty-powerful AI. No cloud needed.
</span>
</h2>
<div className="absolute inset-x-0 -z-10 mix-blend-overlay">
<Particles
quantity={50}
ease={100}
staticity={200}
color={'#ADAFCD'}
className="opacity-80"
refresh
vy={-0.005}
vx={-0.0005}
/>
</div>
<p className="mt-[12px] text-lg tracking-[0.01em] text-ink drop-shadow-md">
Details to be revealed soon...
</p>
<div className="mt-[32.73px]">
<div className="inline-flex items-center justify-center gap-[10px] rounded-full border-2 border-[#FC79E7] bg-[rgba(43,43,59,0.50)] px-[11px] py-[10px]">
<p className="text-center text-[14px] font-[500] leading-[125%] text-[#FFF]">
COMING NEXT YEAR
</p>
</div>
</div>
</div>
</section>
);
};

View File

@@ -0,0 +1,74 @@
'use client';
import { AnimatePresence } from 'framer-motion';
import { memo, useCallback, useState } from 'react';
import { SelectedVideo, Video } from '~/components/video';
const videos: {
title: string;
src: string;
description: string;
}[] = [
{
title: 'Drag and Drop',
src: '/videos/Spacedrive_DragAndDrop.webm',
description: 'Easily drag and drop files or folders.'
},
{
title: 'Tabs',
src: '/videos/Spacedrive_Tabs.webm',
description: 'Browse seamlessly with multiple tabs.'
},
{
title: 'Quick Preview',
src: '/videos/Spacedrive_QuickPreview.webm',
description: 'Instantly preview files and object data.'
}
];
const MemoizedVideo = memo(Video);
const MemoizedExplorer = memo(function Explorer() {
const [selectedVideo, setSelectedVideo] = useState<null | string>(null);
const handleVideoSelect = useCallback((src: string | null) => {
setSelectedVideo(src);
}, []);
return (
<>
{selectedVideo ? (
<AnimatePresence>
<SelectedVideo src={selectedVideo} setSelectedVideo={setSelectedVideo} />
</AnimatePresence>
) : null}
<div className="container mx-auto flex flex-col flex-wrap items-center gap-10 px-4">
<h1 className="flex-1 self-start text-2xl font-semibold leading-8 md:text-3xl md:leading-10 lg:self-start">
Explorer.{' '}
<span className="bg-gradient-to-r from-zinc-400 to-zinc-600 bg-clip-text text-transparent">
{/* Some controlled line breaks here based on breakpoint to make sure the breaks looks nice always :) */}
<br className="lg:hidden" />
Browse and manage your data
<br className="sm:hidden" /> like never before.
</span>
</h1>
<div className="grid w-full grid-cols-1 gap-16 md:grid-cols-3 md:gap-4">
{videos.map((video) => (
<div key={video.src}>
<MemoizedVideo
setSelectedVideo={handleVideoSelect}
layoutId={`video-${video.src}`}
onClick={() => handleVideoSelect(video.src)}
{...video}
/>
<h2 className="mt-5 text-lg font-bold text-white">{video.title}</h2>
<p className="text-md text-ink-dull">{video.description}</p>
</div>
))}
</div>
</div>
</>
);
});
export { MemoizedExplorer as Explorer };

View File

@@ -0,0 +1,126 @@
import clsx from 'clsx';
import Image, { StaticImageData } from 'next/image';
import extensionsIllustration from '~/assets/illustration/extensions.webp';
import spacedropIllustration from '~/assets/illustration/spacedrop.webp';
import tagsIllustration from '~/assets/illustration/tags.webp';
import vaultIllustration from '~/assets/illustration/vault.webp';
export const Features = () => {
return (
<section className="container relative mx-auto flex items-center justify-center p-4">
<h2 className="sr-only">Features</h2>
{/** Lines & middle circle */}
<div className="absolute inset-x-0 mx-auto hidden h-[90%] w-px bg-gradient-to-b from-transparent via-[#6C708F]/30 to-transparent lg:flex" />
<div className="absolute hidden h-px w-full self-center bg-gradient-to-r from-transparent via-[#6C708F]/30 to-transparent lg:flex" />
<div className="absolute left-1/2 top-1/2 z-10 mx-auto hidden size-3 -translate-x-1/2 -translate-y-1/2 rounded-full bg-[#636783] lg:flex" />
{/** Features */}
<div className="grid grid-cols-1 max-lg:gap-14 lg:grid-cols-2">
{info.map((item, index) => (
<Feature
{...item}
key={index}
titleClassName={clsx((index === 1 || index === 3) && 'self-start')}
title={item.title}
image={item.image}
description={item.description}
/>
))}
</div>
</section>
);
};
interface FeatureImage {
src: StaticImageData;
alt: string;
maxWidth?: number;
maxHeight?: number;
}
interface Props {
title: string;
description: string;
image: FeatureImage;
className?: string;
titleClassName?: string;
scale?: number;
}
const Feature = ({
title,
description,
className,
titleClassName,
image: { src: image, alt = '', maxWidth, maxHeight }
}: Props) => {
return (
<div className={clsx('flex w-full flex-col gap-3 px-8 pb-8 pt-6', className)}>
<div className="flex flex-col gap-1">
<h1 className={clsx('text-2xl font-semibold', titleClassName)}>{title}</h1>
<p className="w-full max-w-96 text-ink-faint">{description}</p>
</div>
<Image
className="mx-auto my-6 size-full overflow-hidden object-contain"
loading="eager"
quality={100}
style={{
maxWidth,
maxHeight
}}
src={image}
alt={alt}
/>
</div>
);
};
const info = [
{
title: 'Spacedrop',
description:
'Quickly send files between devices. Just select what you want to share and instantly transfer it to nearby devices on the same network.',
image: {
src: spacedropIllustration,
// TODO: write alt text
alt: '',
maxWidth: 450
}
},
{
title: 'Tags',
description:
'Organize and find your files faster by assigning custom tags to your folders and documents. Simplify your data management with easy categorization.',
image: {
src: tagsIllustration,
// TODO: write alt text
alt: '',
maxWidth: 260
}
},
{
title: 'End-To-End Encryption',
description:
'Any time you send files across a network with Spacedrive, its end-to-end encrypted — ensuring that only you can access your files. Ever.',
image: {
src: vaultIllustration,
// TODO: write alt text
alt: '',
maxWidth: 450
}
},
{
title: 'Extensions',
description:
'Install add-ons to customize Spacedrive with extra features and integrations, tailoring it to your unique workflow.',
image: {
src: extensionsIllustration,
// TODO: write alt text
alt: '',
maxWidth: 280
}
}
] satisfies {
title: string;
description: string;
image: FeatureImage;
}[];

View File

@@ -0,0 +1,33 @@
import { Github as GithubLogo } from '@sd/assets/svgs/brands';
import React from 'react';
import { CtaSecondaryButton } from '~/components/cta-secondary-button';
export const Github = () => {
return (
<section className="container mx-auto mb-32 flex w-full flex-col flex-wrap items-start px-4">
<hgroup>
<h1 className="flex-1 self-start text-2xl font-bold leading-8 md:text-3xl md:leading-10">
Free and open-source.{' '}
<span className="bg-gradient-to-r from-zinc-400 to-zinc-500/70 bg-clip-text font-semibold text-transparent">
See for yourself.
</span>
</h1>
<p className="mt-[12px] text-balance font-plex text-lg leading-[125%] tracking-[0.01em] text-ink-dull">
When we promise strong privacy and encryption, we mean it. Our apps source code
is entirely open-source and available on GitHub, so if youre wondering what
Spacedrive does with your data or have an improvement to share, youre welcome
to do so we welcome and appreciate contributions!
</p>
</hgroup>
<div className="relative z-10 mt-[36px]">
<CtaSecondaryButton
icon={<GithubLogo fill="#CBDBEC" className="size-5 opacity-60" />}
href="https://github.com/spacedriveapp/spacedrive"
target="_blank"
>
View source on GitHub
</CtaSecondaryButton>
</div>
</section>
);
};

View File

@@ -0,0 +1,91 @@
import Image from 'next/image';
import {
getLatestRelease,
getReleaseFrontmatter,
getRepoStats,
githubFetch
} from '~/app/api/github';
import { GoldenBadge } from '~/components/golden-badge';
import { HeroImage } from '~/components/hero-image'; // Import the client-side component
import { HomeCtaButtons } from '~/components/home-cta-buttons';
import Particles from '~/components/particles';
import { PlatformIcons } from '~/components/platform-icons';
import { toTitleCase } from '~/utils/misc';
export async function Header() {
const [release, repoStats] = await Promise.all([
githubFetch(getLatestRelease),
githubFetch(getRepoStats)
]);
const { frontmatter } = getReleaseFrontmatter(release);
const starCount = (repoStats.stargazers_count / 1000).toFixed(1);
return (
<div className="flex w-full flex-col items-center px-4">
<div className="mt-22 lg:mt-28" id="content" aria-hidden="true" />
<div className="mt-24 lg:mt-8" aria-hidden="true" />
<GoldenBadge
headline={`${starCount}k stars on GitHub`}
className="mt-[50px] lg:mt-0"
href={`https://github.com/spacedriveapp/spacedrive`}
/>
<h1 className="fade-in-heading z-30 mb-3 text-center text-3xl font-bold leading-[1.3] tracking-tight md:text-5xl lg:text-6xl">
<span className="inline bg-gradient-to-b from-[#EFF1FB_15%] to-[#B8CEE0_85%] bg-clip-text text-transparent">
{`Sync, manage, & discover.`}
<br />
{`Across all your devices.`}
</span>
</h1>
<p className="animation-delay-1 fade-in-heading text-md leading-2 z-30 mb-8 mt-1 max-w-4xl text-balance text-center text-gray-450 lg:text-lg lg:leading-8">
Your files, always within reach. Experience seamless synchronization, intuitive
management, and powerful discovery tools all in one place.
</p>
<HomeCtaButtons
latestVersion={[
frontmatter.category && toTitleCase(frontmatter.category),
`v${release.tag_name}`
]
.filter(Boolean)
.join(' ')}
/>
<PlatformIcons />
<div>
<div className="xl2:relative z-30 flex h-[255px] w-full px-6 sm:h-[428px] md:mt-12 md:h-[428px] lg:h-auto">
<div className="absolute inset-x-0 top-[450px] mx-auto flex size-[200px] md:size-[500px]">
<Particles
quantity={80}
ease={80}
staticity={100}
color={'#58B3FF'}
refresh
vy={-0.2}
vx={-0.05}
/>
</div>
{/* 1st light */}
<Image
loading="eager"
className="absolute-horizontal-center animation-delay-2 top-[300px] -z-10 select-none fade-in xs:top-[180px] md:top-[130px]"
width={1200}
height={626}
alt="l"
src="/images/app/gradient.webp"
/>
{/* 2nd light */}
<div className="animation-delay-2 absolute-horizontal-center top-[450px] size-[150px] rounded-full bg-gradient-to-t from-transparent to-[#328FDD]/40 blur-[20px] fade-in xs:top-[180px] md:top-[500px] md:h-[500px] md:w-[240px] md:blur-2xl" />
<HeroImage
src="/images/app/app.webp"
alt="Spacedrive App Image"
width={1200}
height={800}
/>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,7 @@
export * from './header';
export * from './assistant';
export * from './explorer';
export * from './github';
export * from './features';
export * from './search';
export * from './tags';

View File

@@ -0,0 +1,54 @@
'use client';
import { GooglePlayLogo } from '@phosphor-icons/react';
import { Apple } from '@sd/assets/svgs/brands';
import Image from 'next/image';
import { tw } from '@sd/ui';
const DownloadButton = tw.button`flex w-fit flex-row items-center gap-2 rounded-lg border border-zinc-800 bg-zinc-900 p-2.5 text-[13px] transition-all duration-300 hover:border-zinc-700 hover:bg-zinc-800`;
const Mobile = () => {
return (
<div className="container mx-auto mt-10 flex flex-col flex-wrap items-center gap-10 px-4">
<Image
style={{
maxWidth: 900,
maxHeight: 600
}}
loading="eager"
quality={100}
width={900}
height={600}
className="object-contain"
layout="responsive"
src="/images/mobile.webp"
alt="Mobile"
/>
<div className="flex flex-col gap-1">
<h1 className="flex flex-col self-center text-center text-2xl font-semibold md:flex-row md:text-3xl">
Cross platform.&nbsp;
<span className="bg-gradient-to-r from-zinc-400 to-zinc-600 bg-clip-text text-transparent">
<br className="hidden lg:visible" />
Available on iOS and Android
</span>
</h1>
<p className="w-full max-w-[600px] text-center text-ink-faint">
Using the mobile app, you can sync your files across all your devices. Take your
personal data with you wherever you are!
</p>
<div className="mx-auto mt-4 flex flex-row flex-wrap justify-center gap-4">
<DownloadButton>
<GooglePlayLogo />
Open Play Store
</DownloadButton>
<DownloadButton>
<Apple className="size-4" />
Open App Store
</DownloadButton>
</div>
</div>
</div>
);
};
export default Mobile;

View File

@@ -0,0 +1,23 @@
export const Search = () => {
return (
<section className="container mx-auto flex w-full flex-col flex-wrap items-start overflow-hidden">
<h2 className="w-full max-w-[600px] flex-1 self-start p-4 text-2xl font-semibold leading-7 md:text-3xl md:leading-10">
Search.{' '}
<span className="bg-gradient-to-r from-zinc-400 to-zinc-600 bg-clip-text text-transparent">
Find what youre looking for with ease using advanced filters.
</span>
</h2>
<div className="mt-4 w-full p-4">
<video
className="h-auto w-full rounded-xl object-fill"
autoPlay
playsInline
muted
controls={false}
loop
src="/videos/Spacedrive_search.webm"
/>
</div>
</section>
);
};

View File

@@ -0,0 +1,68 @@
'use client';
import { AnimatePresence } from 'framer-motion';
import React, { memo, useCallback, useState } from 'react';
import { SelectedVideo, Video } from '~/components/video';
const videos: {
title: string;
src: string;
description: string;
}[] = [
{
title: 'Tag Assignment mode',
src: '/videos/Spacedrive_tagmode.webm',
description: 'Assign tags to files and folders quickly and easily'
},
{
title: 'Contextual Tagging',
src: '/videos/Spacedrive_tags.webm',
description: 'Tag files and folders directly from the right-click menu'
}
];
const MemoizedVideo = memo(Video);
const MemoizedTags = memo(function Tags() {
const [selectedVideo, setSelectedVideo] = useState<null | string>(null);
const handleVideoSelect = useCallback((src: string | null) => {
setSelectedVideo(src);
}, []);
return (
<>
{selectedVideo ? (
<AnimatePresence>
<SelectedVideo src={selectedVideo} setSelectedVideo={setSelectedVideo} />
</AnimatePresence>
) : null}
<div className="container mx-auto flex flex-col flex-wrap items-center gap-10 p-4">
<h1 className="flex-1 self-start text-2xl font-semibold leading-8 md:text-3xl md:leading-10 lg:self-start">
Multiple ways to set tags.{' '}
<span className="bg-gradient-to-r from-zinc-400 to-zinc-600 bg-clip-text text-transparent">
{/* Some controlled line breaks here based on breakpoint to make sure the breaks looks nice always :) */}
<br className="lg:hidden" />
quickly organize your files.
</span>
</h1>
<div className="grid w-full grid-cols-1 gap-16 md:grid-cols-2 md:gap-6">
{videos.map((video) => (
<div key={video.src}>
<MemoizedVideo
setSelectedVideo={handleVideoSelect}
layoutId={`video-${video.src}`}
onClick={() => handleVideoSelect(video.src)}
{...video}
/>
<h2 className="mt-5 text-lg font-bold text-white">{video.title}</h2>
<p className="text-md text-ink-dull">{video.description}</p>
</div>
))}
</div>
</div>
</>
);
});
export default MemoizedTags;

View File

@@ -1,18 +1,12 @@
import { ArrowUp } from '@phosphor-icons/react/dist/ssr';
import Image from 'next/image';
import CyclingImage from '~/components/CyclingImage';
import { toTitleCase } from '~/utils/util';
import { Assistant, Explorer, Features, Github, Header, Search } from '~/app/page-sections';
import { getLatestRelease, getReleaseFrontmatter, githubFetch } from './api/github';
import { Background } from './Background';
import { Downloads } from './Downloads';
import { NewBanner } from './NewBanner';
import Mobile from './page-sections/mobile';
import Tags from './page-sections/tags';
export const metadata = {
title: 'Spacedrive — A file manager from the future.',
title: 'Spacedrive — Sync, manage, and discover. Across all your devices.',
description:
'Combine your drives and clouds into one database that you can organize and explore from any device. Designed for creators, hoarders and the painfully disorganized.',
keywords:
'files,file manager,spacedrive,file explorer,vdfs,distributed filesystem,cas,content addressable storage,virtual filesystem,photos app, video organizer,video encoder,tags,tag based filesystem',
authors: {
@@ -21,102 +15,18 @@ export const metadata = {
}
};
export default async function Page() {
const release = await githubFetch(getLatestRelease);
const { frontmatter } = getReleaseFrontmatter(release);
export default function Page() {
return (
<>
<Background />
<Image
loading="eager"
className="absolute-horizontal-center fade-in"
width={1278}
height={626}
alt="l"
src="/images/misc/header-gradient.webp"
/>
<div className="flex w-full flex-col items-center px-4">
<div className="mt-22 lg:mt-28" id="content" aria-hidden="true" />
<div className="mt-24 lg:mt-8" />
<NewBanner
headline={`Alpha ${release.tag_name} is out!`}
className="mt-[50px] lg:mt-0"
href={`/docs/changelog/alpha/${release.tag_name}`}
/>
<h1 className="fade-in-heading z-30 mb-3 bg-clip-text px-2 text-center text-4xl font-bold leading-tight text-white md:text-5xl lg:text-7xl">
One Explorer. All Your Files.
</h1>
<p className="animation-delay-1 fade-in-heading text-md leading-2 z-30 mb-8 mt-1 max-w-4xl text-center text-gray-450 lg:text-lg lg:leading-8">
Unify files from all your devices and clouds into a single, easy-to-use
explorer.
<br />
<span className="hidden sm:block">
Designed for creators, hoarders and the painfully disorganized.
</span>
</p>
<Downloads
latestVersion={[
frontmatter.category && toTitleCase(frontmatter.category),
`v${release.tag_name}`
]
.filter(Boolean)
.join(' ')}
/>
<div className="pb-6 xs:pb-24">
<div className="xl2:relative z-30 flex h-[255px] w-full px-6 sm:h-[428px] md:mt-[75px] md:h-[428px] lg:h-auto">
<Image
loading="eager"
className="absolute-horizontal-center animation-delay-2 top-[380px] fade-in xs:top-[180px] md:top-[130px]"
width={1200}
height={626}
alt="l"
src="/images/app/gradient.webp"
/>
<div className="relative m-auto mt-10 flex w-full max-w-7xl overflow-hidden rounded-lg transition-transform duration-700 ease-in-out hover:-translate-y-4 hover:scale-[1.02] md:mt-0">
<div className="flex flex-col items-center justify-center">
<div className="z-30 flex w-full rounded-lg border-t border-app-line/50 backdrop-blur">
<CyclingImage
loading="eager"
width={1278}
height={626}
alt="spacedrive app"
className="rounded-lg"
images={[
'/images/app/1.webp',
'/images/app/2.webp',
'/images/app/3.webp',
'/images/app/4.webp',
'/images/app/5.webp',
'/images/app/10.webp',
'/images/app/6.webp',
'/images/app/7.webp',
'/images/app/8.webp',
'/images/app/9.webp'
]}
/>
<Image
loading="eager"
className="pointer-events-none absolute opacity-100 transition-opacity duration-1000 ease-in-out hover:opacity-0 md:w-auto"
width={2278}
height={626}
alt="l"
src="/images/app/gradient-overlay.png"
/>
</div>
<ArrowUp className="invisible size-7 pt-2 text-white/40 md:visible" />
<p className="invisible pt-2 text-xs text-white/40 md:visible">
Hover to see more
</p>
</div>
</div>
</div>
</div>
{/* <WormHole /> */}
{/* <BentoBoxes /> */}
{/* <CloudStorage /> */}
{/* <DownloadToday isWindows={deviceOs?.isWindows} /> */}
{/* <div className="h-[100px] sm:h-[200px] w-full" /> */}
<Header />
<div className="flex flex-col gap-20 md:gap-[200px]">
{/* <Mobile /> */}
{/* <Features /> */}
<Explorer />
<Tags />
<Search />
{/* <Assistant /> */}
<Github />
</div>
</>
);

View File

@@ -1,13 +0,0 @@
'use client';
import dynamic from 'next/dynamic';
const Space = dynamic(() => import('~/components/Space'), { ssr: false });
export function Background() {
return (
<div className="opacity-60">
<Space />
</div>
);
}

View File

@@ -0,0 +1,68 @@
'use client';
import { X } from '@phosphor-icons/react';
import { Button, Dialog } from '@sd/ui';
import type { Plan } from './plans';
interface PlanAddonsProps {
plan: Plan;
isAnnual: boolean;
onClose: () => void;
}
export function PlanAddons({ plan, isAnnual, onClose }: PlanAddonsProps) {
return (
<Dialog open onOpenChange={() => onClose()}>
<div className="fixed inset-0 z-50 bg-black/50 backdrop-blur-sm">
<div className="fixed left-1/2 top-1/2 w-full max-w-2xl -translate-x-1/2 -translate-y-1/2 rounded-xl bg-gray-850 p-6">
<div className="mb-6 flex items-center justify-between">
<h2 className="text-2xl font-bold text-white">
Customize your {plan.name} plan
</h2>
<button onClick={onClose}>
<X className="h-6 w-6 text-gray-400" />
</button>
</div>
<div className="space-y-6">
<div className="rounded-lg border border-gray-500/50 p-4">
<h3 className="mb-4 text-lg font-semibold text-white">
Data Retention Add-on
</h3>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-3">
{[1, 2, 3, 4, 5].map((years) => (
<label
key={years}
className="relative flex cursor-pointer rounded-lg border border-gray-500/50 p-4 hover:bg-gray-800/50"
>
<input
type="radio"
name="retention"
className="peer sr-only"
/>
<div className="flex flex-col">
<span className="text-sm font-medium text-white">
{years} Year{years > 1 ? 's' : ''}
</span>
<span className="text-sm text-gray-400">
${(years * 36).toFixed(2)}
</span>
</div>
</label>
))}
</div>
</div>
<div className="flex justify-end gap-4">
<Button variant="gray" onClick={onClose}>
Cancel
</Button>
<Button variant="accent">Continue to checkout</Button>
</div>
</div>
</div>
</div>
</Dialog>
);
}

View File

@@ -0,0 +1,79 @@
'use client';
import { Check } from '@phosphor-icons/react';
import { useState } from 'react';
import { Button, Switch } from '@sd/ui';
import { PlanAddons } from './PlanAddons';
import { plans, type Plan } from './plans';
export function PricingCards() {
const [isAnnual, setIsAnnual] = useState(false);
const [selectedPlan, setSelectedPlan] = useState<Plan | null>(null);
return (
<div className="mx-auto max-w-7xl px-4">
<div className="fade-in-heading animation-delay-2 mx-auto mb-8 flex w-full items-center justify-center gap-3">
<p className="text-sm font-medium text-white">Monthly</p>
<Switch checked={isAnnual} onCheckedChange={setIsAnnual} size="lg" />
<p className="text-sm font-medium text-white">
Yearly <span className="text-primary-500">(20% off)</span>
</p>
</div>
<div className="grid grid-cols-1 gap-6 lg:grid-cols-5">
{plans.map((plan) => (
<div
key={plan.name}
className={`rounded-xl border p-6 ${
plan.name === 'Pro'
? 'pro-card-border-gradient pro-card-shadow lg:col-span-2'
: 'border-gray-500/50'
}`}
>
<div className="flex flex-col">
<h3 className="text-xl font-semibold text-white">{plan.name}</h3>
{plan.price ? (
<div className="mt-4">
<span className="text-3xl font-bold text-white">
${isAnnual ? plan.price.yearly : plan.price.monthly}
</span>
<span className="text-gray-400">/mo</span>
</div>
) : (
<div className="mt-4">
<span className="text-xl text-white">{plan.subTitle}</span>
</div>
)}
<ul className="mt-6 space-y-4">
{plan.features.map((feature) => (
<li key={feature} className="flex items-start">
<Check className="mr-2 h-5 w-5 flex-shrink-0 text-primary-500" />
<span className="text-sm text-gray-300">{feature}</span>
</li>
))}
</ul>
<Button
variant={plan.name === 'Pro' ? 'accent' : 'gray'}
className="mt-8"
onClick={() => setSelectedPlan(plan)}
>
{plan.name === 'Free' ? 'Get Started' : 'Select Plan'}
</Button>
</div>
</div>
))}
</div>
{selectedPlan && (
<PlanAddons
plan={selectedPlan}
isAnnual={isAnnual}
onClose={() => setSelectedPlan(null)}
/>
)}
</div>
);
}

View File

@@ -1,7 +1,7 @@
import Image from 'next/image';
import React from 'react';
import { Background } from './Background';
// import { Background } from './Background';
import { Cards } from './Cards';
export const metadata = {
@@ -12,7 +12,7 @@ export const metadata = {
export default function PricingPage() {
return (
<>
<Background />
{/* <Background /> */}
<Image
loading="eager"
className="absolute-horizontal-center top-0 fade-in"

View File

@@ -0,0 +1,69 @@
export interface Plan {
name: string;
subTitle?: string;
price?: {
monthly: number;
yearly: number;
};
features: string[];
}
export const plans: Plan[] = [
{
name: 'Free',
subTitle: 'Free forever',
features: [
'Local storage only',
'Cross-platform support',
'End-to-end encryption',
'2 cloud devices',
'100 MB cloud database sync',
'7 days version history'
]
},
{
name: 'Personal',
price: {
monthly: 5.99,
yearly: 4.79
},
features: [
'1 TB storage',
'Unlimited cloud database sync',
'5 cloud devices',
'3 shares',
'Cold storage redundancy',
'30 days version history'
]
},
{
name: 'Pro',
price: {
monthly: 19.99,
yearly: 15.99
},
features: [
'5 TB storage',
'Unlimited cloud devices',
'Unlimited shares',
'Custom branding',
'Priority support',
'90 days version history'
]
},
{
name: 'Business',
price: {
monthly: 39.99,
yearly: 31.99
},
features: [
'10 TB storage',
'Up to 10 team members',
'SSO integration',
'Advanced security',
'Priority phone support',
'180 days version history'
]
}
];

View File

@@ -1,205 +1,260 @@
export const items = [
{
when: 'Big Bang',
subtext: 'Q1 2022',
completed: true,
title: 'File discovery',
description:
'Scan devices, drives and cloud accounts to build a directory of all files with metadata.'
},
{
title: 'Preview generation',
completed: true,
description: 'Auto generate lower resolution stand-ins for image and video.'
},
{
title: 'Statistics',
completed: true,
description: 'Total capacity, index size, preview media size, free space etc.'
},
{
title: 'Jobs',
completed: true,
description:
'Tasks to be performed via a queue system with multi-threaded workers, such as indexing, identifying, generating preview media and moving files. With a Job Manager interface for tracking progress, pausing and restarting jobs.'
},
{
completed: true,
title: 'Explorer',
description:
'Browse online/offline storage locations, view files with metadata, perform basic CRUD.'
},
{
completed: true,
title: 'Self hosting',
description:
'Spacedrive can be deployed as a service via Docker, behaving as just another device powering your personal cloud.'
},
{
completed: true,
title: 'Tags',
description:
'Define routines on custom tags to automate workflows, easily tag files individually, in bulk and automatically via rules.'
},
{
completed: true,
title: 'Search',
description:
'Instantly search your library, including offline locations. Use advanced filters to refine and save searches for later.'
},
{
completed: true,
title: 'Quick view',
description:
'View images, videos and documents in a full screen modal with nested inspector and context switcher.'
},
{
completed: true,
title: 'Media view',
description: 'Turn any directory into a camera roll including media from subdirectories'
},
{
completed: true,
title: 'Spacedrop',
description: 'Drop files between devices and contacts on a keybind like AirDrop.'
},
{
completed: true,
title: 'AI labeling for images',
description:
'Automatically label images with objects, with a model loader to support future models and upgrading to more powerful models for various jobs.'
},
{
title: 'Drag & drop',
completed: true,
description: 'Drag and drop files between devices and drives.'
},
{
title: 'Language support',
completed: true,
description: 'Support for 12+ languages, with a community-driven translation via i18n.'
},
{
when: '0.2 Alpha',
subtext: 'February 2024',
title: 'Column view',
description: 'View files in the beloved column layout with a nested inspector, with trees!'
},
{
title: 'Improved file transfers',
description:
'Instant copy for files on the same device. An improved progress bar and speed indicator. Pause, resume and prioritize transfers.'
},
{
title: 'Folder sync',
description:
'Configure replication or bidirectional sync between folders on any device or drive.'
},
{
title: 'File converter',
description: 'Convert image and video between common formats from the context menu.'
},
{
title: 'Command Palette',
description: 'Quickly navigate to any file or folder from anywhere in the app.'
},
{
title: 'Video thumbstrips',
description:
'Generate and display thumbstrips for videos, with a scrubber to preview the video.'
},
{
when: '0.3 Alpha',
subtext: 'May 2024',
title: 'Connect devices & sync',
description: 'Automatically synchronized libraries across all your devices.'
},
{
title: 'Mobile app TestFlight & Play Store Beta',
description: 'Access your library on the go, with a mobile app for iOS and Android.'
},
{
title: 'Spacedrive in every language, i18n',
description: 'Spacedrive will be available in every language.'
},
{
title: 'Key manager',
description:
'View, mount, unmount and hide keys. Mounted keys can be used to instantly encrypt and decrypt any files on your node.'
},
{
title: 'Advanced media analysis',
description: 'Transcribe audio, identify faces, video scenes and more.'
},
{
title: 'Third-party cloud integrations',
description:
'Filesystem integrations with iCloud, Google Drive, Dropbox, OneDrive & Mega + easy API for the community to add more.'
},
{
title: 'Comments',
description:
'Add comments to files and folders, with support for XY coordinates for photos and timestamp for videos.'
},
{
when: '0.4 Alpha',
subtext: 'June 2024',
title: 'AI search',
description:
'Search the contents of your files, including images, audio and video with a deep understanding of context and content.'
},
{
title: 'Spaces',
description:
'Create and manage Spaces, hosted locally or on the cloud, to share with friends or publish on the web. Spaces are AI native, with a custom local language model that can converse with the user and puppeteer the Explorer view.'
},
{
title: 'Spacedrive Cloud',
description:
'Backup and sync from anywhere with a Spacedrive Cloud account. Paid plans for additional storage and cloud features.'
},
{
title: 'CLI',
description:
'Access Spacedrive from the command line, with a rich set of commands to manage your library and devices.'
},
{
title: 'Web portal',
description:
'Access the web interface via the browser, remotely access your library and manage your devices and Spaces.'
},
{
title: 'iOS & Android release',
description: 'Spacedrive will be available on the App Store and Google Play Store.'
},
{
title: 'File versioning',
description:
'Automatically save versions of files when they change, with a timeline view and the ability to restore.'
},
{
title: 'Local Server Protection',
description:
"Protect local instances of Spacedrive's server from other clients on your network."
},
{
when: '0.5 Beta',
subtext: 'August 2024',
title: 'Encrypted vault(s)',
description:
'Effortlessly manage & encrypt sensitive files. Encrypt individual files or create flexible-size vaults.'
},
{
title: 'Extensions',
description:
'Build tools on top of Spacedrive, extend functionality and integrate third party services.'
},
{
when: '1.0 Release',
subtext: '2025',
title: 'Security audit',
description:
'We will hire a third party security firm to audit the codebase and ensure the highest level of security.'
}
];
export const items: {
when?: string;
subtext?: string;
completed?: boolean;
title: string;
description: string;
}[] = [
{
when: 'Big Bang',
subtext: 'Q1 2022',
completed: true,
title: 'File discovery',
description:
'Scan devices, drives and cloud accounts to build a directory of all files with metadata.'
},
{
title: 'Preview generation',
completed: true,
description: 'Auto generate lower resolution stand-ins for image and video.'
},
{
title: 'Statistics',
completed: true,
description: 'Total capacity, index size, preview media size, free space etc.'
},
{
title: 'Jobs',
completed: true,
description:
'Tasks to be performed via a queue system with multi-threaded workers, such as indexing, identifying, generating preview media and moving files. With a Job Manager interface for tracking progress, pausing and restarting jobs.'
},
{
completed: true,
title: 'Explorer',
description:
'Browse online/offline storage locations, view files with metadata, perform basic CRUD.'
},
{
completed: true,
title: 'Self hosting',
description:
'Spacedrive can be deployed as a service via Docker, behaving as just another device powering your personal cloud.'
},
{
completed: true,
title: 'Tags',
description:
'Define routines on custom tags to automate workflows, easily tag files individually, in bulk and automatically via rules.'
},
{
completed: true,
title: 'Search',
description:
'Instantly search your library, including offline locations. Use advanced filters to refine and save searches for later.'
},
{
completed: true,
title: 'Quick view',
description:
'View images, videos and documents in a full screen modal with nested inspector and context switcher.'
},
{
completed: true,
title: 'Media view',
description: 'Turn any directory into a camera roll including media from subdirectories'
},
{
completed: true,
title: 'Spacedrop',
description: 'Drop files between devices and contacts on a keybind like AirDrop.'
},
// {
// completed: true,
// title: 'AI labeling for images',
// description:
// 'Automatically label images with objects, with a model loader to support future models and upgrading to more powerful models for various jobs.'
// },
{
title: 'Drag & drop',
completed: true,
description: 'Drag and drop files between devices and drives.'
},
{
title: 'Language support',
completed: true,
description: 'Support for 12+ languages, with a community-driven translation via i18n.'
},
{
when: '0.2 Alpha',
subtext: 'February 2024',
title: 'Command Palette',
completed: true,
description: 'Quickly navigate to any file or folder from anywhere in the app.'
},
{
title: 'Video thumbstrips',
completed: true,
description:
'Generate and display thumbstrips for videos, with a scrubber to preview the video.'
},
{
title: 'Resizable Sidebar',
completed: true,
description:
'Customize the sidebar width to your liking, with a toggle to hide it completely.'
},
{
title: 'Move to Trash',
completed: true,
description:
'Have the option to move files and folders to the trash, instead of deleting them permanently.'
},
{
when: '0.3 Alpha',
subtext: 'May 2024',
title: 'Media File Metadata',
description: 'View metadata for media files, for all common formats.',
completed: true
},
{
when: '0.4 Alpha',
subtext: 'June 2024',
title: 'New Overview Design',
description:
'New Overview design with a focus on the most important information about your library.',
completed: true,
},
{
title: 'Tag Assignment',
completed: true,
description: 'Assign tags to multiple files and folders at once.'
},
{
title: 'Estimated time remaining for Jobs',
completed: true,
description: 'See how long a job will take to complete.'
},
{
when: '0.5 Beta',
subtext: 'December 2024',
title: 'iOS & Android release',
completed: false,
description: 'Spacedrive will be available on the App Store and Google Play Store.'
},
{
title: 'Spacedrive Cloud',
completed: false,
description:
'Sync your library to the cloud, to be accessed from anywhere (mobile & desktop apps).'
},
{
title: "New Authentication Services",
completed: false,
description: "Support for OAuth, Apple ID, Google Sign-In, and more."
},
{
when: '0.6 Beta',
subtext: 'Q1 2025',
title: '3rd-Party Authentication',
completed: false,
description: 'Authenticate with Spacedrive using your Google, Apple, or Github accounts.'
},
{
title: "Peer-to-Peer Sync",
completed: false,
description: "Sync & Fetch files from your library with other devices on your local network."
},
{
title: 'AI Search',
completed: false,
description:
'Search the contents of your files, including images, audio and video with a deep understanding of context and content.'
},
{
when: '0.7 Beta',
subtext: 'Q2 2025',
title: 'Third-party cloud integrations',
completed: false,
description:
'Filesystem integrations with iCloud, Google Drive, Dropbox, OneDrive & Mega + easy API for the community to add more.'
},
{
title: 'Column view',
completed: false,
description: 'View files in the beloved column layout with a nested inspector, with trees!'
},
{
when: '1.0 Release',
subtext: 'Q3 2025',
title: 'Security audit',
completed: false,
description:
'We will hire a third party security firm to audit the codebase and ensure the highest level of security.'
},
{
title: 'Local Server Protection',
completed: false,
description:
"Protect local instances of Spacedrive's server from other clients on your network."
},
{
when: 'The Future',
subtext: 'To be determined',
title: 'Security audit',
description:
'We will hire a third party security firm to audit the codebase and ensure the highest level of security.'
},
{
title: 'Extensions',
completed: false,
description:
'Build tools on top of Spacedrive, extend functionality and integrate third party services.'
},
{
title: 'File versioning',
completed: false,
description:
'Automatically save versions of files when they change, with a timeline view and the ability to restore.'
},
{
title: 'CLI',
completed: false,
description:
'Access Spacedrive from the command line, with a rich set of commands to manage your library and devices.'
},
{
title: 'Web portal',
completed: false,
description:
'Access the web interface via the browser, remotely access your library and manage your devices and Spaces.'
},
{
title: 'Spaces',
completed: false,
description:
'Create and manage Spaces, hosted locally or on the cloud, to share with friends or publish on the web. Spaces are AI native, with a custom local language model that can converse with the user and puppeteer the Explorer view.'
},
{
title: 'Key manager',
completed: false,
description:
'View, mount, unmount and hide keys. Mounted keys can be used to instantly encrypt and decrypt any files on your node.'
},
{
title: 'Advanced media analysis',
completed: false,
description: 'Transcribe audio, identify faces, video scenes and more.'
},
{
title: 'Comments',
completed: false,
description:
'Add comments to files and folders, with support for XY coordinates for photos and timestamp for videos.'
},
{
title: 'File converter',
completed: false,
description: 'Convert image and video between common formats from the context menu.'
},
];

View File

@@ -11,8 +11,16 @@ export const metadata = {
export default function Page() {
return (
<div className="lg:prose-xs container prose m-auto mb-20 flex max-w-4xl flex-col gap-20 p-4 pt-32 dark:prose-invert">
<section className="flex flex-col items-center">
<div className="lg:prose-xs container prose prose-invert m-auto mb-20 mt-40 flex max-w-4xl flex-col gap-20 overflow-x-hidden p-4 md:overflow-visible">
<section className="relative flex flex-col items-center">
<div
className="animation-delay-1 absolute top-[-1025px] size-[1800px] opacity-50 blur-sm duration-150 fade-in sm:right-[-400px] md:right-[-400px] lg:right-[-500px]"
style={{
backgroundImage: 'url(/images/roadmapbg.webp',
backgroundRepeat: 'no-repeat',
backgroundSize: 'contain'
}}
/>
<h1 className="fade-in-heading mb-0 text-center text-5xl leading-snug">
What's next for Spacedrive?
</h1>
@@ -21,10 +29,10 @@ export default function Page() {
so far.
</p>
<p>
Last updated: <span className="font-bold text-white">March 13, 2024</span>
Last updated: <span className="font-bold text-white">September 19th, 2024</span>
</p>
</section>
<section className="grid auto-cols-auto grid-flow-row grid-cols-[auto_1fr] gap-x-4">
<section className="mt-32 grid auto-cols-auto grid-flow-row grid-cols-[auto_1fr] gap-x-4">
{items.map((item, i) => (
<Fragment key={i}>
{/* Using span so i can use the group-last-of-type selector */}
@@ -85,7 +93,7 @@ export default function Page() {
</Fragment>
))}
</section>
<section className="space-y-2 rounded-xl bg-gray-850 p-8">
<section className="space-y-2 p-8 backdrop-blur-md">
<h2 className="my-1">That's not all.</h2>
<p>
We're always open to ideas and feedback over{' '}

View File

@@ -0,0 +1,7 @@
import React from 'react';
const Page: React.FC = () => {
return <h1>Store</h1>;
};
export default Page;

View File

@@ -1,6 +1,6 @@
import { ArrowRight } from '@phosphor-icons/react/dist/ssr';
import Link from 'next/link';
import Markdown from '~/components/Markdown';
import { CtaSecondaryButton } from '~/components/cta-secondary-button';
import { investors, teamMembers } from './people';
import { TeamMember } from './TeamMember';
@@ -12,65 +12,81 @@ export const metadata = {
export default function Page() {
return (
<Markdown articleClassNames="mx-auto mt-32 prose-a:text-white">
<div className="team-page relative mx-auto">
<div className="relative z-10">
<h1 className="fade-in-heading text-5xl leading-tight sm:leading-snug">
We believe file management should be{' '}
<span className="title-gradient">universal</span>.
</h1>
<p className="animation-delay-2 fade-in-heading text-white/50">
<div className="mt-[200px] flex w-full flex-col items-center overflow-x-hidden px-4 md:overflow-visible">
<div className="relative mx-auto h-auto w-full max-w-[1200px]">
<div
className="absolute -left-1/4 top-[10%] mx-auto size-[750px] blur-[5px] fade-in xs:inset-x-0 xs:left-[-16%] xs:top-[-50px] sm:left-0 sm:top-[-175px] sm:mx-auto md:inset-x-0 md:top-[-250px] md:size-[900px] lg:top-[-420px] lg:size-[1100px]"
style={{
backgroundImage: 'url(/images/circlebg.webp',
backgroundRepeat: 'no-repeat',
backgroundSize: 'contain',
backgroundPosition: 'center top'
}}
/>
<h1 className="fade-in-heading mx-auto w-full max-w-[750px] text-center text-5xl font-bold leading-tight sm:leading-snug">
We believe file management should be{' '}
<span className="bg-gradient-to-r from-fuchsia-500 to-indigo-500 bg-clip-text text-transparent">
universal
</span>
.
</h1>
<div className="mt-10 space-y-4">
<p className="animation-delay-2 fade-in-heading mx-auto max-w-[750px] text-center text-ink-faint">
Your priceless personal data shouldn't be stuck in a device ecosystem. It
should be OS agnostic, permanent and owned by you.
</p>
<p className="animation-delay-2 fade-in-heading text-white/50">
<p className="animation-delay-2 fade-in-heading mx-auto max-w-[600px] text-center text-ink-faint">
The data we create daily is our legacy—that will long outlive us. Open
source technology is the only way to ensure we retain absolute control over
the files that define our lives, at unlimited scale.
</p>
<Link
</div>
<div className="mt-12 flex w-full justify-center">
<CtaSecondaryButton
className="2 animation-delay-3 fade-in-heading min-w-fit px-5 duration-150"
icon={<ArrowRight weight="bold" />}
href="/docs/product/resources/faq"
className="animation-delay-3 fade-in-heading text-underline flex flex-row items-center text-gray-400 underline-offset-4 duration-150 hover:text-white"
>
<ArrowRight className="mr-2" width="1rem" height="1rem" />
Read more
</Link>
<div className="fade-in-heading animation-delay-5">
<h2 className="mt-10 text-2xl leading-relaxed sm:mt-20">Meet the team</h2>
<div className="my-10 grid grid-cols-2 gap-x-5 gap-y-10 xs:grid-cols-3 sm:grid-cols-4">
{teamMembers.map((member) => (
<TeamMember key={member.name} {...member} />
))}
</div>
<p className="text-sm text-gray-400">
... and all the awesome{' '}
<Link
href="https://github.com/spacedriveapp/spacedrive/graphs/contributors"
target="_blank"
rel="noreferrer"
className="oss-credit-gradient duration-200 hover:opacity-75"
>
open source contributors
</Link>{' '}
on GitHub.
</p>
<h2 id="investors" className="mb-2 mt-10 text-2xl leading-relaxed sm:mt-20">
Our investors
</h2>
<p className="text-sm text-gray-400">
We're backed by some of the greatest leaders in the technology industry.
</p>
<div className="my-10 grid grid-cols-3 gap-x-5 gap-y-10 sm:grid-cols-5">
{investors.map((investor) => (
<TeamMember
key={investor.name + investor.investmentRound}
{...investor}
/>
))}
</div>
</div>
</CtaSecondaryButton>
</div>
</div>
</Markdown>
<div className="fade-in-heading animation-delay-5">
<div className="mx-auto mt-40 w-full max-w-[700px]">
<h2 className="text-2xl font-bold leading-relaxed">Meet the team</h2>
<div className="my-10 grid grid-cols-2 gap-x-0 gap-y-10 xs:grid-cols-3 sm:grid-cols-4">
{teamMembers.map((member) => (
<TeamMember key={member.name} {...member} />
))}
</div>
<p className="text-sm text-gray-400">
... and all the awesome{' '}
<Link
href="https://github.com/spacedriveapp/spacedrive/graphs/contributors"
target="_blank"
rel="noreferrer"
className="oss-credit-gradient duration-200 hover:opacity-75"
>
open source contributors
</Link>{' '}
on GitHub.
</p>
</div>
<h2
id="investors"
className="mb-2 mt-10 text-2xl font-bold leading-relaxed sm:mt-20"
>
Our investors
</h2>
<p className="text-sm text-gray-400">
We're backed by some of the greatest leaders in the technology industry.
</p>
<div className="my-10 grid w-full max-w-[700px] grid-cols-3 gap-x-5 gap-y-10 sm:grid-cols-5">
{investors.map((investor) => (
<TeamMember key={investor.name + investor.investmentRound} {...investor} />
))}
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,11 @@
import React from 'react';
interface Props {
// Define your component props here
}
const Page: React.FC<Props> = () => {
return <h1>Use Cases</h1>;
};
export default Page;

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 882 KiB

View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 15 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 902 KiB

View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 78 KiB

View File

@@ -0,0 +1,263 @@
<svg viewBox="0 0 249 172" fill="none" xmlns="http://www.w3.org/2000/svg">
<g opacity="0.3" filter="url(#filter0_b_2155_6283)">
<rect width="96.5581" height="96.5581" rx="10"
transform="matrix(0.866025 0.5 -0.866025 0.5 124.358 74.5879)"
fill="url(#paint0_linear_2155_6283)" />
<rect y="0.5" width="95.5581" height="95.5581" rx="9.5"
transform="matrix(0.866025 0.5 -0.866025 0.5 124.791 74.8379)"
stroke="url(#paint1_linear_2155_6283)" />
</g>
<g filter="url(#filter1_b_2155_6283)">
<rect width="119.508" height="119.508" rx="10"
transform="matrix(0.866025 0.5 -0.866025 0.5 124.358 37.8677)"
fill="url(#paint2_linear_2155_6283)" />
<rect y="0.5" width="118.508" height="118.508" rx="9.5"
transform="matrix(0.866025 0.5 -0.866025 0.5 124.791 38.1177)"
stroke="url(#paint3_linear_2155_6283)" />
</g>
<g filter="url(#filter2_b_2155_6283)">
<rect width="143.438" height="143.438" rx="10"
transform="matrix(0.866025 0.5 -0.866025 0.5 124.358 0)" fill="url(#paint4_linear_2155_6283)" />
<rect y="0.5" width="142.438" height="142.438" rx="9.5"
transform="matrix(0.866025 0.5 -0.866025 0.5 124.791 0.25)"
stroke="url(#paint5_linear_2155_6283)" />
</g>
<path
d="M124.887 5C124.887 4.58578 124.551 4.25 124.137 4.25C123.723 4.25 123.387 4.58578 123.387 5L124.887 5ZM124.887 35C124.887 34.5858 124.551 34.25 124.137 34.25C123.723 34.25 123.387 34.5858 123.387 35L124.887 35ZM123.387 31C123.387 31.4142 123.723 31.75 124.137 31.75C124.551 31.75 124.887 31.4142 124.887 31L123.387 31ZM124.887 27C124.887 26.5858 124.551 26.25 124.137 26.25C123.723 26.25 123.387 26.5858 123.387 27L124.887 27ZM123.387 23C123.387 23.4142 123.723 23.75 124.137 23.75C124.551 23.75 124.887 23.4142 124.887 23L123.387 23ZM124.887 19C124.887 18.5858 124.551 18.25 124.137 18.25C123.723 18.25 123.387 18.5858 123.387 19L124.887 19ZM123.387 15C123.387 15.4142 123.723 15.75 124.137 15.75C124.551 15.75 124.887 15.4142 124.887 15L123.387 15ZM124.887 11C124.887 10.5858 124.551 10.25 124.137 10.25C123.723 10.25 123.387 10.5858 123.387 11L124.887 11ZM123.387 7C123.387 7.41421 123.723 7.75 124.137 7.75C124.551 7.75 124.887 7.41421 124.887 7L123.387 7ZM124.887 37L124.887 35L123.387 35L123.387 37L124.887 37ZM124.887 31L124.887 27L123.387 27L123.387 31L124.887 31ZM124.887 23L124.887 19L123.387 19L123.387 23L124.887 23ZM124.887 15L124.887 11L123.387 11L123.387 15L124.887 15ZM124.887 7L124.887 5L123.387 5L123.387 7L124.887 7Z"
fill="url(#paint6_linear_2155_6283)" />
<path
d="M123.387 147C123.387 147.414 123.723 147.75 124.137 147.75C124.551 147.75 124.887 147.414 124.887 147L123.387 147ZM123.387 106.15C123.387 106.564 123.723 106.9 124.137 106.9C124.551 106.9 124.887 106.564 124.887 106.15L123.387 106.15ZM124.887 110.45C124.887 110.036 124.551 109.7 124.137 109.7C123.723 109.7 123.387 110.036 123.387 110.45L124.887 110.45ZM123.387 114.75C123.387 115.164 123.723 115.5 124.137 115.5C124.551 115.5 124.887 115.164 124.887 114.75L123.387 114.75ZM124.887 119.05C124.887 118.636 124.551 118.3 124.137 118.3C123.723 118.3 123.387 118.636 123.387 119.05L124.887 119.05ZM123.387 123.35C123.387 123.764 123.723 124.1 124.137 124.1C124.551 124.1 124.887 123.764 124.887 123.35L123.387 123.35ZM124.887 127.65C124.887 127.236 124.551 126.9 124.137 126.9C123.723 126.9 123.387 127.236 123.387 127.65L124.887 127.65ZM123.387 131.95C123.387 132.364 123.723 132.7 124.137 132.7C124.551 132.7 124.887 132.364 124.887 131.95L123.387 131.95ZM124.887 136.25C124.887 135.836 124.551 135.5 124.137 135.5C123.723 135.5 123.387 135.836 123.387 136.25L124.887 136.25ZM123.387 140.55C123.387 140.964 123.723 141.3 124.137 141.3C124.551 141.3 124.887 140.964 124.887 140.55L123.387 140.55ZM124.887 144.85C124.887 144.436 124.551 144.1 124.137 144.1C123.723 144.1 123.387 144.436 123.387 144.85L124.887 144.85ZM123.387 104L123.387 106.15L124.887 106.15L124.887 104L123.387 104ZM123.387 110.45L123.387 114.75L124.887 114.75L124.887 110.45L123.387 110.45ZM123.387 119.05L123.387 123.35L124.887 123.35L124.887 119.05L123.387 119.05ZM123.387 127.65L123.387 131.95L124.887 131.95L124.887 127.65L123.387 127.65ZM123.387 136.25L123.387 140.55L124.887 140.55L124.887 136.25L123.387 136.25ZM123.387 144.85L123.387 147L124.887 147L124.887 144.85L123.387 144.85Z"
fill="url(#paint7_linear_2155_6283)" />
<path
d="M7.13672 70.25C6.7225 70.25 6.38672 70.5858 6.38672 71C6.38672 71.4142 6.7225 71.75 7.13672 71.75V70.25ZM65.2617 70.25C64.8475 70.25 64.5117 70.5858 64.5117 71C64.5117 71.4142 64.8475 71.75 65.2617 71.75V70.25ZM61.5117 71.75C61.9259 71.75 62.2617 71.4142 62.2617 71C62.2617 70.5858 61.9259 70.25 61.5117 70.25V71.75ZM57.7617 70.25C57.3475 70.25 57.0117 70.5858 57.0117 71C57.0117 71.4142 57.3475 71.75 57.7617 71.75V70.25ZM54.0117 71.75C54.4259 71.75 54.7617 71.4142 54.7617 71C54.7617 70.5858 54.4259 70.25 54.0117 70.25V71.75ZM50.2617 70.25C49.8475 70.25 49.5117 70.5858 49.5117 71C49.5117 71.4142 49.8475 71.75 50.2617 71.75V70.25ZM46.5117 71.75C46.9259 71.75 47.2617 71.4142 47.2617 71C47.2617 70.5858 46.9259 70.25 46.5117 70.25V71.75ZM42.7617 70.25C42.3475 70.25 42.0117 70.5858 42.0117 71C42.0117 71.4142 42.3475 71.75 42.7617 71.75V70.25ZM39.0117 71.75C39.4259 71.75 39.7617 71.4142 39.7617 71C39.7617 70.5858 39.4259 70.25 39.0117 70.25V71.75ZM35.2617 70.25C34.8475 70.25 34.5117 70.5858 34.5117 71C34.5117 71.4142 34.8475 71.75 35.2617 71.75V70.25ZM31.5117 71.75C31.9259 71.75 32.2617 71.4142 32.2617 71C32.2617 70.5858 31.9259 70.25 31.5117 70.25V71.75ZM27.7617 70.25C27.3475 70.25 27.0117 70.5858 27.0117 71C27.0117 71.4142 27.3475 71.75 27.7617 71.75V70.25ZM24.0117 71.75C24.4259 71.75 24.7617 71.4142 24.7617 71C24.7617 70.5858 24.4259 70.25 24.0117 70.25V71.75ZM20.2617 70.25C19.8475 70.25 19.5117 70.5858 19.5117 71C19.5117 71.4142 19.8475 71.75 20.2617 71.75V70.25ZM16.5117 71.75C16.9259 71.75 17.2617 71.4142 17.2617 71C17.2617 70.5858 16.9259 70.25 16.5117 70.25V71.75ZM12.7617 70.25C12.3475 70.25 12.0117 70.5858 12.0117 71C12.0117 71.4142 12.3475 71.75 12.7617 71.75V70.25ZM9.01172 71.75C9.42593 71.75 9.76172 71.4142 9.76172 71C9.76172 70.5858 9.42593 70.25 9.01172 70.25V71.75ZM67.1367 70.25H65.2617V71.75H67.1367V70.25ZM61.5117 70.25H57.7617V71.75H61.5117V70.25ZM54.0117 70.25H50.2617V71.75H54.0117V70.25ZM46.5117 70.25H42.7617V71.75H46.5117V70.25ZM39.0117 70.25H35.2617V71.75H39.0117V70.25ZM31.5117 70.25H27.7617V71.75H31.5117V70.25ZM24.0117 70.25H20.2617V71.75H24.0117V70.25ZM16.5117 70.25H12.7617V71.75H16.5117V70.25ZM9.01172 70.25L7.13672 70.25V71.75L9.01172 71.75V70.25Z"
fill="url(#paint8_linear_2155_6283)" />
<path
d="M183.137 70.25C182.723 70.25 182.387 70.5858 182.387 71C182.387 71.4142 182.723 71.75 183.137 71.75V70.25ZM241.262 70.25C240.848 70.25 240.512 70.5858 240.512 71C240.512 71.4142 240.848 71.75 241.262 71.75V70.25ZM237.512 71.75C237.926 71.75 238.262 71.4142 238.262 71C238.262 70.5858 237.926 70.25 237.512 70.25V71.75ZM233.762 70.25C233.348 70.25 233.012 70.5858 233.012 71C233.012 71.4142 233.348 71.75 233.762 71.75V70.25ZM230.012 71.75C230.426 71.75 230.762 71.4142 230.762 71C230.762 70.5858 230.426 70.25 230.012 70.25V71.75ZM226.262 70.25C225.848 70.25 225.512 70.5858 225.512 71C225.512 71.4142 225.848 71.75 226.262 71.75V70.25ZM222.512 71.75C222.926 71.75 223.262 71.4142 223.262 71C223.262 70.5858 222.926 70.25 222.512 70.25V71.75ZM218.762 70.25C218.348 70.25 218.012 70.5858 218.012 71C218.012 71.4142 218.348 71.75 218.762 71.75V70.25ZM215.012 71.75C215.426 71.75 215.762 71.4142 215.762 71C215.762 70.5858 215.426 70.25 215.012 70.25V71.75ZM211.262 70.25C210.848 70.25 210.512 70.5858 210.512 71C210.512 71.4142 210.848 71.75 211.262 71.75V70.25ZM207.512 71.75C207.926 71.75 208.262 71.4142 208.262 71C208.262 70.5858 207.926 70.25 207.512 70.25V71.75ZM203.762 70.25C203.348 70.25 203.012 70.5858 203.012 71C203.012 71.4142 203.348 71.75 203.762 71.75V70.25ZM200.012 71.75C200.426 71.75 200.762 71.4142 200.762 71C200.762 70.5858 200.426 70.25 200.012 70.25V71.75ZM196.262 70.25C195.848 70.25 195.512 70.5858 195.512 71C195.512 71.4142 195.848 71.75 196.262 71.75V70.25ZM192.512 71.75C192.926 71.75 193.262 71.4142 193.262 71C193.262 70.5858 192.926 70.25 192.512 70.25V71.75ZM188.762 70.25C188.348 70.25 188.012 70.5858 188.012 71C188.012 71.4142 188.348 71.75 188.762 71.75V70.25ZM185.012 71.75C185.426 71.75 185.762 71.4142 185.762 71C185.762 70.5858 185.426 70.25 185.012 70.25V71.75ZM243.137 70.25H241.262V71.75H243.137V70.25ZM237.512 70.25H233.762V71.75H237.512V70.25ZM230.012 70.25H226.262V71.75H230.012V70.25ZM222.512 70.25H218.762V71.75H222.512V70.25ZM215.012 70.25H211.262V71.75H215.012V70.25ZM207.512 70.25H203.762V71.75H207.512V70.25ZM200.012 70.25H196.262V71.75H200.012V70.25ZM192.512 70.25H188.762V71.75H192.512V70.25ZM185.012 70.25L183.137 70.25V71.75L185.012 71.75V70.25Z"
fill="url(#paint9_linear_2155_6283)" />
<g opacity="0.5" filter="url(#filter3_bi_2155_6283)">
<circle cx="47.6215" cy="47.6215" r="47.6215"
transform="matrix(0.866025 -0.5 0.866025 0.5 42.5947 70.5717)" fill="#2C2F3E" />
<circle cx="47.6215" cy="47.6215" r="47.1215"
transform="matrix(0.866025 -0.5 0.866025 0.5 42.5947 70.5717)"
stroke="url(#paint10_linear_2155_6283)" />
</g>
<g filter="url(#filter4_d_2155_6283)">
<g filter="url(#filter5_bi_2155_6283)">
<path
d="M134.531 54.5834C133.208 55.3473 131.063 55.3473 129.74 54.5834L125.009 51.8517C116.713 47.0624 103.154 47.0951 94.7895 51.9247C86.4245 56.7542 86.3678 64.5824 94.6631 69.3716L99.3946 72.1034C100.718 72.8672 100.718 74.1058 99.3946 74.8697C96.7493 76.3969 96.7314 78.8647 99.3546 80.3792L118.36 91.3523C120.984 92.8668 125.258 92.8564 127.903 91.3292L163.04 71.0429C165.685 69.5157 165.703 67.0479 163.08 65.5334L144.074 54.5603C141.451 53.0458 137.177 53.0562 134.531 54.5834ZM117.023 56.4622C120.959 58.7348 120.899 62.4539 116.963 64.7265C113.027 66.9991 106.585 67.0337 102.649 64.7611C98.7189 62.4922 98.7457 58.7847 102.709 56.4968C106.671 54.2088 113.093 54.1933 117.023 56.4622Z"
fill="url(#paint11_linear_2155_6283)" />
<path
d="M129.307 54.8334C130.869 55.7354 133.402 55.7354 134.964 54.8334C137.374 53.4423 141.261 53.4359 143.641 54.8103L162.647 65.7834C165.028 67.1578 165.017 69.4018 162.607 70.7929L127.47 91.0792C125.061 92.4703 121.174 92.4767 118.793 91.1023L99.7876 80.1292C97.407 78.7548 97.4181 76.5108 99.8276 75.1197C101.39 74.2177 101.39 72.7553 99.8276 71.8534L95.0961 69.1216C87.0434 64.4724 87.0933 56.8681 95.2225 52.1747C103.352 47.4812 116.523 47.4524 124.576 52.1017L129.307 54.8334ZM117.396 64.9765C121.567 62.5686 121.636 58.6255 117.456 56.2122C113.283 53.8032 106.474 53.8227 102.276 56.2468C98.077 58.6708 98.0433 62.6022 102.216 65.0111C106.396 67.4245 113.225 67.3845 117.396 64.9765Z"
stroke="url(#paint12_linear_2155_6283)" />
</g>
<g filter="url(#filter6_bi_2155_6283)">
<path
d="M134.531 52.2883C133.208 53.0522 131.063 53.0522 129.74 52.2883L125.009 49.5566C116.713 44.7673 103.154 44.8001 94.7895 49.6296C86.4245 54.4591 86.3678 62.2873 94.6631 67.0766L99.3946 69.8083C100.718 70.5722 100.718 71.8107 99.3946 72.5746C96.7493 74.1019 96.7314 76.5697 99.3546 78.0842L118.36 89.0572C120.984 90.5717 125.258 90.5614 127.903 89.0342L163.04 68.7479C165.685 67.2207 165.703 64.7528 163.08 63.2383L144.074 52.2653C141.451 50.7508 137.177 50.7611 134.531 52.2883ZM117.023 54.1671C120.959 56.4397 120.899 60.1589 116.963 62.4315C113.027 64.7041 106.585 64.7387 102.649 62.4661C98.7189 60.1972 98.7457 56.4897 102.709 54.2017C106.671 51.9137 113.093 51.8982 117.023 54.1671Z"
fill="url(#paint13_linear_2155_6283)" />
<path
d="M129.307 52.5383C130.869 53.4403 133.402 53.4403 134.964 52.5383C137.374 51.1472 141.261 51.1408 143.641 52.5153L162.647 63.4883C165.028 64.8628 165.017 67.1068 162.607 68.4979L127.47 88.7842C125.061 90.1753 121.174 90.1817 118.793 88.8072L99.7876 77.8342C97.407 76.4597 97.4181 74.2158 99.8276 72.8246C101.39 71.9227 101.39 70.4603 99.8276 69.5583L95.0961 66.8266C87.0434 62.1773 87.0933 54.573 95.2225 49.8796C103.352 45.1862 116.523 45.1574 124.576 49.8066L129.307 52.5383ZM117.396 62.6815C121.567 60.2736 121.636 56.3305 117.456 53.9171C113.283 51.5082 106.474 51.5276 102.276 53.9517C98.077 56.3758 98.0433 60.3071 102.216 62.7161C106.396 65.1294 113.225 65.0894 117.396 62.6815Z"
stroke="url(#paint14_linear_2155_6283)" />
</g>
<g filter="url(#filter7_bi_2155_6283)">
<path
d="M134.531 49.9934C133.208 50.7573 131.063 50.7573 129.74 49.9934L125.009 47.2617C116.713 42.4724 103.154 42.5052 94.7895 47.3347C86.4245 52.1642 86.3678 59.9924 94.6631 64.7817L99.3946 67.5134C100.718 68.2773 100.718 69.5158 99.3946 70.2797C96.7493 71.8069 96.7314 74.2748 99.3546 75.7893L118.36 86.7623C120.984 88.2768 125.258 88.2665 127.903 86.7392L163.04 66.453C165.685 64.9257 165.703 62.4579 163.08 60.9434L144.074 49.9704C141.451 48.4559 137.177 48.4662 134.531 49.9934ZM117.023 51.8722C120.959 54.1448 120.899 57.864 116.963 60.1366C113.027 62.4091 106.585 62.4437 102.649 60.1711C98.7189 57.9022 98.7457 54.1948 102.709 51.9068C106.671 49.6188 113.093 49.6033 117.023 51.8722Z"
fill="url(#paint15_linear_2155_6283)" />
<path
d="M129.307 50.2434C130.869 51.1454 133.402 51.1454 134.964 50.2434C137.374 48.8523 141.261 48.8459 143.641 50.2204L162.647 61.1934C165.028 62.5679 165.017 64.8118 162.607 66.203L127.47 86.4892C125.061 87.8804 121.174 87.8868 118.793 86.5123L99.7876 75.5393C97.407 74.1648 97.4181 71.9208 99.8276 70.5297C101.39 69.6277 101.39 68.1654 99.8276 67.2634L95.0961 64.5317C87.0434 59.8824 87.0933 52.2781 95.2225 47.5847C103.352 42.8913 116.523 42.8625 124.576 47.5117L129.307 50.2434ZM117.396 60.3866C121.567 57.9786 121.636 54.0356 117.456 51.6222C113.283 49.2133 106.474 49.2327 102.276 51.6568C98.077 54.0809 98.0433 58.0122 102.216 60.4211C106.396 62.8345 113.225 62.7945 117.396 60.3866Z"
stroke="url(#paint16_linear_2155_6283)" />
</g>
</g>
<defs>
<filter id="filter0_b_2155_6283" x="41.8096" y="73.5168" width="165.098" height="98.7002"
filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feGaussianBlur in="BackgroundImageFix" stdDeviation="2" />
<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_2155_6283" />
<feBlend mode="normal" in="SourceGraphic" in2="effect1_backgroundBlur_2155_6283"
result="shape" />
</filter>
<filter id="filter1_b_2155_6283" x="21.9346" y="36.7966" width="204.848" height="121.65"
filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feGaussianBlur in="BackgroundImageFix" stdDeviation="2" />
<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_2155_6283" />
<feBlend mode="normal" in="SourceGraphic" in2="effect1_backgroundBlur_2155_6283"
result="shape" />
</filter>
<filter id="filter2_b_2155_6283" x="1.20996" y="-1.07104" width="246.297" height="145.58"
filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feGaussianBlur in="BackgroundImageFix" stdDeviation="2" />
<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_2155_6283" />
<feBlend mode="normal" in="SourceGraphic" in2="effect1_backgroundBlur_2155_6283"
result="shape" />
</filter>
<filter id="filter3_bi_2155_6283" x="56.7529" y="26.8982" width="136.648" height="87.3469"
filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feGaussianBlur in="BackgroundImageFix" stdDeviation="5" />
<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_2155_6283" />
<feBlend mode="normal" in="SourceGraphic" in2="effect1_backgroundBlur_2155_6283"
result="shape" />
<feColorMatrix in="SourceAlpha" type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" />
<feMorphology radius="2" operator="erode" in="SourceAlpha"
result="effect2_innerShadow_2155_6283" />
<feOffset />
<feGaussianBlur stdDeviation="4.3" />
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" />
<feColorMatrix type="matrix" values="0 0 0 0 0.688818 0 0 0 0 0.743733 0 0 0 0 1 0 0 0 0.1 0" />
<feBlend mode="normal" in2="shape" result="effect2_innerShadow_2155_6283" />
</filter>
<filter id="filter4_d_2155_6283" x="86.2785" y="42.491" width="80.9576" height="53.1904"
filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feColorMatrix in="SourceAlpha" type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" />
<feOffset dy="1" />
<feGaussianBlur stdDeviation="1.1" />
<feComposite in2="hardAlpha" operator="out" />
<feColorMatrix type="matrix"
values="0 0 0 0 0.109804 0 0 0 0 0.117647 0 0 0 0 0.14902 0 0 0 0.7 0" />
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_2155_6283" />
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_2155_6283" result="shape" />
</filter>
<filter id="filter5_bi_2155_6283" x="84.4785" y="44.281" width="84.5576" height="52.2004"
filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feGaussianBlur in="BackgroundImageFix" stdDeviation="2" />
<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_2155_6283" />
<feBlend mode="normal" in="SourceGraphic" in2="effect1_backgroundBlur_2155_6283"
result="shape" />
<feColorMatrix in="SourceAlpha" type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" />
<feOffset dy="3" />
<feGaussianBlur stdDeviation="2" />
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" />
<feColorMatrix type="matrix"
values="0 0 0 0 0.222205 0 0 0 0 0.238795 0 0 0 0 0.321745 0 0 0 1 0" />
<feBlend mode="normal" in2="shape" result="effect2_innerShadow_2155_6283" />
</filter>
<filter id="filter6_bi_2155_6283" x="84.4785" y="41.986" width="84.5576" height="52.2004"
filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feGaussianBlur in="BackgroundImageFix" stdDeviation="2" />
<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_2155_6283" />
<feBlend mode="normal" in="SourceGraphic" in2="effect1_backgroundBlur_2155_6283"
result="shape" />
<feColorMatrix in="SourceAlpha" type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" />
<feOffset dy="3" />
<feGaussianBlur stdDeviation="2" />
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" />
<feColorMatrix type="matrix"
values="0 0 0 0 0.222205 0 0 0 0 0.238795 0 0 0 0 0.321745 0 0 0 1 0" />
<feBlend mode="normal" in2="shape" result="effect2_innerShadow_2155_6283" />
</filter>
<filter id="filter7_bi_2155_6283" x="84.4785" y="39.691" width="84.5576" height="52.2004"
filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feGaussianBlur in="BackgroundImageFix" stdDeviation="2" />
<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_2155_6283" />
<feBlend mode="normal" in="SourceGraphic" in2="effect1_backgroundBlur_2155_6283"
result="shape" />
<feColorMatrix in="SourceAlpha" type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" />
<feOffset dy="3" />
<feGaussianBlur stdDeviation="2" />
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" />
<feColorMatrix type="matrix"
values="0 0 0 0 0.222205 0 0 0 0 0.238795 0 0 0 0 0.321745 0 0 0 1 0" />
<feBlend mode="normal" in2="shape" result="effect2_innerShadow_2155_6283" />
</filter>
<linearGradient id="paint0_linear_2155_6283" x1="14.4634" y1="4.1202" x2="110.189" y2="24.3943"
gradientUnits="userSpaceOnUse">
<stop stop-color="#303341" />
<stop offset="1" stop-color="#16171D" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint1_linear_2155_6283" x1="3.74952" y1="3.97513" x2="107.317" y2="57.4938"
gradientUnits="userSpaceOnUse">
<stop stop-color="#656984" />
<stop offset="1" stop-color="#18191F" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint2_linear_2155_6283" x1="17.901" y1="5.09949" x2="136.379" y2="30.1924"
gradientUnits="userSpaceOnUse">
<stop stop-color="#6368F3" stop-opacity="0.4" />
<stop offset="1" stop-color="#16171D" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint3_linear_2155_6283" x1="4.64072" y1="4.91994" x2="132.824" y2="71.159"
gradientUnits="userSpaceOnUse">
<stop stop-color="#6368F3" />
<stop offset="1" stop-color="#18191F" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint4_linear_2155_6283" x1="21.4855" y1="6.1206" x2="163.687" y2="36.2381"
gradientUnits="userSpaceOnUse">
<stop stop-color="#32364B" />
<stop offset="1" stop-color="#16171D" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint5_linear_2155_6283" x1="5.56996" y1="5.9051" x2="159.421" y2="85.4077"
gradientUnits="userSpaceOnUse">
<stop stop-color="#656984" />
<stop offset="1" stop-color="#18191F" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint6_linear_2155_6283" x1="124" y1="3" x2="140.751" y2="19.8118"
gradientUnits="userSpaceOnUse">
<stop stop-color="#303446" stop-opacity="0" />
<stop offset="0.955" stop-color="#6469F4" />
<stop offset="1" stop-color="#313442" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint7_linear_2155_6283" x1="124.5" y1="147.5" x2="126" y2="105"
gradientUnits="userSpaceOnUse">
<stop stop-color="#303446" stop-opacity="0" />
<stop offset="0.955" stop-color="#6469F4" />
<stop offset="1" stop-color="#313442" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint8_linear_2155_6283" x1="5.50001" y1="71" x2="18.9215" y2="45.4658"
gradientUnits="userSpaceOnUse">
<stop stop-color="#303446" stop-opacity="0" />
<stop offset="0.955" stop-color="#6469F4" />
<stop offset="1" stop-color="#313442" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint9_linear_2155_6283" x1="243.5" y1="71" x2="230.197" y2="95.8082"
gradientUnits="userSpaceOnUse">
<stop stop-color="#303446" stop-opacity="0" />
<stop offset="0.955" stop-color="#6469F4" />
<stop offset="1" stop-color="#313442" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint10_linear_2155_6283" x1="59.3143" y1="11.171" x2="16.9095" y2="55.3262"
gradientUnits="userSpaceOnUse">
<stop stop-color="#6E739B" />
<stop offset="1" stop-color="#1C1E26" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint11_linear_2155_6283" x1="146.241" y1="80.7417" x2="163.118"
y2="37.5961" gradientUnits="userSpaceOnUse">
<stop stop-color="#373A75" />
<stop offset="1" stop-color="#16171D" />
</linearGradient>
<linearGradient id="paint12_linear_2155_6283" x1="118.904" y1="62.5391" x2="117.203"
y2="92.1779" gradientUnits="userSpaceOnUse">
<stop stop-color="#6368F3" stop-opacity="0.3" />
<stop offset="1" stop-color="#33374B" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint13_linear_2155_6283" x1="146.241" y1="78.4467" x2="163.118" y2="35.301"
gradientUnits="userSpaceOnUse">
<stop stop-color="#373A75" />
<stop offset="1" stop-color="#16171D" />
</linearGradient>
<linearGradient id="paint14_linear_2155_6283" x1="118.904" y1="60.244" x2="117.203" y2="89.8828"
gradientUnits="userSpaceOnUse">
<stop stop-color="#6368F3" stop-opacity="0.7" />
<stop offset="1" stop-color="#33374B" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint15_linear_2155_6283" x1="146.241" y1="76.1518" x2="163.118"
y2="33.0061" gradientUnits="userSpaceOnUse">
<stop stop-color="#373A75" />
<stop offset="1" stop-color="#16171D" />
</linearGradient>
<linearGradient id="paint16_linear_2155_6283" x1="118.904" y1="57.9491" x2="117.203"
y2="87.5879" gradientUnits="userSpaceOnUse">
<stop stop-color="#6368F3" />
<stop offset="1" stop-color="#33374B" stop-opacity="0" />
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -0,0 +1,281 @@
<svg viewBox="0 0 250 250" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect opacity="0.2" x="0.433013" y="0.25" width="171.82" height="114.426" rx="9.5"
transform="matrix(0.866025 -0.5 0 1 0.0580127 86.6263)" fill="url(#paint0_linear_2155_6304)"
stroke="url(#paint1_linear_2155_6304)" />
<rect opacity="0.4" x="0.433013" y="0.25" width="171.82" height="114.426" rx="9.5"
transform="matrix(0.866025 -0.5 0 1 14.7719 89.7701)" fill="url(#paint2_linear_2155_6304)"
stroke="url(#paint3_linear_2155_6304)" />
<rect x="0.433013" y="0.25" width="183.603" height="122.296" rx="9.5"
transform="matrix(0.866025 -0.5 0 1 29.4877 92.5183)" fill="url(#paint4_linear_2155_6304)"
stroke="url(#paint5_linear_2155_6304)" />
<g opacity="0.4">
<rect x="0.433013" y="0.25" width="20.3902" height="20.3902" rx="4.5"
transform="matrix(0.866025 -0.5 0 1 42.3002 170.447)" fill="url(#paint6_linear_2155_6304)"
stroke="url(#paint7_linear_2155_6304)" />
<rect x="0.433013" y="0.25" width="20.3902" height="20.3902" rx="4.5"
transform="matrix(0.866025 -0.5 0 1 68.1479 157.97)" fill="url(#paint8_linear_2155_6304)"
stroke="url(#paint9_linear_2155_6304)" />
<rect x="0.433013" y="0.25" width="20.3902" height="20.3902" rx="4.5"
transform="matrix(0.866025 -0.5 0 1 93.9926 145.492)" fill="url(#paint10_linear_2155_6304)"
stroke="url(#paint11_linear_2155_6304)" />
<rect opacity="0.9" x="0.433013" y="0.25" width="20.3902" height="20.3902" rx="4.5"
transform="matrix(0.866025 -0.5 0 1 119.84 133.015)" fill="url(#paint12_linear_2155_6304)"
stroke="url(#paint13_linear_2155_6304)" />
<rect opacity="0.5" x="0.433013" y="0.25" width="20.3902" height="20.3902" rx="4.5"
transform="matrix(0.866025 -0.5 0 1 145.687 120.537)" fill="url(#paint14_linear_2155_6304)"
stroke="url(#paint15_linear_2155_6304)" />
<rect opacity="0.5" x="0.433013" y="0.25" width="20.3902" height="20.3902" rx="4.5"
transform="matrix(0.866025 -0.5 0 1 170.642 108.059)" fill="url(#paint16_linear_2155_6304)"
stroke="url(#paint17_linear_2155_6304)" />
</g>
<g opacity="0.5">
<rect x="0.433013" y="0.25" width="20.3902" height="20.3902" rx="4.5"
transform="matrix(0.866025 -0.5 0 1 43.1928 135.688)" fill="url(#paint18_linear_2155_6304)"
stroke="url(#paint19_linear_2155_6304)" />
<rect x="0.433013" y="0.25" width="20.3902" height="20.3902" rx="4.5"
transform="matrix(0.866025 -0.5 0 1 69.0404 123.211)" fill="url(#paint20_linear_2155_6304)"
stroke="url(#paint21_linear_2155_6304)" />
<rect x="0.433013" y="0.25" width="20.3902" height="20.3902" rx="4.5"
transform="matrix(0.866025 -0.5 0 1 94.8852 110.733)" fill="url(#paint22_linear_2155_6304)"
stroke="url(#paint23_linear_2155_6304)" />
<rect x="0.433013" y="0.25" width="20.3902" height="20.3902" rx="4.5"
transform="matrix(0.866025 -0.5 0 1 120.733 98.2554)" fill="url(#paint24_linear_2155_6304)"
stroke="url(#paint25_linear_2155_6304)" />
<rect opacity="0.5" x="0.433013" y="0.25" width="20.3902" height="20.3902" rx="4.5"
transform="matrix(0.866025 -0.5 0 1 146.579 85.7778)" fill="url(#paint26_linear_2155_6304)"
stroke="url(#paint27_linear_2155_6304)" />
<rect opacity="0.5" x="0.433013" y="0.25" width="20.3902" height="20.3902" rx="4.5"
transform="matrix(0.866025 -0.5 0 1 171.535 73.3)" fill="url(#paint28_linear_2155_6304)"
stroke="url(#paint29_linear_2155_6304)" />
</g>
<rect x="0.433013" y="0.25" width="20.3902" height="20.3902" rx="4.5"
transform="matrix(0.866025 -0.5 0 1 43.1928 104.494)" fill="url(#paint30_linear_2155_6304)"
stroke="url(#paint31_linear_2155_6304)" />
<rect x="0.433013" y="0.25" width="20.3902" height="20.3902" rx="4.5"
transform="matrix(0.866025 -0.5 0 1 69.0404 92.2927)" fill="url(#paint32_linear_2155_6304)"
stroke="url(#paint33_linear_2155_6304)" />
<rect opacity="0.8" x="0.433013" y="0.25" width="22.7464" height="22.7464" rx="4.5"
transform="matrix(0.866025 -0.5 0 1 94.8861 79.539)" fill="url(#paint34_linear_2155_6304)"
stroke="url(#paint35_linear_2155_6304)" />
<rect opacity="0.7" x="0.433013" y="0.25" width="23.6231" height="23.6231" rx="4.5"
transform="matrix(0.866025 -0.5 0 1 120.734 67.0614)" fill="url(#paint36_linear_2155_6304)"
stroke="url(#paint37_linear_2155_6304)" />
<rect opacity="0.5" x="0.433013" y="0.25" width="21.8787" height="21.8787" rx="4.5"
transform="matrix(0.866025 -0.5 0 1 146.579 54.5837)" fill="url(#paint38_linear_2155_6304)"
stroke="url(#paint39_linear_2155_6304)" />
<rect opacity="0.5" x="0.433013" y="0.25" width="20.3902" height="20.3902" rx="4.5"
transform="matrix(0.866025 -0.5 0 1 171.535 42.1059)" fill="url(#paint40_linear_2155_6304)"
stroke="url(#paint41_linear_2155_6304)" />
<defs>
<linearGradient id="paint0_linear_2155_6304" x1="2.55084" y1="4.14512" x2="164.848" y2="115.426"
gradientUnits="userSpaceOnUse">
<stop stop-color="#3E4255" />
<stop offset="1" stop-color="#16171D" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint1_linear_2155_6304" x1="3.50741" y1="5.7394" x2="111.599" y2="73.9745"
gradientUnits="userSpaceOnUse">
<stop stop-color="#5C6285" />
<stop offset="1" stop-color="#18191F" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint2_linear_2155_6304" x1="2.55084" y1="4.14512" x2="164.848" y2="115.426"
gradientUnits="userSpaceOnUse">
<stop stop-color="#3E4255" />
<stop offset="1" stop-color="#16171D" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint3_linear_2155_6304" x1="3.50741" y1="5.7394" x2="111.599" y2="73.9745"
gradientUnits="userSpaceOnUse">
<stop stop-color="#5C6285" />
<stop offset="1" stop-color="#18191F" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint4_linear_2155_6304" x1="27.6515" y1="5.26111" x2="201.4" y2="60.358"
gradientUnits="userSpaceOnUse">
<stop stop-color="#4A2B4E" />
<stop offset="1" stop-color="#16171D" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint5_linear_2155_6304" x1="3.74655" y1="6.13072" x2="119.209" y2="79.0182"
gradientUnits="userSpaceOnUse">
<stop stop-color="#DF63F3" />
<stop offset="1" stop-color="#18191F" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint6_linear_2155_6304" x1="0.315723" y1="0.768158" x2="24.7278"
y2="11.9477" gradientUnits="userSpaceOnUse">
<stop stop-color="#7A366B" />
<stop offset="1" stop-color="#16171D" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint7_linear_2155_6304" x1="0.434119" y1="1.0636" x2="24.1699" y2="6.85666"
gradientUnits="userSpaceOnUse">
<stop stop-color="#DF63F3" />
<stop offset="1" stop-color="#18191F" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint8_linear_2155_6304" x1="0.315723" y1="0.768158" x2="24.7278"
y2="11.9477" gradientUnits="userSpaceOnUse">
<stop stop-color="#7A366B" />
<stop offset="1" stop-color="#16171D" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint9_linear_2155_6304" x1="0.434119" y1="1.0636" x2="24.1699" y2="6.85666"
gradientUnits="userSpaceOnUse">
<stop stop-color="#DF63F3" />
<stop offset="1" stop-color="#18191F" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint10_linear_2155_6304" x1="0.315723" y1="0.768158" x2="24.7278"
y2="11.9477" gradientUnits="userSpaceOnUse">
<stop stop-color="#7A366B" />
<stop offset="1" stop-color="#16171D" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint11_linear_2155_6304" x1="0.434119" y1="1.0636" x2="24.1699"
y2="6.85666" gradientUnits="userSpaceOnUse">
<stop stop-color="#DF63F3" />
<stop offset="1" stop-color="#18191F" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint12_linear_2155_6304" x1="0.315723" y1="0.768158" x2="24.7278"
y2="11.9477" gradientUnits="userSpaceOnUse">
<stop stop-color="#7A366B" />
<stop offset="1" stop-color="#16171D" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint13_linear_2155_6304" x1="0.434119" y1="1.0636" x2="24.1699"
y2="6.85666" gradientUnits="userSpaceOnUse">
<stop stop-color="#DF63F3" />
<stop offset="1" stop-color="#18191F" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint14_linear_2155_6304" x1="0.315723" y1="0.768158" x2="24.7278"
y2="11.9477" gradientUnits="userSpaceOnUse">
<stop stop-color="#7A366B" />
<stop offset="1" stop-color="#16171D" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint15_linear_2155_6304" x1="0.434119" y1="1.0636" x2="24.1699"
y2="6.85666" gradientUnits="userSpaceOnUse">
<stop stop-color="#DF63F3" />
<stop offset="1" stop-color="#18191F" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint16_linear_2155_6304" x1="0.315723" y1="0.768158" x2="24.7278"
y2="11.9477" gradientUnits="userSpaceOnUse">
<stop stop-color="#7A366B" />
<stop offset="1" stop-color="#16171D" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint17_linear_2155_6304" x1="0.434119" y1="1.0636" x2="24.1699"
y2="6.85666" gradientUnits="userSpaceOnUse">
<stop stop-color="#DF63F3" />
<stop offset="1" stop-color="#18191F" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint18_linear_2155_6304" x1="0.315723" y1="0.768158" x2="24.7278"
y2="11.9477" gradientUnits="userSpaceOnUse">
<stop stop-color="#7A366B" />
<stop offset="1" stop-color="#16171D" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint19_linear_2155_6304" x1="0.434119" y1="1.0636" x2="24.1699"
y2="6.85666" gradientUnits="userSpaceOnUse">
<stop stop-color="#DF63F3" />
<stop offset="1" stop-color="#18191F" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint20_linear_2155_6304" x1="0.315723" y1="0.768158" x2="24.7278"
y2="11.9477" gradientUnits="userSpaceOnUse">
<stop stop-color="#7A366B" />
<stop offset="1" stop-color="#16171D" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint21_linear_2155_6304" x1="0.434119" y1="1.0636" x2="24.1699"
y2="6.85666" gradientUnits="userSpaceOnUse">
<stop stop-color="#DF63F3" />
<stop offset="1" stop-color="#18191F" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint22_linear_2155_6304" x1="0.315723" y1="0.768158" x2="24.7278"
y2="11.9477" gradientUnits="userSpaceOnUse">
<stop stop-color="#7A366B" />
<stop offset="1" stop-color="#16171D" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint23_linear_2155_6304" x1="0.434119" y1="1.0636" x2="24.1699"
y2="6.85666" gradientUnits="userSpaceOnUse">
<stop stop-color="#DF63F3" />
<stop offset="1" stop-color="#18191F" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint24_linear_2155_6304" x1="0.315723" y1="0.768158" x2="24.7278"
y2="11.9477" gradientUnits="userSpaceOnUse">
<stop stop-color="#7A366B" />
<stop offset="1" stop-color="#16171D" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint25_linear_2155_6304" x1="0.434119" y1="1.0636" x2="24.1699"
y2="6.85666" gradientUnits="userSpaceOnUse">
<stop stop-color="#DF63F3" />
<stop offset="1" stop-color="#18191F" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint26_linear_2155_6304" x1="0.315723" y1="0.768158" x2="24.7278"
y2="11.9477" gradientUnits="userSpaceOnUse">
<stop stop-color="#7A366B" />
<stop offset="1" stop-color="#16171D" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint27_linear_2155_6304" x1="0.434119" y1="1.0636" x2="24.1699"
y2="6.85666" gradientUnits="userSpaceOnUse">
<stop stop-color="#DF63F3" />
<stop offset="1" stop-color="#18191F" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint28_linear_2155_6304" x1="0.315723" y1="0.768158" x2="24.7278"
y2="11.9477" gradientUnits="userSpaceOnUse">
<stop stop-color="#7A366B" />
<stop offset="1" stop-color="#16171D" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint29_linear_2155_6304" x1="0.434119" y1="1.0636" x2="24.1699"
y2="6.85666" gradientUnits="userSpaceOnUse">
<stop stop-color="#DF63F3" />
<stop offset="1" stop-color="#18191F" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint30_linear_2155_6304" x1="0.315723" y1="0.768158" x2="24.7278"
y2="11.9477" gradientUnits="userSpaceOnUse">
<stop stop-color="#7A366B" />
<stop offset="1" stop-color="#16171D" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint31_linear_2155_6304" x1="0.434119" y1="1.0636" x2="24.1699"
y2="6.85666" gradientUnits="userSpaceOnUse">
<stop stop-color="#DF63F3" />
<stop offset="1" stop-color="#18191F" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint32_linear_2155_6304" x1="0.315723" y1="0.768158" x2="24.7278"
y2="11.9477" gradientUnits="userSpaceOnUse">
<stop stop-color="#7A366B" />
<stop offset="1" stop-color="#16171D" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint33_linear_2155_6304" x1="0.434119" y1="1.0636" x2="24.1699"
y2="6.85666" gradientUnits="userSpaceOnUse">
<stop stop-color="#DF63F3" />
<stop offset="1" stop-color="#18191F" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint34_linear_2155_6304" x1="0.3505" y1="0.852771" x2="27.4516"
y2="13.2637" gradientUnits="userSpaceOnUse">
<stop stop-color="#7A366B" />
<stop offset="1" stop-color="#16171D" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint35_linear_2155_6304" x1="0.481938" y1="1.18076" x2="26.8323"
y2="7.61192" gradientUnits="userSpaceOnUse">
<stop stop-color="#DF63F3" />
<stop offset="1" stop-color="#18191F" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint36_linear_2155_6304" x1="0.36344" y1="0.884255" x2="28.4651"
y2="13.7534" gradientUnits="userSpaceOnUse">
<stop stop-color="#7A366B" />
<stop offset="1" stop-color="#16171D" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint37_linear_2155_6304" x1="0.499731" y1="1.22435" x2="27.8229"
y2="7.89295" gradientUnits="userSpaceOnUse">
<stop stop-color="#DF63F3" />
<stop offset="1" stop-color="#18191F" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint38_linear_2155_6304" x1="0.337693" y1="0.821612" x2="26.4486"
y2="12.7791" gradientUnits="userSpaceOnUse">
<stop stop-color="#7A366B" />
<stop offset="1" stop-color="#16171D" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint39_linear_2155_6304" x1="0.464329" y1="1.13762" x2="25.8519"
y2="7.3338" gradientUnits="userSpaceOnUse">
<stop stop-color="#DF63F3" />
<stop offset="1" stop-color="#18191F" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint40_linear_2155_6304" x1="0.315723" y1="0.768158" x2="24.7278"
y2="11.9477" gradientUnits="userSpaceOnUse">
<stop stop-color="#7A366B" />
<stop offset="1" stop-color="#16171D" stop-opacity="0" />
</linearGradient>
<linearGradient id="paint41_linear_2155_6304" x1="0.434119" y1="1.0636" x2="24.1699"
y2="6.85666" gradientUnits="userSpaceOnUse">
<stop stop-color="#DF63F3" />
<stop offset="1" stop-color="#18191F" stop-opacity="0" />
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 26 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 619 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

View File

@@ -1,29 +0,0 @@
import Image from 'next/image';
import React from 'react';
const AccessData = () => {
return (
<div className="my-[150px] md:my-[300px]">
<Image
width={390}
height={300}
quality={100}
className="mx-auto mb-10"
alt="data globe"
src="/images/misc/globe.webp"
/>
<div className="relative">
<h1 className="bg-gradient-to-r from-white to-violet-400 bg-clip-text text-center text-[25px] font-bold text-transparent md:text-[30px]">
Access data from anywhere
</h1>
<p className="mx-auto w-full max-w-[800px] text-center text-sm text-ink-faint md:text-lg">
users can enjoy the freedom of accessing their important files, documents, and
media assets from any device with an internet connection, ensuring productivity
and convenience on the go.
</p>
</div>
</div>
);
};
export default AccessData;

View File

@@ -1,212 +0,0 @@
import dynamic from 'next/dynamic';
import Image from 'next/image';
import Link from 'next/link';
import { useMemo } from 'react';
import { Button, tw } from '@sd/ui';
import { useWindowSize } from '~/hooks/useWindowSize';
import { MagicCard, MagicContainer } from './MagicCard';
import PlatformsArt from './PlatformsArt';
import SpacedropArt from './SpacedropArt';
const Heading = tw.h1`z-30 text-center font-semibold leading-tight text-white text-lg`;
const Text = tw.p`leading-2 text-zinc-500 z-30 mb-8 mt-1 max-w-4xl text-center text-[14px] lg:leading-8"`;
interface Props {
rowSpan?: number;
colSpan?: number;
className?: string;
bgUrl?: string;
children?: React.ReactNode;
}
const BentoBox = ({ rowSpan = 1, colSpan = 1, className = '', children, bgUrl = '' }: Props) => (
<div
className="rounded-[12px] border border-white/10"
style={{
gridRow: `span ${rowSpan}`,
gridColumn: `span ${colSpan}`,
backgroundImage: `url('${bgUrl}')`,
backgroundSize: 'cover',
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat',
height: '420px'
}}
>
<MagicCard className={className}>{children}</MagicCard>
</div>
);
// const AppFrameOuter = tw.div`relative m-auto flex w-full max-w-7xl rounded-lg border border-black transition-opacity`;
// const AppFrameInner = tw.div`z-30 flex w-full rounded-lg border-t border-app-line/50 bg-app/30 backdrop-blur`;
const GitHubButton = dynamic(() => import('react-github-btn'), { ssr: false });
const BentoBoxes = () => {
const { width } = useWindowSize();
const particleCount = useMemo(() => {
if (width) {
return width > 768 ? 50 : 25;
}
return 50;
}, [width]);
return (
<MagicContainer className="flex h-fit w-full max-w-7xl auto-rows-[420px] flex-col gap-4 lg:grid lg:grid-cols-6">
<BentoBox colSpan={4} className="p-6" bgUrl="images/bento/encrypt-bg.webp">
<div className="bento-radial-gradient-fade absolute right-0 top-0 z-20 size-full" />
<div className="relative z-20">
<Heading>Encryption</Heading>
<Text className="mx-auto max-w-[417px]">
Your files and folders are fully encrypted through our algorithm, preventing
unauthorized access and guaranteed protection.
</Text>
</div>
<div className="flex h-4/5 w-auto items-start justify-center">
<Image
className="mx-auto"
alt="Encryption"
loading="lazy"
width={200}
height={300}
quality={100}
src="/images/bento/lock.webp"
/>
</div>
</BentoBox>
<BentoBox colSpan={2} className="p-6">
<div className="flex h-3/4 w-auto items-center justify-center">
<Image
className="mx-auto mt-3 brightness-125"
alt="Powerful tags"
width={300}
quality={100}
loading="lazy"
height={100}
src="/images/bento/tags.webp"
/>
</div>
<div className="bento-radial-gradient-fade absolute right-0 top-0 z-20 size-full" />
<div className="relative z-40 mt-2 md:mt-7">
<Heading>Powerful tags</Heading>
<Text>
Create and apply tags to your files and folders, and instantly locate
desired content through filterable tags.
</Text>
</div>
</BentoBox>
<BentoBox colSpan={2} className="p-6">
<div className="relative z-30">
<Heading>Search everything</Heading>
<Text className="mx-auto max-w-[417px]">
Easily find your files and folders through our search
</Text>
</div>
<div className="bento-radial-gradient-fade absolute right-0 top-0 z-20 size-full" />
<div className="flex h-4/5 w-auto items-start justify-center">
<Image
className="mx-auto brightness-110"
alt="Search"
width={340}
loading="lazy"
height={300}
quality={100}
src="/images/bento/search.webp"
/>
</div>
</BentoBox>
<BentoBox colSpan={2} className="p-6">
<div className="bento-radial-gradient-fade absolute right-0 top-0 z-20 size-full" />
<div className="flex h-4/5 w-auto items-center justify-center">
<Image
className="mx-auto brightness-125"
alt="Library"
width={340}
height={300}
loading="lazy"
quality={100}
src="/images/bento/library.webp"
/>
</div>
<div className="relative z-30 mt-[30px]">
<Heading>Full Ownership & Control</Heading>
<Text className="mx-auto">Make Spacedrive yours</Text>
</div>
</BentoBox>
<BentoBox colSpan={2} className="p-6">
<div className="relative z-30">
<Heading>Spacedrop</Heading>
<Text className="mx-auto max-w-[417px]">
Send files to other devices quickly and easily
</Text>
</div>
<div className="bento-radial-gradient-fade absolute right-0 top-0 z-20 size-full" />
<div className="flex h-4/5 w-auto items-center justify-center">
<div
style={
{
'--floatduration': '4s'
} as React.CSSProperties
}
className="floating"
>
<SpacedropArt />
</div>
</div>
</BentoBox>
<BentoBox
colSpan={3}
className="h-[354px] p-6 brightness-110 lg:h-auto"
bgUrl="images/bento/opensource-bg.webp"
>
<div className="relative z-30">
<Heading>Free & Opensource</Heading>
<Text className="mx-auto">
Developers and users can contribute with new ideas and features
</Text>
</div>
<div className="bento-radial-gradient-fade absolute right-0 top-0 z-20 h-[420px] w-full" />
<div className="absolute-center relative z-40 mt-[40px] md:mt-0">
<Link target="_blank" href="https://github.com/spacedriveapp/spacedrive">
<Button
size="lg"
className="contribute-drop-shadow mx-auto mb-4 block cursor-pointer border-0 bg-gradient-to-r from-emerald-400 to-cyan-500 text-sm text-black !transition-all !duration-200"
>
{`<>`} Contribute
</Button>
</Link>
<GitHubButton
href="https://github.com/spacedriveapp/spacedrive"
data-size="large"
data-show-count="true"
aria-label="Star spacedriveapp/spacedrive on GitHub"
>
Star
</GitHubButton>
</div>
</BentoBox>
<BentoBox colSpan={3} className="relative p-6">
<div
style={
{
'--floatduration': '4s'
} as React.CSSProperties
}
className="floating mx-auto flex h-[300px] w-full max-w-[500px]"
>
<PlatformsArt />
</div>
<div className="absolute-center h-[120px] w-[300px] bg-gradient-to-r from-fuchsia-500 from-10% to-blue-500 opacity-10 blur-[175px]" />
<div className="relative z-30">
<Heading>Cross platform</Heading>
<Text className="mx-auto max-w-[400px]">
Windows, macOS, Linux, iOS, Android, and the web. Spacedrive is everywhere.
</Text>
</div>
<div className="bento-radial-gradient-fade absolute right-0 top-0 z-20 size-full" />
</BentoBox>
</MagicContainer>
);
};
export default BentoBoxes;

View File

@@ -1,96 +0,0 @@
import clsx from 'clsx';
import { useInView } from 'framer-motion';
import Image from 'next/image';
import React, { useRef } from 'react';
import CloudStorageArt from './CloudStorageArt';
const CloudStorage = () => {
const ref = useRef<HTMLDivElement>(null);
const isInView = useInView(ref, {
amount: 0.5,
once: true
});
return (
<div
ref={ref}
className={clsx(
'relative mt-[200px] w-full max-w-[960px] opacity-0 md:mt-[250px]',
isInView && 'fade-in-heading'
)}
>
<div className="absolute-horizontal-center top-[-100px] h-[248px] w-[500px] md:top-[-55px] md:w-[960px]">
<div className="relative right-[270px] z-10 md:right-0">
<CloudStorageArt />
</div>
<Image
src="/images/cloud-providers/gradient.webp"
className="absolute inset-x-0 top-[-100px] mx-auto"
width={560}
height={200}
alt="cloud gradient"
/>
</div>
<div className="mt-[60px] flex w-full flex-col flex-wrap items-center justify-center gap-5 md:flex-row">
<CloudCard
title="Dropbox"
logoUrl="/images/cloud-providers/icons/dropbox.svg"
imageWidth={49}
/>
<CloudCard title="iCloud" logoUrl="/images/cloud-providers/icons/icloud.svg" />
<CloudCard
title="Google drive"
logoUrl="/images/cloud-providers/icons/google-drive.svg"
imageWidth={53}
/>
<div className="flex w-full flex-col justify-center gap-5 md:flex-row">
<CloudCard
imageWidth={45}
title="Mega"
logoUrl="/images/cloud-providers/icons/mega.svg"
/>
<CloudCard
title="Amazon S3"
logoUrl="/images/cloud-providers/icons/s3.svg"
imageWidth={40}
/>
</div>
</div>
<h1 className="mt-[50px] bg-gradient-to-r from-white to-blue-400 bg-clip-text text-center text-[30px] font-bold leading-10 text-transparent">
Coming soon
</h1>
<h1 className="bg-gradient-to-r from-white to-blue-300 bg-clip-text text-center text-[20px] text-transparent md:text-[40px] md:leading-[50px]">
Combine all storage locations & clouds
</h1>
</div>
);
};
interface Props {
logoUrl: string;
title: string;
imageWidth?: number;
children?: React.ReactNode;
}
const CloudCard = ({ logoUrl, title, imageWidth = 70, children }: Props) => {
return (
<div className="flex w-full flex-col justify-center rounded-md border border-[#161524] bg-[#080710]/30 py-6 text-center backdrop-blur-sm transition-all duration-200 hover:brightness-125 md:h-[165px] md:basis-[30%]">
{children}
<div className="relative z-10">
<Image
width={imageWidth}
height={100}
quality={100}
alt="cloud storage"
className="mx-auto"
src={logoUrl}
/>
<p className="mt-3 font-semibold">{title}</p>
</div>
</div>
);
};
export default CloudStorage;

View File

@@ -1,126 +0,0 @@
const CloudStorageArt = () => {
return (
<svg
width="971"
height="249"
viewBox="0 0 971 249"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M0 146.095H74.6846M74.6846 146.095V248H187.161V161.425H249.699M74.6846 146.095H119.675V20.7418H226M249.699 161.425H312.236H347V138M249.699 161.425V129.411M387.82 97.3964V74.5M387.82 97.3964H347M387.82 97.3964V112.5H449.5M516.494 112.5V168.64H625.821M516.494 112.5H449.5M516.494 112.5V94.5H456V74.5M746.846 0V37.8764H638.868V52.3055M625.821 168.64H735.148V115.433H638.868V83.8691M625.821 168.64V205.615H810.733V146.095H970V79.36H896.215L869.221 52.3055H782V83.8691H638.868M625.821 168.64V135H555.5V103M638.868 52.3055H599V20.7418H501V41.3709M638.868 52.3055V83.8691M501 41.3709V62H555.5V103M501 41.3709H456V74.5M456 74.5H387.82M387.82 74.5V44.1891H339M638.868 83.8691H593.5V103H555.5M249.699 129.411V97.3964H298.349M249.699 129.411H183V79.36H226V20.7418M226 20.7418H278.943V44.1891H339M347 97.3964H298.349M347 97.3964V138M298.349 97.3964V70H339V44.1891M449.5 112.5V138H347"
stroke="url(#paint0_linear_630_169)"
/>
<path
className="gradient-path"
d="M391.5 74.5H456V41.3333H478.5L501 41.3333V62H555.5V103H593.5V84H635.5"
stroke="url(#paint1_linear_630_169)"
/>
<g className="circle-two" filter="url(#filter0_d_630_169)">
<circle cx="388" cy="74" r="4" fill="#376FA9" className="circle-two" />
</g>
<g className="circle-one" filter="url(#filter1_d_630_169)">
<circle className="circle-one" cx="639" cy="84" r="4" fill="#376FA9" />
</g>
<defs>
<filter
id="filter0_d_630_169"
x="372"
y="58"
width="32"
height="32"
filterUnits="userSpaceOnUse"
colorInterpolationFilters="sRGB"
>
<feFlood floodOpacity="0" result="BackgroundImageFix" />
<feColorMatrix
in="SourceAlpha"
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
result="hardAlpha"
/>
<feOffset />
<feGaussianBlur stdDeviation="6" />
<feComposite in2="hardAlpha" operator="out" />
<feColorMatrix
type="matrix"
values="0 0 0 0 0.231373 0 0 0 0 0.443137 0 0 0 0 0.67451 0 0 0 1 0"
/>
<feBlend
mode="normal"
in2="BackgroundImageFix"
result="effect1_dropShadow_630_169"
/>
<feBlend
mode="normal"
in="SourceGraphic"
in2="effect1_dropShadow_630_169"
result="shape"
/>
</filter>
<filter
id="filter1_d_630_169"
x="623"
y="68"
width="32"
height="32"
filterUnits="userSpaceOnUse"
colorInterpolationFilters="sRGB"
>
<feFlood floodOpacity="0" result="BackgroundImageFix" />
<feColorMatrix
in="SourceAlpha"
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
result="hardAlpha"
/>
<feOffset />
<feGaussianBlur stdDeviation="6" />
<feComposite in2="hardAlpha" operator="out" />
<feColorMatrix
type="matrix"
values="0 0 0 0 0.235294 0 0 0 0 0.458824 0 0 0 0 0.698039 0 0 0 1 0"
/>
<feBlend
mode="normal"
in2="BackgroundImageFix"
result="effect1_dropShadow_630_169"
/>
<feBlend
mode="normal"
in="SourceGraphic"
in2="effect1_dropShadow_630_169"
result="shape"
/>
</filter>
<linearGradient
id="paint0_linear_630_169"
x1="49"
y1="80.9999"
x2="769.061"
y2="0.547463"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#343259" stopOpacity="0" />
<stop offset="0.593922" stopColor="#25273E" />
<stop offset="1" stopColor="#343259" stopOpacity="0" />
</linearGradient>
<linearGradient
id="paint1_linear_630_169"
x1="633.5"
y1="83.5"
x2="388.5"
y2="74"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#4AA8FF" stopOpacity="0" />
<stop offset="0.552083" stopColor="#4AA8FF" />
<stop offset="1" stopColor="#4AA8FF" stopOpacity="0" />
</linearGradient>
</defs>
</svg>
);
};
export default CloudStorageArt;

View File

@@ -1,53 +0,0 @@
import { useId } from 'react';
import { cn } from './MagicCard';
interface DotPatternProps {
width?: any;
height?: any;
x?: any;
y?: any;
cx?: any;
cy?: any;
cr?: any;
className?: string;
[key: string]: any;
}
export function DotPattern({
width = 16,
height = 16,
x = 0,
y = 0,
cx = 1,
cy = 1,
cr = 1,
className,
...props
}: DotPatternProps) {
const id = useId();
return (
<svg
aria-hidden="true"
className={cn('absolute inset-0 h-full w-full fill-gray-400/30', className)}
{...props}
>
<defs>
<pattern
id={id}
width={width}
height={height}
patternUnits="userSpaceOnUse"
patternContentUnits="userSpaceOnUse"
x={x}
y={y}
>
<circle id="pattern-circle" cx={cy} cy={cy} r={cr} />
</pattern>
</defs>
<rect width="100%" height="100%" strokeWidth={0} fill={`url(#${id})`} />
</svg>
);
}
export default DotPattern;

View File

@@ -1,39 +0,0 @@
import { Download } from '@phosphor-icons/react/dist/ssr';
import clsx from 'clsx';
import { useInView } from 'framer-motion';
import React, { useRef } from 'react';
import { Button } from '@sd/ui';
interface Props {
isWindows?: boolean;
}
const DownloadToday = ({ isWindows }: Props) => {
const ref = useRef<HTMLDivElement>(null);
const isInView = useInView(ref, {
amount: 0.5,
once: true
});
return (
<div
ref={ref}
className={clsx(
'relative mb-[150px] mt-10 flex h-[250px] w-full max-w-7xl flex-col justify-center bg-app-box/30 opacity-0',
'overflow-hidden rounded-md p-2 text-center md:mb-[250px] md:h-[350px]',
isInView && 'fade-in-heading'
)}
>
<div className="relative z-10">
<h1 className="mx-auto w-full max-w-[500px] text-[20px] font-semibold leading-tight md:text-[30px]">
Ready to get organized?
</h1>
<Button className="mx-auto mt-5 flex gap-2" variant="accent" size="md">
<Download size={20} />
{isWindows ? 'Download on Windows' : 'Download on Mac'}
</Button>
</div>
</div>
);
};
export default DownloadToday;

View File

@@ -1,30 +0,0 @@
import { type IconProps } from '@phosphor-icons/react';
import clsx from 'clsx';
import { Button, LinkButtonProps } from '@sd/ui';
interface Props extends LinkButtonProps {
className?: string;
text: string;
icon?: IconProps;
onClick?: () => void;
}
export function HomeCTA({ className, text, icon, ...props }: Props) {
return (
<Button
size="lg"
className={clsx(
'home-button-border-gradient relative z-30 flex cursor-pointer items-center gap-2 !rounded-[7px] border-0 !bg-[#2F3152]/30 py-2 text-sm !backdrop-blur-lg hover:brightness-110 md:text-[16px]',
className
)}
{...props}
>
<>
{icon && icon}
{text}
</>
</Button>
);
}
export default HomeCTA;

View File

@@ -1,25 +0,0 @@
import clsx from 'clsx';
import { PropsWithChildren } from 'react';
interface MarkdownPageProps {
classNames?: string;
articleClassNames?: string;
}
function MarkdownPage(props: PropsWithChildren<MarkdownPageProps>) {
return (
<div className={clsx('mb-10 p-4', props.classNames)}>
<article
id="content"
className={clsx(
'lg:prose-xs prose text-[15px] dark:prose-invert prose-h1:text-[3.25em] prose-a:text-primary prose-a:no-underline prose-blockquote:rounded prose-blockquote:bg-gray-600 prose-code:rounded-md prose-code:bg-gray-650 prose-code:p-1 prose-code:font-normal prose-code:text-gray-400 prose-code:before:hidden prose-code:after:hidden prose-table:border-b prose-table:border-gray-500 prose-tr:even:bg-gray-700 prose-th:p-2 prose-td:border-l prose-td:border-gray-500 prose-td:p-2 prose-td:last:border-r prose-img:rounded sm:text-[16px]',
props.articleClassNames
)}
>
{props.children}
</article>
</div>
);
}
export default MarkdownPage;

View File

@@ -1,166 +0,0 @@
import { Book, Chat, DotsThreeVertical, MapPin, User } from '@phosphor-icons/react/dist/ssr';
import { Academia, Discord, Github } from '@sd/assets/svgs/brands';
import clsx from 'clsx';
import Image from 'next/image';
import Link from 'next/link';
import { NextRouter, useRouter } from 'next/router';
import { PropsWithChildren, useEffect, useState } from 'react';
import { Button, Dropdown } from '@sd/ui';
import { positions } from '~/app/careers/data';
import { getWindow } from '~/utils/util';
import Logo from '../../public/logo.png';
function NavLink(props: PropsWithChildren<{ link?: string }>) {
return (
<Link
href={props.link ?? '#'}
target={props.link?.startsWith('http') ? '_blank' : undefined}
className="cursor-pointer p-4 text-[11pt] text-gray-300 no-underline transition hover:text-gray-50"
rel="noreferrer"
>
{props.children}
</Link>
);
}
function link(path: string, router: NextRouter) {
const selected = getWindow()?.location.href.includes(path);
return {
selected,
onClick: () => router.push(path),
className: clsx(selected && 'bg-accent/20')
};
}
function redirect(href: string) {
return () => (window.location.href = href);
}
export default function NavBar() {
const [isAtTop, setIsAtTop] = useState(true);
const window = getWindow();
function onScroll() {
if ((getWindow()?.pageYOffset || 0) < 20) setIsAtTop(true);
else if (isAtTop) setIsAtTop(false);
}
const router = useRouter();
useEffect(() => {
if (!window) return;
setTimeout(onScroll, 0);
getWindow()?.addEventListener('scroll', onScroll);
return () => getWindow()?.removeEventListener('scroll', onScroll);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<div className="navbar-blur fixed z-[55] h-16 w-full !bg-black/10 px-2 transition">
<div className="relative m-auto flex h-full max-w-[100rem] items-center p-5">
<Link href="/" className="absolute flex flex-row items-center">
<Image alt="Spacedrive logo" src={Logo} className="z-30 mr-3 size-8" />
<h3 className="text-xl font-bold text-white">Spacedrive</h3>
</Link>
<div className="m-auto hidden space-x-4 text-white lg:block">
<NavLink link="/roadmap">Roadmap</NavLink>
<NavLink link="/team">Team</NavLink>
{/* <NavLink link="/pricing">Pricing</NavLink> */}
<NavLink link="/blog">Blog</NavLink>
<NavLink link="/docs/product/getting-started/introduction">Docs</NavLink>
<div className="relative inline">
<NavLink link="/careers">Careers</NavLink>
{positions.length > 0 ? (
<span className="absolute -right-2 -top-1 rounded-md bg-primary/80 px-[5px] text-xs">
{` ${positions.length} `}
</span>
) : null}
</div>
</div>
<div className="flex-1 lg:hidden" />
<Dropdown.Root
button={
<Button
aria-label="mobile-menu"
className="ml-[140px] hover:!bg-transparent"
size="icon"
>
<DotsThreeVertical weight="bold" className="size-6" />
</Button>
}
className="right-4 top-2 block h-6 w-44 text-white lg:hidden"
itemsClassName="!rounded-2xl shadow-2xl shadow-black p-2 !bg-gray-850 mt-2 !border-gray-500 text-[15px]"
>
<Dropdown.Section>
<Dropdown.Item
icon={Discord}
onClick={redirect('https://discord.gg/gTaF2Z44f5')}
>
Join Discord
</Dropdown.Item>
<Dropdown.Item
icon={Github}
onClick={redirect('https://github.com/spacedriveapp/spacedrive')}
>
Repository
</Dropdown.Item>
</Dropdown.Section>
<Dropdown.Section>
<Dropdown.Item icon={MapPin} {...link('/roadmap', router)}>
Roadmap
</Dropdown.Item>
<Dropdown.Item icon={User} {...link('/team', router)}>
Team
</Dropdown.Item>
{/* <Dropdown.Item icon={Money} {...link('/pricing', router)}>
Pricing
</Dropdown.Item> */}
<Dropdown.Item icon={Chat} {...link('/blog', router)}>
Blog
</Dropdown.Item>
<Dropdown.Item
icon={Book}
{...link('/docs/product/getting-started/introduction', router)}
>
Docs
</Dropdown.Item>
<Dropdown.Item icon={Academia} {...link('/careers', router)}>
Careers
{positions.length > 0 ? (
<span className="ml-2 rounded-md bg-primary px-[5px] py-px text-xs">
{positions.length}
</span>
) : null}
</Dropdown.Item>
</Dropdown.Section>
</Dropdown.Root>
<div className="absolute right-3 hidden flex-row space-x-5 lg:flex">
<Link
aria-label="discord"
href="https://discord.gg/gTaF2Z44f5"
target="_blank"
rel="noreferrer"
>
<Discord className="size-6 text-white opacity-100 duration-300 hover:opacity-50" />
</Link>
<Link
aria-label="github"
href="https://github.com/spacedriveapp/spacedrive"
target="_blank"
rel="noreferrer"
>
<Github className="size-6 text-white opacity-100 duration-300 hover:opacity-50" />
</Link>
</div>
</div>
<div className="absolute bottom-0 flex h-1 w-full flex-row items-center justify-center pt-4 opacity-100">
<div className="h-px w-1/2 bg-gradient-to-r from-transparent to-white/10"></div>
<div className="h-px w-1/2 bg-gradient-to-l from-transparent to-white/10"></div>
</div>
</div>
);
}

View File

@@ -1,38 +0,0 @@
import { Newspaper } from '@phosphor-icons/react/dist/ssr';
import clsx from 'clsx';
import Link from 'next/link';
export interface NewBannerProps {
headline: string;
href: string;
link: string;
className?: string;
}
const NewBanner: React.FC<NewBannerProps> = (props) => {
const { headline, href, link } = props;
return (
<aside
onClick={() => (window.location.href = href)}
className={clsx(
props.className,
'news-banner-border-gradient news-banner-glow animation-delay-1 fade-in-whats-new z-10 mb-5 flex w-fit cursor-pointer flex-row rounded-full bg-black/10 px-5 py-2.5 text-xs backdrop-blur-md transition hover:bg-purple-900/20 sm:w-auto sm:text-base'
)}
>
<div className="flex items-center gap-2">
<Newspaper weight="fill" className="text-white" size={20} />
<p className="font-regular truncate text-white">{headline}</p>
</div>
<div role="separator" className="h-22 mx-4 w-px bg-zinc-700/70" />
<Link
href={href}
className="font-regular shrink-0 bg-gradient-to-r from-violet-400 to-fuchsia-400 bg-clip-text text-transparent decoration-primary-600"
>
{link} <span aria-hidden="true">&rarr;</span>
</Link>
</aside>
);
};
export default NewBanner;

View File

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More