design/craft

3 min czytania 777 słów
design/craft screenshot

Craft: Doskonałość Dokumentów z Priorytetem Natywności

„Wierzymy, że narzędzia do myślenia powinny być przedłużeniem Twoich myśli, a nie przeszkodą."

Craft dowodzi, że natywne aplikacje mogą dostarczać doświadczenia nieosiągalne dla aplikacji webowych. Zbudowany z głęboką integracją platformową, osiąga responsywność i dopracowanie, które sprawiają, że cyfrowe notowanie jest tak naturalne jak pisanie na papierze.


Kluczowe Wnioski

  1. Natywne bije Electron - Czas odpowiedzi poniżej 50ms wymaga UI specyficznego dla platformy, nie nakładek webowych
  2. Strony wewnątrz stron - Każdy blok może stać się stroną, pozwalając strukturze wyłaniać się z treści
  3. Wiele układów, te same dane - Listy, karty, galerie i tablice dają użytkownikom właściwy widok dla każdego kontekstu
  4. Publikacja w sieci jednym kliknięciem - Udostępnione strony działają bez kont odbiorców czy pobierania aplikacji
  5. Dostosowane do platformy, nie identyczne - Craft na Mac wygląda jak Mac; Craft na iOS wygląda jak iOS

Dlaczego Craft Ma Znaczenie

Craft zdobył Apple Design Award 2021 i konsekwentnie otrzymuje wyróżnienia App Store, odmawiając kompromisu między natywną wydajnością a wygodą wieloplatformowości.

Kluczowe osiągnięcia: - Prawdziwie natywne aplikacje na iOS, macOS, Windows (nie Electron) - Czas odpowiedzi poniżej 50ms dla wszystkich interakcji - Offline-first z płynną synchronizacją - Architektura blokowa bez zacinania się aplikacji webowych - Piękne strony udostępniania działające bez kont


Podstawowa Filozofia Projektowania

Priorytet Natywności, Nie Opakowanie Webowe

Definiujący wybór Craft: budowanie natywnych aplikacji dla każdej platformy, używając współdzielonej logiki biznesowej, ale UI specyficznego dla platformy.

ELECTRON/WEB APPROACH              CRAFT'S NATIVE APPROACH
───────────────────────────────────────────────────────────────────
Single codebase (JavaScript)       Platform-specific UI (Swift, etc.)
Web rendering engine               Native rendering
Cross-platform "consistency"       Platform-appropriate behavior
~200ms input latency               ~16ms input latency
Generic keyboard shortcuts         Platform-native shortcuts
Web-standard text selection        Native text engine

Kluczowy wniosek: Użytkownicy nie chcą aplikacji, które wyglądają tak samo wszędzie—chcą aplikacji, które czują się właściwie na ich platformie.

macOS przez Catalyst (Zrobione Dobrze)

Craft używa Mac Catalyst, ale mocno go dostosowuje, by czuł się prawdziwie natywnie na Mac:

// Craft's Catalyst customization approach
#if targetEnvironment(macCatalyst)
extension SceneDelegate {
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession) {
        guard let windowScene = scene as? UIWindowScene else { return }

        // Enable native Mac toolbar
        if let titlebar = windowScene.titlebar {
            titlebar.titleVisibility = .hidden
            titlebar.toolbar = createNativeToolbar()
        }

        // Mac-specific window sizing
        windowScene.sizeRestrictions?.minimumSize = CGSize(width: 800, height: 600)

        // Enable window tabs
        UIApplication.shared.connectedScenes
            .compactMap { $0 as? UIWindowScene }
            .forEach { $0.titlebar?.toolbar?.showsBaselineSeparator = false }
    }

    private func createNativeToolbar() -> NSToolbar {
        let toolbar = NSToolbar(identifier: "CraftToolbar")
        toolbar.displayMode = .iconOnly
        toolbar.delegate = self
        return toolbar
    }
}
#endif

Biblioteka Wzorców

1. Architektura Treści Oparta na Blokach

Każdy element w Craft jest blokiem. Każdy blok z treścią jest potencjalnie stroną, tworząc nieskończone zagnieżdżanie bez obciążenia poznawczego.

Typy bloków:

TEXT BLOCKS
├── Paragraph (default)
├── Heading 1, 2, 3
├── Quote
├── Callout (info, warning, success)
└── Code (with syntax highlighting)

MEDIA BLOCKS
├── Image (with caption)
├── Video
├── File attachment
├── Drawing (Apple Pencil)
└── Audio recording

STRUCTURAL BLOCKS
├── Toggle (collapsible)
├── Page (nested document)
├── Divider
└── Table

Koncepcja implementacji:

protocol CraftBlock: Identifiable {
    var id: UUID { get }
    var content: BlockContent { get set }
    var style: BlockStyle { get set }
    var children: [any CraftBlock] { get set }
    var metadata: BlockMetadata { get }
}

enum BlockContent {
    case text(AttributedString)
    case image(ImageData)
    case page(PageReference)
    case toggle(isExpanded: Bool)
    case code(language: String, content: String)
    case table(TableData)
}

struct BlockMetadata {
    let created: Date
    let modified: Date
    let createdBy: UserReference?
}

// Every block can contain other blocks
class PageBlock: CraftBlock {
    var id = UUID()
    var content: BlockContent
    var style: BlockStyle
    var children: [any CraftBlock] = []
    var metadata: BlockMetadata

    // A page is just a block that can be opened full-screen
    var canOpenAsPage: Bool { true }
}

Kluczowy wniosek: Gdy każdy blok może być stroną, architektura informacji wyłania się z treści, a nie z ustalonej z góry struktury.


2. Zagnieżdżone Strony (Strony Wewnątrz Stron)

Charakterystyczna funkcja Craft: każdy blok może stać się stroną, a strony zagnieżdżają się nieskończenie.

DOCUMENT STRUCTURE
───────────────────────────────────────────────────────────────────
📄 Project Alpha
├── 📝 Introduction paragraph
├── 📄 Research Notes         ← This is a page (nested document)
│   ├── 📝 User interviews
│   ├── 📄 Interview: Sarah   ← Another nested page
│   │   └── 📝 Key insights
│   └── 📝 Competitive analysis
├── 📝 Timeline overview
└── 📄 Meeting Notes          ← Another page
    └── 📝 Action items

Navigation: Breadcrumb trail shows path
Project Alpha > Research Notes > Interview: Sarah

Wzorzec implementacji SwiftUI:

struct PageView: View {
    @Bindable var page: PageDocument
    @State private var selectedBlock: CraftBlock?

    var body: some View {
        ScrollView {
            LazyVStack(alignment: .leading, spacing: 0) {
                ForEach(page.blocks) { block in
                    BlockView(block: block, onTap: { tapped in
                        if tapped.canOpenAsPage {
                            navigateToPage(tapped)
                        } else {
                            selectedBlock = tapped
                        }
                    })
                }
            }
        }
        .navigationTitle(page.title)
        .toolbar {
            // Breadcrumb navigation
            ToolbarItem(placement: .principal) {
                BreadcrumbView(path: page.ancestorPath)
            }
        }
    }
}

struct BreadcrumbView: View {
    let path: [PageReference]

    var body: some View {
        HStack(spacing: 4) {
            ForEach(Array(path.enumerated()), id: \.element.id) { index, page in
                if index > 0 {
                    Image(systemName: "chevron.right")
                        .font(.caption)
                        .foregroundStyle(.secondary)
                }
                Button(page.title) {
                    navigateTo(page)
                }
                .buttonStyle(.plain)
                .foregroundStyle(index == path.count - 1 ? .primary : .secondary)
            }
        }
    }
}

3. System Układu Kart

Craft oferuje 5 stylów wizualnych dla stron, czyniąc dokumenty przejrzystymi na pierwszy rzut oka.

Style kart:

STYLE 1: LIST (Default)
┌────────────────────────────────────────┐
│ 📄 Page Title                          │
│    Preview text appears here...        │
└────────────────────────────────────────┘

STYLE 2: CARD (Medium)
┌──────────────────┐
│ ┌──────────────┐ │
│ │   [Image]    │ │
│ └──────────────┘ │
│ Page Title       │
│ Preview text...  │
└──────────────────┘

STYLE 3: CARD (Large)
┌────────────────────────────────────────┐
│ ┌────────────────────────────────────┐ │
│ │                                    │ │
│ │           [Cover Image]            │ │
│ │                                    │ │
│ └────────────────────────────────────┘ │
│ Page Title                             │
│ Longer preview text with more room...  │
└────────────────────────────────────────┘

STYLE 4: GALLERY (Grid)
┌────────┐ ┌────────┐ ┌────────┐
│ [Img]  │ │ [Img]  │ │ [Img]  │
│ Title  │ │ Title  │ │ Title  │
└────────┘ └────────┘ └────────┘

STYLE 5: BOARD (Kanban-style)
│ To Do    │ Doing    │ Done     │
├──────────┼──────────┼──────────┤
│ Task 1   │ Task 3   │ Task 5   │
│ Task 2   │ Task 4   │          │

Implementacja:

enum PageLayoutStyle: String, CaseIterable {
    case list = "list"
    case cardMedium = "card_medium"
    case cardLarge = "card_large"
    case gallery = "gallery"
    case board = "board"
}

struct PageGridView: View {
    let pages: [PageReference]
    let style: PageLayoutStyle

    var body: some View {
        switch style {
        case .list:
            LazyVStack(spacing: 8) {
                ForEach(pages) { page in
                    ListRowView(page: page)
                }
            }

        case .cardMedium, .cardLarge:
            let columns = style == .cardMedium ? 3 : 2
            LazyVGrid(columns: Array(repeating: .init(.flexible()), count: columns)) {
                ForEach(pages) { page in
                    CardView(page: page, size: style == .cardLarge ? .large : .medium)
                }
            }

        case .gallery:
            LazyVGrid(columns: Array(repeating: .init(.flexible()), count: 4)) {
                ForEach(pages) { page in
                    GalleryThumbnail(page: page)
                }
            }

        case .board:
            BoardView(pages: pages)
        }
    }
}

4. Tła Stron i Motywy

Każda strona może mieć własną tożsamość wizualną poprzez tła i kolory akcentowe.

struct PageAppearance {
    // Background options
    enum Background {
        case solid(Color)
        case gradient(Gradient)
        case image(ImageReference)
        case paper(PaperTexture)
    }

    // Paper textures for writing feel
    enum PaperTexture: String {
        case none
        case lined
        case grid
        case dotted
    }

    var background: Background = .solid(.white)
    var accentColor: Color = .blue
    var iconEmoji: String?
    var coverImage: ImageReference?
}

// Applied to page
struct PageContainer: View {
    let page: PageDocument

    var body: some View {
        ZStack {
            // Background layer
            backgroundView(for: page.appearance.background)

            // Content layer
            PageContentView(page: page)
        }
    }

    @ViewBuilder
    private func backgroundView(for background: PageAppearance.Background) -> some View {
        switch background {
        case .solid(let color):
            color.ignoresSafeArea()
        case .gradient(let gradient):
            LinearGradient(gradient: gradient, startPoint: .top, endPoint: .bottom)
                .ignoresSafeArea()
        case .image(let ref):
            AsyncImage(url: ref.url) { image in
                image.resizable().scaledToFill()
            } placeholder: {
                Color.gray.opacity(0.1)
            }
            .ignoresSafeArea()
            .overlay(.ultraThinMaterial)
        case .paper(let texture):
            PaperTextureView(texture: texture)
        }
    }
}

5. Strony Udostępniania (Publikacja Webowa)

Craft generuje piękne, responsywne strony internetowe z dokumentów. Nie wymaga konta do przeglądania.

DOCUMENT IN CRAFT                 SHARE PAGE ON WEB
───────────────────────────────────────────────────────────────────
📄 Project Proposal               https://www.craft.do/s/abc123
├── 📝 Executive Summary    →
├── 📄 Budget Details             Clean, responsive layout
├── 📝 Timeline                   Typography preserved
└── 📄 Team Bios                  Images optimized
                                  Dark mode supported
                                  No Craft account needed

Kluczowe funkcje: - Publikacja jednym kliknięciem - Dostępne własne domeny - Opcja ochrony hasłem - Analityka wyświetleń - Renderowanie przyjazne SEO - Responsywność na wszystkich urządzeniach


System Projektowania Wizualnego

Paleta Kolorów

extension Color {
    // Craft's signature palette
    static let craftPurple = Color(hex: "#6366F1")  // Primary accent
    static let craftBackground = Color(hex: "#FAFAFA")  // Light mode
    static let craftSurface = Color(hex: "#FFFFFF")

    // Semantic colors
    static let craftSuccess = Color(hex: "#10B981")
    static let craftWarning = Color(hex: "#F59E0B")
    static let craftError = Color(hex: "#EF4444")
    static let craftInfo = Color(hex: "#3B82F6")

    // Text hierarchy
    static let craftTextPrimary = Color(hex: "#111827")
    static let craftTextSecondary = Color(hex: "#6B7280")
    static let craftTextMuted = Color(hex: "#9CA3AF")
}

Typografia

struct CraftTypography {
    // Document typography
    static let title = Font.system(size: 32, weight: .bold, design: .default)
    static let heading1 = Font.system(size: 28, weight: .semibold)
    static let heading2 = Font.system(size: 22, weight: .semibold)
    static let heading3 = Font.system(size: 18, weight: .medium)
    static let body = Font.system(size: 16, weight: .regular)
    static let caption = Font.system(size: 14, weight: .regular)

    // Code blocks
    static let code = Font.system(size: 14, weight: .regular, design: .monospaced)

    // Line heights (generous for readability)
    static let bodyLineSpacing: CGFloat = 8
    static let headingLineSpacing: CGFloat = 4
}

System Odstępów

struct CraftSpacing {
    // Block spacing
    static let blockGap: CGFloat = 4        // Between blocks
    static let sectionGap: CGFloat = 24     // Between sections
    static let pageMargin: CGFloat = 40     // Page edges (desktop)
    static let mobileMargin: CGFloat = 16   // Page edges (mobile)

    // Content width
    static let maxContentWidth: CGFloat = 720  // Optimal reading
    static let wideContentWidth: CGFloat = 960 // Tables, galleries
}

Filozofia Animacji

Płynne Przejścia Stron

// Navigation between pages uses matched geometry
struct NavigationTransition: View {
    @Namespace private var namespace
    @State private var selectedPage: PageReference?

    var body: some View {
        ZStack {
            // Page list
            PageListView(
                onSelect: { page in
                    withAnimation(.spring(response: 0.35, dampingFraction: 0.85)) {
                        selectedPage = page
                    }
                },
                namespace: namespace
            )

            // Selected page expands from its card position
            if let page = selectedPage {
                PageDetailView(page: page)
                    .matchedGeometryEffect(id: page.id, in: namespace)
                    .transition(.scale.combined(with: .opacity))
            }
        }
    }
}

Animacje Bloków

// Blocks animate when reordering
struct AnimatedBlockList: View {
    @Binding var blocks: [CraftBlock]

    var body: some View {
        LazyVStack(spacing: CraftSpacing.blockGap) {
            ForEach(blocks) { block in
                BlockView(block: block)
                    .transition(.asymmetric(
                        insertion: .move(edge: .top).combined(with: .opacity),
                        removal: .move(edge: .bottom).combined(with: .opacity)
                    ))
            }
        }
        .animation(.spring(response: 0.3, dampingFraction: 0.8), value: blocks)
    }
}

Lekcje dla Naszej Pracy

1. Natywna Wydajność Jest Warta Inwestycji

Użytkownicy czują różnicę między opóźnieniem 16ms a 200ms. Natywne aplikacje wygrywają odczuciem.

2. Strony Wewnątrz Stron Umożliwiają Organiczną Organizację

Pozwól strukturze wyłaniać się z treści. Nie zmuszaj użytkowników do decydowania o hierarchii z góry.

3. Wiele Układów Wizualnych dla Tej Samej Treści

Karty, listy, galerie—te same dane, różne widoki dla różnych kontekstów.

4. Udostępnianie Bez Tarcia

Publikacja webowa jednym kliknięciem całkowicie eliminuje problem „jak to udostępnić?".

5. Dostosowane do Platformy, Nie Identyczne na Platformach

Craft na Mac czuje się jak aplikacja Mac. Craft na iOS czuje się jak aplikacja iOS. To jest cel.


Najczęściej Zadawane Pytania

Czym Craft różni się od Notion?

Craft jest natywny (zbudowany specjalnie dla platform Apple), podczas gdy Notion jest oparty na webie. Oznacza to, że Craft osiąga czasy odpowiedzi poniżej 50ms, działa w pełni offline i głęboko integruje się z funkcjami iOS/macOS jak Apple Pencil, Shortcuts i wyszukiwanie systemowe. Notion oferuje więcej funkcji bazodanowych; Craft priorytetyzuje doświadczenie pisania.

Czy mogę używać Craft offline?

Tak. Craft przechowuje wszystkie dokumenty lokalnie i synchronizuje przez iCloud po połączeniu. Możesz tworzyć, edytować i organizować dokumenty bez internetu. Zmiany synchronizują się automatycznie po ponownym połączeniu.

Co się dzieje, gdy udostępniam stronę Craft?

Craft generuje responsywną stronę internetową pod adresem craft.do. Odbiorcy przeglądają ją w dowolnej przeglądarce bez tworzenia konta czy instalowania aplikacji. Strona zachowuje typografię dokumentu, obrazy i strukturę zagnieżdżonych stron.

Czy Craft obsługuje Markdown?

Craft używa własnego formatu blokowego, ale eksportuje do Markdown. Możesz kopiować treść jako Markdown lub eksportować całe dokumenty. Niektóre skróty Markdown działają podczas edycji (jak # dla nagłówków), ale Craft kładzie nacisk na edycję wizualną ponad znaczniki tekstowe.

Jak działają zagnieżdżone strony w Craft?

Dowolny blok tekstowy może stać się stroną przez naciśnięcie ikony strony lub wpisanie /page. Zagnieżdżone strony pojawiają się inline w dokumencie nadrzędnym i mogą być otwierane na pełnym ekranie. Nawigacja breadcrumb pokazuje Twoją lokalizację w hierarchii. Tworzy to naturalną organizację bez wymuszania struktur folderów z góry.