Craft: Excelencia en Documentos Nativos
Por que Craft gano el Apple Design Award 2021: rendimiento nativo, paginas anidadas, arquitectura de bloques y publicacion con un clic. Codigo Swift incluido.
“Creemos que las herramientas para pensar deben sentirse como una extensión de tus pensamientos, no como un obstáculo.”
Craft demuestra que las apps nativas pueden ofrecer experiencias que las aplicaciones web no pueden igualar. Construida con una profunda integración de plataforma, logra la capacidad de respuesta y el nivel de pulido que hace que la toma de notas digital se sienta tan natural como el papel.
Puntos Clave
- Lo nativo supera a Electron - Un tiempo de respuesta inferior a 50ms requiere UI específica de plataforma, no wrappers web
- Páginas dentro de páginas - Cualquier bloque puede convertirse en una página, permitiendo que la estructura emerja del contenido
- Múltiples layouts, mismos datos - Listas, tarjetas, galerías y tableros ofrecen al usuario la vista adecuada para cada contexto
- Publicación web con un clic - Las páginas compartidas funcionan sin que el destinatario necesite una cuenta o descargar la app
- Apropiado para la plataforma, no idéntico - Craft en Mac se siente como Mac; Craft en iOS se siente como iOS
Por Qué Craft Importa
Craft ganó el Apple Design Award 2021 y obtiene reconocimiento constante en la App Store al negarse a comprometer el rendimiento nativo en favor de la conveniencia multiplataforma.
Logros clave: - Apps verdaderamente nativas en iOS, macOS, Windows (no Electron) - Tiempo de respuesta inferior a 50ms en todas las interacciones - Offline-first con sincronización transparente - Arquitectura basada en bloques sin el lag típico de las apps web - Páginas compartidas elegantes que funcionan sin necesidad de cuenta
Filosofía de Diseño Central
Nativo Primero, No Envuelto en Web
La decisión definitoria de Craft: construir apps nativas para cada plataforma, usando lógica de negocio compartida pero UI específica de plataforma.
ENFOQUE ELECTRON/WEB ENFOQUE NATIVO DE CRAFT
───────────────────────────────────────────────────────────────────
Código único (JavaScript) UI específica de plataforma (Swift, etc.)
Motor de renderizado web Renderizado nativo
"Consistencia" multiplataforma Comportamiento apropiado a la plataforma
~200ms de latencia de entrada ~16ms de latencia de entrada
Atajos de teclado genéricos Atajos nativos de plataforma
Selección de texto estándar web Motor de texto nativo
Idea clave: Los usuarios no quieren apps que se vean igual en todas partes—quieren apps que se sientan correctas en su plataforma.
macOS vía Catalyst (Bien Hecho)
Craft usa Mac Catalyst pero lo personaliza profundamente para que se sienta verdaderamente nativo de 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
Biblioteca de Patrones
1. Arquitectura de Contenido Basada en Bloques
Cada elemento en Craft es un bloque. Cada bloque con contenido es potencialmente una página, creando anidamiento infinito sin sobrecarga cognitiva.
Tipos de bloques:
BLOQUES DE TEXTO
├── Párrafo (predeterminado)
├── Heading 1, 2, 3
├── Cita
├── Callout (info, warning, success)
└── Código (con resaltado de sintaxis)
BLOQUES MULTIMEDIA
├── Imagen (con pie de foto)
├── Video
├── Archivo adjunto
├── Dibujo (Apple Pencil)
└── Grabación de audio
BLOQUES ESTRUCTURALES
├── Toggle (colapsable)
├── Página (documento anidado)
├── Separador
└── Tabla
Concepto de implementación:
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 }
}
Idea clave: Cuando cada bloque puede ser una página, la arquitectura de información emerge del contenido, no de una estructura predeterminada.
2. Páginas Anidadas (Páginas Dentro de Páginas)
La característica distintiva de Craft: cualquier bloque puede convertirse en una página, y las páginas se anidan infinitamente.
ESTRUCTURA DEL DOCUMENTO
───────────────────────────────────────────────────────────────────
📄 Proyecto Alpha
├── 📝 Párrafo de introducción
├── 📄 Notas de Investigación ← Esto es una página (documento anidado)
│ ├── 📝 Entrevistas con usuarios
│ ├── 📄 Entrevista: Sarah ← Otra página anidada
│ │ └── 📝 Ideas clave
│ └── 📝 Análisis competitivo
├── 📝 Resumen de cronograma
└── 📄 Notas de Reunión ← Otra página
└── 📝 Elementos de acción
Navegación: La ruta de migas de pan muestra el camino
Proyecto Alpha > Notas de Investigación > Entrevista: Sarah
Patrón de implementación en 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. Sistema de diseño de tarjetas
Craft ofrece 5 estilos visuales para páginas, haciendo que los documentos sean fáciles de escanear de un vistazo.
Estilos de tarjetas:
ESTILO 1: LISTA (Por defecto)
┌────────────────────────────────────────┐
│ 📄 Título de página │
│ El texto de vista previa aparece... │
└────────────────────────────────────────┘
ESTILO 2: TARJETA (Mediana)
┌──────────────────┐
│ ┌──────────────┐ │
│ │ [Imagen] │ │
│ └──────────────┘ │
│ Título de página │
│ Vista previa... │
└──────────────────┘
ESTILO 3: TARJETA (Grande)
┌────────────────────────────────────────┐
│ ┌────────────────────────────────────┐ │
│ │ │ │
│ │ [Imagen de portada] │ │
│ │ │ │
│ └────────────────────────────────────┘ │
│ Título de página │
│ Vista previa más larga con más... │
└────────────────────────────────────────┘
ESTILO 4: GALERÍA (Cuadrícula)
┌────────┐ ┌────────┐ ┌────────┐
│ [Img] │ │ [Img] │ │ [Img] │
│ Título │ │ Título │ │ Título │
└────────┘ └────────┘ └────────┘
ESTILO 5: TABLERO (Estilo Kanban)
│ Por hacer│ En curso │ Hecho │
├──────────┼──────────┼──────────┤
│ Tarea 1 │ Tarea 3 │ Tarea 5 │
│ Tarea 2 │ Tarea 4 │ │
Implementación:
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. Fondos de página y temas
Cada página puede tener su propia identidad visual a través de fondos y colores de acento.
struct PageAppearance {
// Opciones de fondo
enum Background {
case solid(Color)
case gradient(Gradient)
case image(ImageReference)
case paper(PaperTexture)
}
// Texturas de papel para sensación de escritura
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?
}
// Aplicado a la página
struct PageContainer: View {
let page: PageDocument
var body: some View {
ZStack {
// Capa de fondo
backgroundView(for: page.appearance.background)
// Capa de contenido
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. Páginas compartidas (publicación web)
Craft genera páginas web atractivas y responsivas a partir de documentos. No se necesita cuenta para visualizarlas.
DOCUMENTO EN CRAFT PÁGINA COMPARTIDA EN LA WEB
───────────────────────────────────────────────────────────────────
📄 Propuesta de proyecto https://www.craft.do/s/abc123
├── 📝 Resumen ejecutivo →
├── 📄 Detalles del presupuesto Diseño limpio y responsivo
├── 📝 Cronograma Tipografía preservada
└── 📄 Biografías del equipo Imágenes optimizadas
Modo oscuro compatible
No se necesita cuenta de Craft
Características principales: - Publicación con un solo clic - Dominios personalizados disponibles - Opción de protección con contraseña - Analíticas de visualizaciones - Renderizado optimizado para SEO - Responsivo en todos los dispositivos
Sistema de diseño visual
Paleta de colores
extension Color {
// Paleta distintiva de Craft
static let craftPurple = Color(hex: "#6366F1") // Acento primario
static let craftBackground = Color(hex: "#FAFAFA") // Modo claro
static let craftSurface = Color(hex: "#FFFFFF")
// Colores semánticos
static let craftSuccess = Color(hex: "#10B981")
static let craftWarning = Color(hex: "#F59E0B")
static let craftError = Color(hex: "#EF4444")
static let craftInfo = Color(hex: "#3B82F6")
// Jerarquía de texto
static let craftTextPrimary = Color(hex: "#111827")
static let craftTextSecondary = Color(hex: "#6B7280")
static let craftTextMuted = Color(hex: "#9CA3AF")
}
Tipografía
struct CraftTypography {
// Tipografía del documento
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)
// Bloques de código
static let code = Font.system(size: 14, weight: .regular, design: .monospaced)
// Alturas de línea (generosas para legibilidad)
static let bodyLineSpacing: CGFloat = 8
static let headingLineSpacing: CGFloat = 4
}
Sistema de espaciado
struct CraftSpacing {
// Espaciado entre bloques
static let blockGap: CGFloat = 4 // Entre bloques
static let sectionGap: CGFloat = 24 // Entre secciones
static let pageMargin: CGFloat = 40 // Bordes de página (escritorio)
static let mobileMargin: CGFloat = 16 // Bordes de página (móvil)
// Ancho de contenido
static let maxContentWidth: CGFloat = 720 // Lectura óptima
static let wideContentWidth: CGFloat = 960 // Tablas, galerías
}
Filosofía de animación
Transiciones de página fluidas
// La navegación entre páginas usa geometría coincidente
struct NavigationTransition: View {
@Namespace private var namespace
@State private var selectedPage: PageReference?
var body: some View {
ZStack {
// Lista de páginas
PageListView(
onSelect: { page in
withAnimation(.spring(response: 0.35, dampingFraction: 0.85)) {
selectedPage = page
}
},
namespace: namespace
)
// La página seleccionada se expande desde la posición de su tarjeta
if let page = selectedPage {
PageDetailView(page: page)
.matchedGeometryEffect(id: page.id, in: namespace)
.transition(.scale.combined(with: .opacity))
}
}
}
}
Animaciones de bloques
// Los bloques se animan al reordenarse
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)
}
}
Lecciones para nuestro trabajo
1. El rendimiento nativo vale la inversión
Los usuarios perciben la diferencia entre 16ms y 200ms de latencia. Las apps nativas ganan en sensación de uso.
2. Las páginas dentro de páginas permiten una organización orgánica
Deja que la estructura emerja del contenido. No obligues a los usuarios a decidir la jerarquía desde el principio.
3. Múltiples diseños visuales para el mismo contenido
Tarjetas, listas, galerías—los mismos datos, diferentes vistas para diferentes contextos.
4. Compartir sin fricción
La publicación web con un solo clic elimina por completo el problema de “¿cómo comparto esto?”.
5. Apropiado para la plataforma, no idéntico entre plataformas
Craft en Mac se siente como una app de Mac. Craft en iOS se siente como una app de iOS. Ese es el objetivo.
Preguntas frecuentes
¿En qué se diferencia Craft de Notion?
Craft es nativo (desarrollado específicamente para plataformas Apple), mientras que Notion está basado en web. Esto significa que Craft logra tiempos de respuesta inferiores a 50ms, funciona completamente sin conexión y se integra profundamente con las funcionalidades de iOS/macOS como Apple Pencil, Shortcuts y la búsqueda del sistema. Notion ofrece más funciones de base de datos; Craft prioriza la experiencia de escritura.
¿Puedo usar Craft sin conexión?
Sí. Craft almacena todos los documentos localmente y se sincroniza a través de iCloud cuando hay conexión. Puedes crear, editar y organizar documentos sin internet. Los cambios se sincronizan automáticamente al reconectarte.
¿Qué sucede cuando comparto una página de Craft?
Craft genera una página web responsiva en una URL de craft.do. Los destinatarios la visualizan en cualquier navegador sin necesidad de crear una cuenta ni instalar la app. La página conserva la tipografía, las imágenes y la estructura de páginas anidadas de tu documento.
¿Craft soporta Markdown?
Craft utiliza su propio formato de bloques pero exporta a Markdown. Puedes copiar contenido como Markdown o exportar documentos completos. Algunos atajos de Markdown funcionan durante la edición (como # para encabezados), pero Craft enfatiza la edición visual sobre el marcado en texto plano.
¿Cómo funcionan las páginas anidadas en Craft?
Cualquier bloque de texto puede convertirse en una página presionando el ícono de página o escribiendo /page. Las páginas anidadas aparecen en línea dentro del documento principal y pueden abrirse en pantalla completa. La navegación por breadcrumbs muestra tu ubicación en la jerarquía. Esto crea una organización natural sin imponer estructuras de carpetas desde el inicio.