design/craft
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
- Natywne bije Electron - Czas odpowiedzi poniżej 50ms wymaga UI specyficznego dla platformy, nie nakładek webowych
- Strony wewnątrz stron - Każdy blok może stać się stroną, pozwalając strukturze wyłaniać się z treści
- Wiele układów, te same dane - Listy, karty, galerie i tablice dają użytkownikom właściwy widok dla każdego kontekstu
- Publikacja w sieci jednym kliknięciem - Udostępnione strony działają bez kont odbiorców czy pobierania aplikacji
- 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.