Compare commits

...
1 Commits
Author SHA1 Message Date
admin c932814166 Brand mark: scallop ribbon shape, app icon in light / dark / tinted (#11)
CI / build-and-deploy (push) Successful in 18s
2026-08-03 17:00:52 +00:00
10 changed files with 1073 additions and 0 deletions
+4
View File
@@ -17,6 +17,7 @@
1F1CB72BBCFFB33B6533D5C9 /* LibraryListRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E093C878E702891C64D21FD5 /* LibraryListRow.swift */; };
212F713DCC289C48087B79AE /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB7728D15C17219ABFF3EFFE /* Log.swift */; };
22C814FD55D29B88D227C987 /* SpotlightBookmarkSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41DDBB04346F3BF06DE233D2 /* SpotlightBookmarkSearch.swift */; };
2F80DFB7298B733965FE04F5 /* MarksMark.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EC8E834AB663A823D77BCB1 /* MarksMark.swift */; };
337E8272EEB3B10FD0868F76 /* IngestedSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DC9BBF1006495D75DE4A232 /* IngestedSource.swift */; };
3528AF5CB690BBCCF337581B /* Bookmark.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA428181B35885F7D9F4D55 /* Bookmark.swift */; };
4153FBF538C1D3F4BC96E4C5 /* LibraryKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA5C9BFD9C0DD3876CC32B3A /* LibraryKit.swift */; };
@@ -138,6 +139,7 @@
23F172EC9977CD5C51B228B9 /* MarksWidget.appex */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = "wrapper.app-extension"; path = MarksWidget.appex; sourceTree = BUILT_PRODUCTS_DIR; };
240DBB87940F8D255A812EB2 /* BookmarkActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkActions.swift; sourceTree = "<group>"; };
2526E22D8EC5453358F0FCF9 /* SpotlightRetrievalTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpotlightRetrievalTests.swift; sourceTree = "<group>"; };
3EC8E834AB663A823D77BCB1 /* MarksMark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarksMark.swift; sourceTree = "<group>"; };
41DDBB04346F3BF06DE233D2 /* SpotlightBookmarkSearch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpotlightBookmarkSearch.swift; sourceTree = "<group>"; };
47CB3AAED5B64809B06A9650 /* RecentPodcastsWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentPodcastsWidget.swift; sourceTree = "<group>"; };
49685B8F3FEC72E8CF75843E /* AnalyticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsService.swift; sourceTree = "<group>"; };
@@ -257,6 +259,7 @@
1BF2B010DADCCFBC282D37F0 /* LibraryGridView.swift */,
AA5C9BFD9C0DD3876CC32B3A /* LibraryKit.swift */,
E093C878E702891C64D21FD5 /* LibraryListRow.swift */,
3EC8E834AB663A823D77BCB1 /* MarksMark.swift */,
);
path = Library;
sourceTree = "<group>";
@@ -600,6 +603,7 @@
41F00F4E7FFC1C0ACF71E398 /* MarksApp.swift in Sources */,
E10B0B4EC9580342830EC0D2 /* MarksAppIntents.swift in Sources */,
8BD3FA025C082654D55374A2 /* MarksAuth.swift in Sources */,
2F80DFB7298B733965FE04F5 /* MarksMark.swift in Sources */,
14E1B3CE58D36BFF1A2199C1 /* OnboardingView.swift in Sources */,
773C174263D08416248D0675 /* PodcastGenerationManager.swift in Sources */,
0479C0AA16E3AFDBD4414AD0 /* PodcastIndex.swift in Sources */,
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 14 KiB

@@ -5,6 +5,30 @@
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "AppIcon-Dark.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "tinted"
}
],
"filename" : "AppIcon-Tinted.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
+166
View File
@@ -0,0 +1,166 @@
import SwiftUI
// MARK: - The mark
//
// One path, four tails.
//
// The brand mark is a single closed shape rather than a stack of separate
// ribbons, and that is the whole design decision. A logo gets tested in two
// places that have nothing to do with how it looks on a slide: at 16pt in a
// Spotlight result, and in iOS 26's tinted and monochrome icon modes, where the
// palette is thrown away and only the silhouette survives. Every arrangement of
// N separate ribbons collapses into a blob under both tests, because the shapes
// were only ever told apart by colour. A scalloped baseline is told apart by
// its outline, so it reads the same tinted, monochrome, and 16pt tall.
//
// Geometry is defined in a canonical 88 x 98 box and fitted to whatever rect it
// is handed, so the proportions can't drift with the container.
struct ScallopMark: Shape {
/// Downward points along the baseline. Four is the drawn default: three
/// reads as a plain banner, and five starts to look like bunting.
var tails: Int = 4
/// How far the notches cut up from the baseline, as a fraction of height.
/// Shallower than this and the tails read as damage rather than intent.
var notchDepth: CGFloat = 16.0 / 98.0
/// Top-corner radius, as a fraction of width.
var capRadius: CGFloat = 10.0 / 88.0
/// Canonical proportions of the drawn mark. Everything else derives from
/// this, including the app icon artwork see scripts/appicon.
static let aspectRatio: CGFloat = 88.0 / 98.0
func path(in rect: CGRect) -> Path {
// Fit the canonical box into `rect` without distorting it, so callers
// can hand this any frame and still get the drawn proportions.
let height = min(rect.height, rect.width / Self.aspectRatio)
let width = height * Self.aspectRatio
let minX = rect.midX - width / 2
let minY = rect.midY - height / 2
let maxX = minX + width
let maxY = minY + height
let radius = min(capRadius * width, min(width, height) / 2)
let depth = notchDepth * height
let points = max(2, tails)
let step = width / CGFloat(points - 1)
var path = Path()
path.move(to: CGPoint(x: minX + radius, y: minY))
// Top edge, right cap, then straight down the right side.
path.addArc(
tangent1End: CGPoint(x: maxX, y: minY),
tangent2End: CGPoint(x: maxX, y: minY + radius),
radius: radius
)
path.addLine(to: CGPoint(x: maxX, y: maxY))
// The tails, cut right to left so the winding stays consistent with
// the caps above.
for i in 1..<points {
let apex = maxX - step * (CGFloat(i) - 0.5)
path.addLine(to: CGPoint(x: apex, y: maxY - depth))
path.addLine(to: CGPoint(x: maxX - step * CGFloat(i), y: maxY))
}
// Up the left side and through the left cap. The loop above already
// left the pen on the bottom-left point.
path.addArc(
tangent1End: CGPoint(x: minX, y: minY),
tangent2End: CGPoint(x: minX + radius, y: minY),
radius: radius
)
path.closeSubpath()
return path
}
}
// MARK: - Brand colour
extension Paper {
/// The mark's bands, left to right: navy, vermilion, yellow.
///
/// These are palette swatches rather than bespoke brand colours on
/// purpose. The app already hashes every bookmark into `swatchPairs`, so a
/// mark built from three of them is made of the same ink as the content
/// which is also the only part of this design a competitor can't lift
/// without lifting the system underneath it.
static let markBands: [Color] = [swatch(5), swatch(1), swatch(0)]
}
// MARK: - The mark, coloured
/// The mark as it should normally be used. Sizes itself to the drawn aspect
/// ratio, so `.frame(height:)` is the natural way to scale it.
struct MarksMark: View {
var bands: [Color] = Paper.markBands
var tails: Int = 4
/// A single-ink version, for anywhere the bands would be noise: monochrome
/// contexts, the share sheet, a nav bar, print.
static func monochrome(_ color: Color = Paper.ink) -> MarksMark {
MarksMark(bands: [color])
}
var body: some View {
ScallopMark(tails: tails)
.fill(bandFill)
.aspectRatio(ScallopMark.aspectRatio, contentMode: .fit)
}
/// Hard-stopped, not blended: the bands are three inks laid side by side,
/// not a gradient. Doubling the stop at each boundary is what kills the
/// ramp SwiftUI would otherwise interpolate.
private var bandFill: LinearGradient {
let stops = bands.enumerated().flatMap { index, color in
[
Gradient.Stop(color: color, location: Double(index) / Double(bands.count)),
Gradient.Stop(color: color, location: Double(index + 1) / Double(bands.count)),
]
}
return LinearGradient(stops: stops, startPoint: .leading, endPoint: .trailing)
}
}
// MARK: - Previews
#Preview("Size ladder") {
VStack(spacing: 28) {
HStack(alignment: .bottom, spacing: 22) {
ForEach([128, 64, 40, 24, 16], id: \.self) { size in
MarksMark().frame(height: CGFloat(size))
}
}
// The test the shape was chosen to pass: no colour, same mark.
HStack(alignment: .bottom, spacing: 22) {
ForEach([128, 64, 40, 24, 16], id: \.self) { size in
MarksMark.monochrome().frame(height: CGFloat(size))
}
}
HStack(spacing: 22) {
ForEach(3..<6) { tails in
MarksMark(tails: tails).frame(height: 72)
}
}
}
.padding(40)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Paper.sheet)
}
#Preview("Lockup") {
HStack(spacing: 18) {
MarksMark().frame(height: 64)
VStack(alignment: .leading, spacing: 6) {
Text("Marks").font(.system(size: 46, design: .serif))
Text("EVERYTHING YOU SAVED")
.font(PaperType.micro)
.tracking(3)
.foregroundStyle(Paper.tertiary)
}
}
.foregroundStyle(Paper.ink)
.padding(40)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Paper.sheet)
}
+322
View File
@@ -0,0 +1,322 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Marks — C1 development</title>
<style>
:root{--ink:#14110C;--rule:rgba(20,17,12,.16);
--mono:ui-monospace,SFMono-Regular,Menlo,monospace;
--serif:ui-serif,Georgia,"Times New Roman",serif}
*{box-sizing:border-box}
body{margin:0;background:#EFEBE3;color:var(--ink);font-family:var(--mono);font-size:12px;-webkit-font-smoothing:antialiased}
header{padding:56px 40px 28px;border-bottom:1px solid var(--rule)}
h1{font-family:var(--serif);font-size:34px;font-weight:400;margin:0 0 8px}
header p{margin:0;max-width:66ch;line-height:1.7;opacity:.62}
section{padding:40px;border-bottom:1px solid var(--rule)}
h2{font-family:var(--serif);font-size:22px;font-weight:400;margin:0 0 4px}
.sub{opacity:.55;margin:0 0 28px;line-height:1.7;max-width:74ch}
.tag{display:inline-block;font-size:10px;letter-spacing:.14em;text-transform:uppercase;opacity:.45;margin-bottom:10px}
em{font-style:normal;border-bottom:1px solid rgba(20,17,12,.3)}
/* geometry is shared; colour is injected */
.pal {--c1:#22C7F0;--c2:#8B4DEE;--c3:#5E7CF5;--bg:#fff}
.p-orig {--c1:#22C7F0;--c2:#8B4DEE;--c3:#5E7CF5;--bg:#fff}
.p-paper{--c1:#001A55;--c2:#ED663F;--c3:#FECD00;--bg:#F8F5EF}
.p-ink {--c1:#14110C;--c2:#ED663F;--c3:#E0D1BB;--bg:#F8F5EF}
.p-tri {--c1:#FECD00;--c2:#ED663F;--c3:#115AB5;--bg:#F8F5EF}
.p-mono {--c1:#14110C;--c2:#14110C;--c3:#14110C;--bg:#F8F5EF}
.p-dark {--c1:#5B8FD0;--c2:#E07A5C;--c3:#D8AD10;--bg:#15130F}
.grid{display:flex;flex-wrap:wrap;gap:18px}
.card{background:var(--bg);border:1px solid var(--rule);border-radius:14px;padding:22px;width:243px}
.card .stage{display:flex;align-items:center;justify-content:center;height:150px}
.card svg{display:block}
.card h3{font-family:var(--serif);font-size:15px;font-weight:400;margin:14px 0 5px}
.card p{margin:0;font-size:10.5px;line-height:1.65;opacity:.62}
.card .own{margin-top:8px;font-size:9.5px;letter-spacing:.08em;text-transform:uppercase;opacity:.5}
table{border-collapse:collapse}
th{font-weight:400;font-size:10px;letter-spacing:.1em;text-transform:uppercase;opacity:.45;padding:0 0 12px;text-align:center}
th.rowh{text-align:right;padding-right:16px;text-transform:none;letter-spacing:0;font-size:11px;opacity:.7;white-space:nowrap}
td{padding:5px}
td .cell{width:92px;height:92px;border-radius:11px;background:var(--bg);border:1px solid var(--rule);display:flex;align-items:center;justify-content:center}
.ladder{display:flex;align-items:flex-end;gap:22px;background:var(--bg);border:1px solid var(--rule);border-radius:14px;padding:22px 26px;margin-bottom:12px}
.ladder .n{font-size:9.5px;opacity:.4;text-align:center;margin-top:8px}
.ladder .item{display:flex;flex-direction:column;align-items:center}
.ladder .name{font-family:var(--serif);font-size:14px;width:118px;opacity:.8}
.icons{display:flex;flex-wrap:wrap;gap:20px;align-items:flex-end}
.icon{width:118px;height:118px;border-radius:26px;display:flex;align-items:center;justify-content:center;background:var(--bg);box-shadow:0 6px 18px rgba(20,17,12,.14)}
.icon.sm{width:58px;height:58px;border-radius:13px}
.icoWrap{display:flex;flex-direction:column;align-items:center;gap:10px}
.icoWrap span{font-size:10px;opacity:.5}
.lockup{display:flex;align-items:center;gap:20px;background:var(--bg);border:1px solid var(--rule);border-radius:14px;padding:32px 40px;margin-bottom:12px}
.lockup .wm{font-family:var(--serif);font-size:50px;letter-spacing:-.02em;color:var(--ink)}
.lockup.p-dark .wm{color:#F1ECE1}
.lockup .tl{font-size:10px;letter-spacing:.22em;text-transform:uppercase;opacity:.5;margin-top:6px}
.lockup.p-dark .tl{color:#F1ECE1;opacity:.5}
.pattern{display:grid;grid-template-columns:repeat(13,54px);background:var(--bg);border-radius:14px;padding:20px;width:742px;overflow:hidden}
</style>
</head>
<body>
<header>
<h1>C1 — three ribbons, developed</h1>
<p>C1 had the most robust silhouette of the seven and the least ownership: three bookmark ribbons is a shape any read-later app could reach for. Ownability is the thing to attack, so each variant below adds one specific idea — a rhythm, a structure, a colour rule — and the caption says whether it actually buys anything.</p>
</header>
<svg width="0" height="0" style="position:absolute" aria-hidden="true"><defs>
<!-- a. Stepped — the baseline from the first sheet. Tops aligned, bottoms
stepped long/longest/short. -->
<symbol id="v-step" viewBox="0 0 120 120">
<path fill="var(--c1)" d="M24 14 H36 A6 6 0 0 1 42 20 V100 L30 86 L18 100 V20 A6 6 0 0 1 24 14 Z"/>
<path fill="var(--c2)" d="M54 14 H66 A6 6 0 0 1 72 20 V112 L60 98 L48 112 V20 A6 6 0 0 1 54 14 Z"/>
<path fill="var(--c3)" d="M84 14 H96 A6 6 0 0 1 102 20 V92 L90 78 L78 92 V20 A6 6 0 0 1 84 14 Z"/>
</symbol>
<!-- b. Stair — monotonic descent instead of an arbitrary step. Directional,
so it reads as progression rather than decoration. -->
<symbol id="v-stair" viewBox="0 0 120 120">
<path fill="var(--c1)" d="M24 14 H36 A6 6 0 0 1 42 20 V92 L30 78 L18 92 V20 A6 6 0 0 1 24 14 Z"/>
<path fill="var(--c2)" d="M54 14 H66 A6 6 0 0 1 72 20 V102 L60 88 L48 102 V20 A6 6 0 0 1 54 14 Z"/>
<path fill="var(--c3)" d="M84 14 H96 A6 6 0 0 1 102 20 V112 L90 98 L78 112 V20 A6 6 0 0 1 84 14 Z"/>
</symbol>
<!-- c. Tally — four thin equal ribbons. Tally marks and bookmarks are the
same shape, and the app is called Marks. The pun is the ownership. -->
<symbol id="v-tally" viewBox="0 0 120 120">
<path fill="var(--c1)" d="M21 16 H27 A4 4 0 0 1 31 20 V104 L24 94 L17 104 V20 A4 4 0 0 1 21 16 Z"/>
<path fill="var(--c2)" d="M45 16 H51 A4 4 0 0 1 55 20 V104 L48 94 L41 104 V20 A4 4 0 0 1 45 16 Z"/>
<path fill="var(--c1)" d="M69 16 H75 A4 4 0 0 1 79 20 V104 L72 94 L65 104 V20 A4 4 0 0 1 69 16 Z"/>
<path fill="var(--c3)" d="M93 16 H99 A4 4 0 0 1 103 20 V104 L96 94 L89 104 V20 A4 4 0 0 1 93 16 Z"/>
</symbol>
<!-- d. Rail — hung from a bar. Restores the shelf/box-file reading the
reference had and C1 threw away, for the cost of one rectangle. -->
<symbol id="v-rail" viewBox="0 0 120 120">
<path fill="var(--c2)" d="M22 30 H44 V104 L33 91 L22 104 Z"/>
<path fill="var(--c3)" d="M49 30 H71 V112 L60 99 L49 112 Z"/>
<path fill="var(--c2)" d="M76 30 H98 V96 L87 83 L76 96 Z"/>
<path fill="var(--c1)" d="M20 14 H100 A6 6 0 0 1 106 20 V34 H14 V20 A6 6 0 0 1 20 14 Z"/>
</symbol>
<!-- e. Overlap — wide ribbons that cross, multiplied. This is the trick the
reference actually runs on: depth from two flat colours, no shadow. -->
<symbol id="v-overlap" viewBox="0 0 120 120">
<g style="isolation:isolate">
<path style="mix-blend-mode:multiply" fill="var(--c1)" d="M16 14 H42 A8 8 0 0 1 50 22 V100 L29 84 L8 100 V22 A8 8 0 0 1 16 14 Z"/>
<path style="mix-blend-mode:multiply" fill="var(--c2)" d="M47 14 H73 A8 8 0 0 1 81 22 V112 L60 96 L39 112 V22 A8 8 0 0 1 47 14 Z"/>
<path style="mix-blend-mode:multiply" fill="var(--c1)" d="M78 14 H104 A8 8 0 0 1 112 22 V94 L91 78 L70 94 V22 A8 8 0 0 1 78 14 Z"/>
</g>
</symbol>
<!-- f. Foreground — one ribbon pulled forward and keylined in the ground
colour. Asymmetry is cheap ownership; symmetry is what makes C1 generic. -->
<symbol id="v-front" viewBox="0 0 120 120">
<path fill="var(--c1)" d="M26 14 H40 A6 6 0 0 1 46 20 V94 L33 81 L20 94 V20 A6 6 0 0 1 26 14 Z"/>
<path fill="var(--c1)" d="M80 14 H94 A6 6 0 0 1 100 20 V94 L87 81 L74 94 V20 A6 6 0 0 1 80 14 Z"/>
<path fill="var(--c2)" stroke="var(--bg)" stroke-width="5" stroke-linejoin="round"
d="M50 24 H70 A8 8 0 0 1 78 32 V114 L60 96 L42 114 V32 A8 8 0 0 1 50 24 Z"/>
</symbol>
<!-- g. Fan — the same ribbon rotated. Reads as pages fanned rather than
objects lined up; the only variant with any motion in it. -->
<symbol id="v-fan" viewBox="0 0 120 120">
<path fill="var(--c1)" transform="rotate(-15 60 110)" d="M53 18 H67 A6 6 0 0 1 73 24 V104 L60 90 L47 104 V24 A6 6 0 0 1 53 18 Z"/>
<path fill="var(--c3)" transform="rotate(15 60 110)" d="M53 18 H67 A6 6 0 0 1 73 24 V104 L60 90 L47 104 V24 A6 6 0 0 1 53 18 Z"/>
<path fill="var(--c2)" d="M53 14 H67 A6 6 0 0 1 73 20 V108 L60 94 L47 108 V20 A6 6 0 0 1 53 14 Z"/>
</symbol>
<!-- h. Pair — two, not three. Fewer parts survive smaller; the question is
whether two still reads as "a collection". -->
<symbol id="v-pair" viewBox="0 0 120 120">
<path fill="var(--c1)" d="M33 14 H49 A7 7 0 0 1 56 21 V100 L41 84 L26 100 V21 A7 7 0 0 1 33 14 Z"/>
<path fill="var(--c2)" d="M71 14 H87 A7 7 0 0 1 94 21 V112 L79 96 L64 112 V21 A7 7 0 0 1 71 14 Z"/>
</symbol>
<!-- i. Nested — concentric rather than side by side. A notch inside a notch
inside a notch: an archive, not a row. -->
<symbol id="v-nest" viewBox="0 0 120 120">
<path fill="var(--c1)" d="M26 14 H94 A10 10 0 0 1 104 24 V112 L60 92 L16 112 V24 A10 10 0 0 1 26 14 Z"/>
<path fill="var(--c3)" d="M42 14 H78 A8 8 0 0 1 86 22 V92 L60 77 L34 92 V22 A8 8 0 0 1 42 14 Z"/>
<path fill="var(--c2)" d="M54 14 H66 A6 6 0 0 1 72 20 V68 L60 58 L48 68 V20 A6 6 0 0 1 54 14 Z"/>
</symbol>
<!-- j. Scallop — one shape, four tails. Everything above is N objects; this
is a single path, which is what the tinted/mono icon modes want. -->
<symbol id="v-scallop" viewBox="0 0 120 120">
<clipPath id="clipScallop">
<path d="M26 14 H94 A10 10 0 0 1 104 24 V112 L89 96 L75 112 L60 96 L45 112 L31 96 L16 112 V24 A10 10 0 0 1 26 14 Z"/>
</clipPath>
<g clip-path="url(#clipScallop)">
<rect x="0" y="0" width="45" height="120" fill="var(--c1)"/>
<rect x="45" y="0" width="30" height="120" fill="var(--c2)"/>
<rect x="75" y="0" width="45" height="120" fill="var(--c3)"/>
</g>
</symbol>
</defs></svg>
<section>
<span class="tag">01</span>
<h2>Ten developments</h2>
<p class="sub">Shown in the app's own palette (navy / vermilion / yellow from <code>Paper.swatchPairs</code>) rather than the reference cyan-violet, since that was the conclusion of the last sheet. The <em>ownable?</em> line is the honest verdict, not a summary.</p>
<div class="grid" id="cards"></div>
</section>
<section>
<span class="tag">02</span>
<h2>Palette</h2>
<p class="sub">The three-swatch column is the interesting one. The app already hashes bookmarks into a twelve-swatch palette — a mark built from three of those swatches is describing the app's actual data model, which is ownership no competitor can copy without copying the system underneath it.</p>
<table id="matrix"></table>
</section>
<section>
<span class="tag">03</span>
<h2>Size ladder</h2>
<p class="sub">128 / 64 / 40 / 24 / 16&nbsp;px. C1's whole argument is that it survives this; the developments have to survive it too or they've bought ownership with legibility.</p>
<div id="ladders"></div>
</section>
<section>
<span class="tag">04</span>
<h2>App icon</h2>
<p class="sub">Squircle at 118 and 58&nbsp;px, mark at ~62% of the tile.</p>
<div class="icons" id="icons"></div>
<div class="icons" id="iconsSm" style="margin-top:22px"></div>
</section>
<section>
<span class="tag">05</span>
<h2>Lockups</h2>
<div id="lockups"></div>
</section>
<section style="border-bottom:none">
<span class="tag">06</span>
<h2>Pattern</h2>
<p class="sub">The test C1 is most likely to win: a repeating field is where a simple silhouette beats a clever one.</p>
<div class="pattern pal p-paper" id="pat"></div>
</section>
<script>
const VARIANTS = [
{id:'v-step', name:'C1·a Stepped', desc:'The baseline. Tops aligned, bottoms stepped. Balanced and readable, but the step pattern is arbitrary — nothing decides it, so nothing defends it.', own:'ownable? — no'},
{id:'v-stair', name:'C1·b Stair', desc:'Same three ribbons, monotonic descent. Now the rhythm means something (progression, a queue getting shorter) and the eye has a direction to travel.', own:'ownable? — a little'},
{id:'v-tally', name:'C1·c Tally', desc:'Four thin ribbons. A tally mark and a bookmark ribbon are already the same shape, and the app is called Marks — the mark states the name instead of illustrating the category.', own:'ownable? — yes'},
{id:'v-rail', name:'C1·d Rail', desc:'Hung from a bar. Buys back the shelf/box-file reading that made the reference work three ways, for the cost of one rectangle.', own:'ownable? — yes'},
{id:'v-overlap', name:'C1·e Overlap', desc:'Wide ribbons crossing, multiplied. This is the trick the reference actually runs on: depth out of two flat colours and no shadow at all.', own:'ownable? — a little'},
{id:'v-front', name:'C1·f Foreground', desc:'One ribbon pulled forward and keylined in the ground colour. Asymmetry is cheap ownership — the symmetry is most of why the baseline feels anonymous.', own:'ownable? — a little'},
{id:'v-fan', name:'C1·g Fan', desc:'The same ribbon rotated about a point below the mark. Reads as pages fanned rather than objects filed. The only one with motion, and the only one that animates for free.', own:'ownable? — yes'},
{id:'v-pair', name:'C1·h Pair', desc:'Two instead of three. Calmer and it holds smaller, but two things read as a pair, not a collection — which is the wrong idea for an archive.', own:'ownable? — no'},
{id:'v-nest', name:'C1·i Nested', desc:'Concentric instead of side by side: a notch inside a notch inside a notch. Says archive rather than row, and it is the only one that suggests depth without overlap.', own:'ownable? — yes'},
{id:'v-scallop', name:'C1·j Scallop', desc:'One path, four tails. Everything above is N objects fighting for space; this is a single shape, which is what iOS 26 tinted and monochrome icon modes actually want.', own:'ownable? — yes'},
]
const PALETTES = [
{cls:'p-paper', label:'Navy / Verm.'},
{cls:'p-tri', label:'Three swatches'},
{cls:'p-ink', label:'Ink / Verm.'},
{cls:'p-mono', label:'Ink only'},
{cls:'p-dark', label:'Dark'},
{cls:'p-orig', label:'Reference'},
]
const NS = 'http://www.w3.org/2000/svg'
const mark = (id, size, cls) => {
const svg = document.createElementNS(NS, 'svg')
svg.setAttribute('width', size); svg.setAttribute('height', size)
svg.setAttribute('viewBox', '0 0 120 120')
if (cls) svg.setAttribute('class', cls)
const use = document.createElementNS(NS, 'use')
use.setAttribute('href', '#' + id)
svg.appendChild(use)
return svg
}
const el = (tag, cls, html) => {
const n = document.createElement(tag)
if (cls) n.className = cls
if (html != null) n.innerHTML = html
return n
}
// 01 — cards
const cards = document.getElementById('cards')
for (const v of VARIANTS) {
const card = el('div', 'card pal p-paper')
const stage = el('div', 'stage'); stage.appendChild(mark(v.id, 132))
card.append(stage, el('h3', null, v.name), el('p', null, v.desc), el('div', 'own', v.own))
cards.appendChild(card)
}
// 02 — matrix
const matrix = document.getElementById('matrix')
const head = document.createElement('tr')
head.appendChild(el('th', 'rowh'))
for (const p of PALETTES) head.appendChild(el('th', null, p.label))
matrix.appendChild(head)
for (const v of VARIANTS) {
const row = document.createElement('tr')
row.appendChild(el('th', 'rowh', v.name))
for (const p of PALETTES) {
const td = document.createElement('td')
const cell = el('div', 'cell pal ' + p.cls)
cell.appendChild(mark(v.id, 68))
td.appendChild(cell); row.appendChild(td)
}
matrix.appendChild(row)
}
// 03 — size ladder, for the variants that claimed ownership
const ladders = document.getElementById('ladders')
for (const v of VARIANTS.filter(v => v.own.endsWith('yes'))) {
const row = el('div', 'ladder pal p-paper')
row.appendChild(el('div', 'name', v.name))
for (const s of [128, 64, 40, 24, 16]) {
const item = el('div', 'item')
item.appendChild(mark(v.id, s))
item.appendChild(el('div', 'n', s))
row.appendChild(item)
}
ladders.appendChild(row)
}
// 04 — icons
const ICONS = [
['v-tally', 'p-paper'], ['v-rail', 'p-tri'], ['v-fan', 'p-paper'],
['v-nest', 'p-ink'], ['v-scallop','p-tri'], ['v-scallop', 'p-dark'],
]
for (const [wrap, size, big] of [['icons', 72, true], ['iconsSm', 36, false]]) {
const host = document.getElementById(wrap)
for (const [id, cls] of ICONS) {
const w = el('div', 'icoWrap')
const tile = el('div', 'icon pal ' + cls + (big ? '' : ' sm'))
tile.appendChild(mark(id, size))
w.append(tile, el('span', null, big ? id.slice(2) + ' · ' + cls.slice(2) : '58pt'))
host.appendChild(w)
}
}
// 05 — lockups
const lockups = document.getElementById('lockups')
for (const [id, cls] of [['v-tally','p-paper'], ['v-rail','p-tri'], ['v-scallop','p-ink'], ['v-fan','p-dark']]) {
const row = el('div', 'lockup pal ' + cls)
row.appendChild(mark(id, 64))
const txt = el('div')
txt.append(el('div', 'wm', 'Marks'), el('div', 'tl', 'Everything you saved'))
row.appendChild(txt)
lockups.appendChild(row)
}
// 06 — pattern. Row offset stops the palette cycle lining up into stripes.
const cycle = ['p-paper', 'p-tri', 'p-ink', 'p-dark'], COLS = 13
const pat = document.getElementById('pat')
for (let i = 0; i < COLS * 5; i++) {
pat.appendChild(mark('v-tally', 54, 'pal ' + cycle[(i + Math.floor(i / COLS)) % cycle.length]))
}
</script>
</body>
</html>
+447
View File
@@ -0,0 +1,447 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Marks — logo variants</title>
<style>
:root{
--ink:#14110C; --sheet:#F8F5EF; --rule:rgba(20,17,12,.16);
--mono:ui-monospace,SFMono-Regular,Menlo,monospace;
--serif:ui-serif,Georgia,"Times New Roman",serif;
}
*{box-sizing:border-box}
body{margin:0;background:#EFEBE3;color:var(--ink);font-family:var(--mono);font-size:12px;-webkit-font-smoothing:antialiased}
header{padding:56px 40px 28px;border-bottom:1px solid var(--rule)}
h1{font-family:var(--serif);font-size:34px;font-weight:400;margin:0 0 8px}
header p{margin:0;max-width:60ch;line-height:1.7;opacity:.62}
section{padding:40px;border-bottom:1px solid var(--rule)}
h2{font-family:var(--serif);font-size:22px;font-weight:400;margin:0 0 4px}
.sub{opacity:.55;margin:0 0 28px;line-height:1.7;max-width:72ch}
.tag{display:inline-block;font-size:10px;letter-spacing:.14em;text-transform:uppercase;opacity:.45;margin-bottom:10px}
/* ---- palettes: geometry is shared, colour is injected ---- */
.pal{--c1:#22C7F0;--c2:#8B4DEE;--dot:#fff;--bg:#fff}
.p-orig {--c1:#22C7F0;--c2:#8B4DEE;--dot:#fff; --bg:#fff}
.p-paper{--c1:#001A55;--c2:#ED663F;--dot:#F8F5EF;--bg:#F8F5EF} /* navy + vermilion */
.p-ink {--c1:#14110C;--c2:#ED663F;--dot:#F8F5EF;--bg:#F8F5EF} /* ink + vermilion */
.p-warm {--c1:#FECD00;--c2:#ED663F;--dot:#14110C;--bg:#F8F5EF} /* yellow + vermilion */
.p-blue {--c1:#115AB5;--c2:#033B00;--dot:#F8F5EF;--bg:#F8F5EF} /* blue + forest */
.p-dark {--c1:#5B8FD0;--c2:#E07A5C;--dot:#15130F;--bg:#15130F} /* dark-mode pair */
.p-mono1{--c1:#14110C;--c2:#14110C;--dot:#F8F5EF;--bg:#F8F5EF} /* single-colour test */
.grid{display:flex;flex-wrap:wrap;gap:18px}
.card{background:var(--bg);border:1px solid var(--rule);border-radius:14px;padding:22px;width:236px}
.card .stage{display:flex;align-items:center;justify-content:center;height:150px}
.card svg{display:block}
.card h3{font-family:var(--serif);font-size:15px;font-weight:400;margin:14px 0 5px}
.card p{margin:0;font-size:10.5px;line-height:1.65;opacity:.6}
.p-dark h3,.p-dark p{color:#F1ECE1}
.p-dark{border-color:rgba(241,236,225,.14)}
/* matrix */
table{border-collapse:collapse}
th{font-weight:400;font-size:10px;letter-spacing:.1em;text-transform:uppercase;opacity:.45;padding:0 0 12px;text-align:center}
th.rowh{text-align:right;padding-right:16px;text-transform:none;letter-spacing:0;font-size:11px;opacity:.65;white-space:nowrap}
td{padding:6px}
td .cell{width:104px;height:104px;border-radius:12px;background:var(--bg);border:1px solid var(--rule);display:flex;align-items:center;justify-content:center}
/* size ladder */
.ladder{display:flex;align-items:flex-end;gap:22px;background:var(--bg);border:1px solid var(--rule);border-radius:14px;padding:22px 26px;margin-bottom:14px}
.ladder .n{font-size:9.5px;opacity:.4;text-align:center;margin-top:8px}
.ladder .item{display:flex;flex-direction:column;align-items:center}
.ladder .name{font-family:var(--serif);font-size:14px;width:120px;opacity:.8}
/* app icons */
.icons{display:flex;flex-wrap:wrap;gap:20px;align-items:flex-end}
.icon{width:120px;height:120px;border-radius:26.5px;display:flex;align-items:center;justify-content:center;background:var(--bg);box-shadow:0 6px 18px rgba(20,17,12,.14)}
.icon.sm{width:60px;height:60px;border-radius:13.5px}
.icoWrap{display:flex;flex-direction:column;align-items:center;gap:10px}
.icoWrap span{font-size:10px;opacity:.5}
/* wordmark */
.lockup{display:flex;align-items:center;gap:20px;background:var(--bg);border:1px solid var(--rule);border-radius:14px;padding:34px 40px;margin-bottom:14px}
.lockup .wm{font-family:var(--serif);font-size:52px;letter-spacing:-.02em;color:var(--c1)}
.lockup .wm.ink{color:var(--ink)}
.lockup.p-dark .wm.ink{color:#F1ECE1}
.lockup.p-dark .tl{color:#F1ECE1;opacity:.5}
.lockup .tl{font-size:10px;letter-spacing:.22em;text-transform:uppercase;opacity:.5;margin-top:6px}
.pattern{display:grid;grid-template-columns:repeat(13,54px);gap:0;background:var(--bg);border-radius:14px;padding:20px;width:742px;overflow:hidden}
</style>
</head>
<body>
<header>
<h1>Marks — logo variants</h1>
<p>Five geometries derived from the reference (letter&nbsp;+ box&nbsp;files&nbsp;+ bookmark&nbsp;ribbon), each drawn once and re-coloured through the app's existing <code>Paper</code> palette. Geometry lives in <code>&lt;symbol&gt;</code>; colour is injected with CSS custom properties, so any cell in the matrix is a real, buildable combination.</p>
</header>
<!-- ============ GEOMETRY DEFINITIONS ============ -->
<svg width="0" height="0" style="position:absolute" aria-hidden="true">
<defs>
<!-- 1. Binder-B: faithful reconstruction. Violet ribbon stem, three cyan
binder bars (wide / narrow / wide = the B), notch clears the bottom bar. -->
<symbol id="binderB" viewBox="0 0 120 120">
<g style="isolation:isolate">
<path style="mix-blend-mode:multiply" fill="var(--c2)"
d="M31 10 H49 A7 7 0 0 1 56 17 V110 L40 96 L24 110 V17 A7 7 0 0 1 31 10 Z"/>
<path style="mix-blend-mode:multiply" fill="var(--c1)" d="M40 10 H79 A13 13 0 0 1 79 36 H40 Z"/>
<path style="mix-blend-mode:multiply" fill="var(--c1)" d="M40 40 H69 A13 13 0 0 1 69 66 H40 Z"/>
<path style="mix-blend-mode:multiply" fill="var(--c1)" d="M40 70 H83 A13 13 0 0 1 83 96 H40 Z"/>
</g>
<circle cx="32" cy="23" r="5" fill="var(--dot)"/>
<circle cx="32" cy="53" r="5" fill="var(--dot)"/>
<circle cx="32" cy="83" r="5" fill="var(--dot)"/>
</symbol>
<!-- 2. Ribbon-B: same skeleton, but every terminal is a swallowtail.
Consistent language, loses some of the B. -->
<symbol id="ribbonB" viewBox="0 0 120 120">
<g style="isolation:isolate">
<path style="mix-blend-mode:multiply" fill="var(--c2)"
d="M31 10 H49 A7 7 0 0 1 56 17 V110 L40 96 L24 110 V17 A7 7 0 0 1 31 10 Z"/>
<path style="mix-blend-mode:multiply" fill="var(--c1)" d="M40 10 H92 L80 23 L92 36 H40 Z"/>
<path style="mix-blend-mode:multiply" fill="var(--c1)" d="M40 40 H82 L70 53 L82 66 H40 Z"/>
<path style="mix-blend-mode:multiply" fill="var(--c1)" d="M40 70 H96 L84 83 L96 96 H40 Z"/>
</g>
<circle cx="32" cy="23" r="5" fill="var(--dot)"/>
<circle cx="32" cy="53" r="5" fill="var(--dot)"/>
<circle cx="32" cy="83" r="5" fill="var(--dot)"/>
</symbol>
<!-- 3. Slab-M, tailed. A real M: the counters are wedges, apex at the cap
line, widening to the baseline — that is what makes it a letter and not
a slotted slab. Each of the three feet then takes a swallowtail. -->
<symbol id="slabMdeep" viewBox="0 0 120 120">
<clipPath id="clipMdeep">
<path d="M24 16 H96 A10 10 0 0 1 106 26 V104 L97 92 L88 104 L86 20 L69 104 L60 92 L51 104 L34 20 L32 104 L23 92 L14 104 V26 A10 10 0 0 1 24 16 Z"/>
</clipPath>
<g clip-path="url(#clipMdeep)">
<rect x="0" y="0" width="42" height="120" fill="var(--c1)"/>
<rect x="42" y="0" width="36" height="120" fill="var(--c2)"/>
<rect x="78" y="0" width="42" height="120" fill="var(--c1)"/>
</g>
<circle cx="23" cy="68" r="5.5" fill="var(--dot)"/>
<circle cx="60" cy="68" r="5.5" fill="var(--dot)"/>
<circle cx="97" cy="68" r="5.5" fill="var(--dot)"/>
</symbol>
<!-- 4. Slab-M, flat-footed. Same letter, no tails — the bookmark cue is
carried by the rounded cap and the colour split alone. Letterform
first; the ribbon reading is gone. -->
<symbol id="slabMshallow" viewBox="0 0 120 120">
<clipPath id="clipMshallow">
<path d="M24 16 H96 A10 10 0 0 1 106 26 V104 H88 L86 20 L69 104 H51 L34 20 L32 104 H14 V26 A10 10 0 0 1 24 16 Z"/>
</clipPath>
<g clip-path="url(#clipMshallow)">
<rect x="0" y="0" width="42" height="120" fill="var(--c1)"/>
<rect x="42" y="0" width="36" height="120" fill="var(--c2)"/>
<rect x="78" y="0" width="42" height="120" fill="var(--c1)"/>
</g>
<circle cx="23" cy="68" r="5.5" fill="var(--dot)"/>
<circle cx="60" cy="68" r="5.5" fill="var(--dot)"/>
<circle cx="97" cy="68" r="5.5" fill="var(--dot)"/>
</symbol>
<!-- 5. Ribbon + knockout: single ribbon, letter cut out of it.
The one that survives 16px and tinted/monochrome icon modes. -->
<symbol id="ribbonM" viewBox="0 0 120 120">
<mask id="maskM">
<rect width="120" height="120" fill="#000"/>
<path fill="#fff" d="M38 12 H82 A12 12 0 0 1 94 24 V110 L60 92 L26 110 V24 A12 12 0 0 1 38 12 Z"/>
<text x="60" y="70" text-anchor="middle" fill="#000"
font-family="ui-serif,Georgia,serif" font-size="58" font-weight="600">M</text>
</mask>
<rect width="120" height="120" fill="var(--c1)" mask="url(#maskM)"/>
</symbol>
<!-- 6. Three ribbons: no letterform at all. Stepped heights carry the
rhythm; the only thing that has to survive at 16px is the silhouette. -->
<symbol id="threeRibbons" viewBox="0 0 120 120">
<path fill="var(--c1)" d="M24 14 H36 A6 6 0 0 1 42 20 V98 L30 84 L18 98 V20 A6 6 0 0 1 24 14 Z"/>
<path fill="var(--c2)" d="M54 14 H66 A6 6 0 0 1 72 20 V112 L60 98 L48 112 V20 A6 6 0 0 1 54 14 Z"/>
<path fill="var(--c1)" d="M84 14 H96 A6 6 0 0 1 102 20 V90 L90 76 L78 90 V20 A6 6 0 0 1 84 14 Z"/>
</symbol>
<!-- 7. Monoline: the Binder-B as strokes only. Closest to the app's
archival/letterpress register; no fills to fight the paper ground. -->
<symbol id="binderLine" viewBox="0 0 120 120">
<g fill="none" stroke="var(--c1)" stroke-width="6" stroke-linejoin="round">
<path d="M31 13 H49 A4 4 0 0 1 53 17 V104 L40 93 L27 104 V17 A4 4 0 0 1 31 13 Z"/>
<path d="M50 13 H76 A11 11 0 0 1 76 35 H50"/>
<path d="M50 42 H67 A11 11 0 0 1 67 64 H50"/>
<path d="M50 71 H80 A11 11 0 0 1 80 93 H50"/>
</g>
<circle cx="34" cy="24" r="3" fill="var(--c2)"/>
<circle cx="34" cy="53" r="3" fill="var(--c2)"/>
<circle cx="34" cy="82" r="3" fill="var(--c2)"/>
</symbol>
</defs>
</svg>
<!-- ============ 1. THE GEOMETRIES ============ -->
<section>
<span class="tag">01</span>
<h2>Seven geometries</h2>
<p class="sub">Shown in the reference palette (cyan&nbsp;#22C7F0 / violet&nbsp;#8B4DEE) with multiply overlap, so they're directly comparable to the original.</p>
<div class="grid">
<div class="card pal p-orig">
<div class="stage"><svg width="130" height="130" viewBox="0 0 120 120"><use href="#binderB"/></svg></div>
<h3>A1 · Binder-B</h3>
<p>Faithful reconstruction. Ribbon stem carries the notch; three bars go wide / narrow / wide to make the B. Closest to the source.</p>
</div>
<div class="card pal p-orig">
<div class="stage"><svg width="130" height="130" viewBox="0 0 120 120"><use href="#ribbonB"/></svg></div>
<h3>A2 · Ribbon-B</h3>
<p>Every terminal becomes a swallowtail. More internally consistent, but the bowls stop reading as bowls — the B gets weaker.</p>
</div>
<div class="card pal p-orig">
<div class="stage"><svg width="130" height="130" viewBox="0 0 120 120"><use href="#slabMdeep"/></svg></div>
<h3>B1 · Slab-M, tailed</h3>
<p>Retargeted to the app's actual name. A real M — counters are wedges, apex at the cap line — with a swallowtail on each of the three feet.</p>
</div>
<div class="card pal p-orig">
<div class="stage"><svg width="130" height="130" viewBox="0 0 120 120"><use href="#slabMshallow"/></svg></div>
<h3>B2 · Slab-M, flat</h3>
<p>Same letter, tails removed. Sturdier and more type-like, but only the rounded cap is left carrying the bookmark idea — probably too little.</p>
</div>
<div class="card pal p-orig">
<div class="stage"><svg width="130" height="130" viewBox="0 0 120 120"><use href="#ribbonM"/></svg></div>
<h3>B3 · Ribbon knockout</h3>
<p>Single ribbon, letter knocked out. One colour, one shape — the only variant that's trivially correct in iOS 26 tinted and mono icon modes.</p>
</div>
<div class="card pal p-orig">
<div class="stage"><svg width="130" height="130" viewBox="0 0 120 120"><use href="#threeRibbons"/></svg></div>
<h3>C1 · Three ribbons</h3>
<p>Drops the letterform entirely. Stepped heights do the work. Most robust silhouette, least ownable — many apps could use this.</p>
</div>
<div class="card pal p-orig">
<div class="stage"><svg width="130" height="130" viewBox="0 0 120 120"><use href="#binderLine"/></svg></div>
<h3>C2 · Monoline</h3>
<p>Binder-B as strokes. Sits in the same register as the app's serif/paper styling instead of fighting it. Fails below ~28px.</p>
</div>
</div>
</section>
<!-- ============ 2. PALETTE MATRIX ============ -->
<section>
<span class="tag">02</span>
<h2>Palette matrix</h2>
<p class="sub">Each geometry through the reference palette and five pairs drawn from <code>Paper.swatchPairs</code> in <code>LibraryKit.swift</code>. The <em>Dark</em> column uses the palette's own dark-mode counterparts, not dimmed light values.</p>
<table>
<tr>
<th class="rowh"></th>
<th>Reference</th><th>Navy&nbsp;+&nbsp;Verm.</th><th>Ink&nbsp;+&nbsp;Verm.</th>
<th>Yellow&nbsp;+&nbsp;Verm.</th><th>Blue&nbsp;+&nbsp;Forest</th><th>Ink&nbsp;only</th><th>Dark</th>
</tr>
<tr>
<th class="rowh">A1 Binder-B</th>
<td><div class="cell pal p-orig"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#binderB"/></svg></div></td>
<td><div class="cell pal p-paper"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#binderB"/></svg></div></td>
<td><div class="cell pal p-ink"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#binderB"/></svg></div></td>
<td><div class="cell pal p-warm"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#binderB"/></svg></div></td>
<td><div class="cell pal p-blue"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#binderB"/></svg></div></td>
<td><div class="cell pal p-mono1"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#binderB"/></svg></div></td>
<td><div class="cell pal p-dark"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#binderB"/></svg></div></td>
</tr>
<tr>
<th class="rowh">A2 Ribbon-B</th>
<td><div class="cell pal p-orig"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#ribbonB"/></svg></div></td>
<td><div class="cell pal p-paper"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#ribbonB"/></svg></div></td>
<td><div class="cell pal p-ink"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#ribbonB"/></svg></div></td>
<td><div class="cell pal p-warm"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#ribbonB"/></svg></div></td>
<td><div class="cell pal p-blue"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#ribbonB"/></svg></div></td>
<td><div class="cell pal p-mono1"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#ribbonB"/></svg></div></td>
<td><div class="cell pal p-dark"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#ribbonB"/></svg></div></td>
</tr>
<tr>
<th class="rowh">B1 Slab-M deep</th>
<td><div class="cell pal p-orig"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#slabMdeep"/></svg></div></td>
<td><div class="cell pal p-paper"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#slabMdeep"/></svg></div></td>
<td><div class="cell pal p-ink"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#slabMdeep"/></svg></div></td>
<td><div class="cell pal p-warm"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#slabMdeep"/></svg></div></td>
<td><div class="cell pal p-blue"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#slabMdeep"/></svg></div></td>
<td><div class="cell pal p-mono1"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#slabMdeep"/></svg></div></td>
<td><div class="cell pal p-dark"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#slabMdeep"/></svg></div></td>
</tr>
<tr>
<th class="rowh">B2 Slab-M shallow</th>
<td><div class="cell pal p-orig"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#slabMshallow"/></svg></div></td>
<td><div class="cell pal p-paper"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#slabMshallow"/></svg></div></td>
<td><div class="cell pal p-ink"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#slabMshallow"/></svg></div></td>
<td><div class="cell pal p-warm"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#slabMshallow"/></svg></div></td>
<td><div class="cell pal p-blue"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#slabMshallow"/></svg></div></td>
<td><div class="cell pal p-mono1"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#slabMshallow"/></svg></div></td>
<td><div class="cell pal p-dark"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#slabMshallow"/></svg></div></td>
</tr>
<tr>
<th class="rowh">B3 Knockout</th>
<td><div class="cell pal p-orig"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#ribbonM"/></svg></div></td>
<td><div class="cell pal p-paper"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#ribbonM"/></svg></div></td>
<td><div class="cell pal p-ink"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#ribbonM"/></svg></div></td>
<td><div class="cell pal p-warm"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#ribbonM"/></svg></div></td>
<td><div class="cell pal p-blue"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#ribbonM"/></svg></div></td>
<td><div class="cell pal p-mono1"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#ribbonM"/></svg></div></td>
<td><div class="cell pal p-dark"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#ribbonM"/></svg></div></td>
</tr>
<tr>
<th class="rowh">C1 Three ribbons</th>
<td><div class="cell pal p-orig"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#threeRibbons"/></svg></div></td>
<td><div class="cell pal p-paper"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#threeRibbons"/></svg></div></td>
<td><div class="cell pal p-ink"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#threeRibbons"/></svg></div></td>
<td><div class="cell pal p-warm"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#threeRibbons"/></svg></div></td>
<td><div class="cell pal p-blue"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#threeRibbons"/></svg></div></td>
<td><div class="cell pal p-mono1"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#threeRibbons"/></svg></div></td>
<td><div class="cell pal p-dark"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#threeRibbons"/></svg></div></td>
</tr>
<tr>
<th class="rowh">C2 Monoline</th>
<td><div class="cell pal p-orig"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#binderLine"/></svg></div></td>
<td><div class="cell pal p-paper"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#binderLine"/></svg></div></td>
<td><div class="cell pal p-ink"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#binderLine"/></svg></div></td>
<td><div class="cell pal p-warm"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#binderLine"/></svg></div></td>
<td><div class="cell pal p-blue"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#binderLine"/></svg></div></td>
<td><div class="cell pal p-mono1"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#binderLine"/></svg></div></td>
<td><div class="cell pal p-dark"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#binderLine"/></svg></div></td>
</tr>
</table>
</section>
<!-- ============ 3. SIZE LADDER ============ -->
<section>
<span class="tag">03</span>
<h2>Size ladder</h2>
<p class="sub">128 / 64 / 40 / 24 / 16&nbsp;px. 40px is the Settings-row and tab-bar size; 16px is the favicon / Spotlight-result size. Anything that turns to mush at 24 is a wordmark, not a mark.</p>
<div class="ladder pal p-paper">
<div class="name">A1 Binder-B</div>
<div class="item"><svg width="128" height="128" viewBox="0 0 120 120"><use href="#binderB"/></svg><div class="n">128</div></div>
<div class="item"><svg width="64" height="64" viewBox="0 0 120 120"><use href="#binderB"/></svg><div class="n">64</div></div>
<div class="item"><svg width="40" height="40" viewBox="0 0 120 120"><use href="#binderB"/></svg><div class="n">40</div></div>
<div class="item"><svg width="24" height="24" viewBox="0 0 120 120"><use href="#binderB"/></svg><div class="n">24</div></div>
<div class="item"><svg width="16" height="16" viewBox="0 0 120 120"><use href="#binderB"/></svg><div class="n">16</div></div>
</div>
<div class="ladder pal p-paper">
<div class="name">B1 Slab-M deep</div>
<div class="item"><svg width="128" height="128" viewBox="0 0 120 120"><use href="#slabMdeep"/></svg><div class="n">128</div></div>
<div class="item"><svg width="64" height="64" viewBox="0 0 120 120"><use href="#slabMdeep"/></svg><div class="n">64</div></div>
<div class="item"><svg width="40" height="40" viewBox="0 0 120 120"><use href="#slabMdeep"/></svg><div class="n">40</div></div>
<div class="item"><svg width="24" height="24" viewBox="0 0 120 120"><use href="#slabMdeep"/></svg><div class="n">24</div></div>
<div class="item"><svg width="16" height="16" viewBox="0 0 120 120"><use href="#slabMdeep"/></svg><div class="n">16</div></div>
</div>
<div class="ladder pal p-paper">
<div class="name">B3 Knockout</div>
<div class="item"><svg width="128" height="128" viewBox="0 0 120 120"><use href="#ribbonM"/></svg><div class="n">128</div></div>
<div class="item"><svg width="64" height="64" viewBox="0 0 120 120"><use href="#ribbonM"/></svg><div class="n">64</div></div>
<div class="item"><svg width="40" height="40" viewBox="0 0 120 120"><use href="#ribbonM"/></svg><div class="n">40</div></div>
<div class="item"><svg width="24" height="24" viewBox="0 0 120 120"><use href="#ribbonM"/></svg><div class="n">24</div></div>
<div class="item"><svg width="16" height="16" viewBox="0 0 120 120"><use href="#ribbonM"/></svg><div class="n">16</div></div>
</div>
<div class="ladder pal p-paper">
<div class="name">C1 Three ribbons</div>
<div class="item"><svg width="128" height="128" viewBox="0 0 120 120"><use href="#threeRibbons"/></svg><div class="n">128</div></div>
<div class="item"><svg width="64" height="64" viewBox="0 0 120 120"><use href="#threeRibbons"/></svg><div class="n">64</div></div>
<div class="item"><svg width="40" height="40" viewBox="0 0 120 120"><use href="#threeRibbons"/></svg><div class="n">40</div></div>
<div class="item"><svg width="24" height="24" viewBox="0 0 120 120"><use href="#threeRibbons"/></svg><div class="n">24</div></div>
<div class="item"><svg width="16" height="16" viewBox="0 0 120 120"><use href="#threeRibbons"/></svg><div class="n">16</div></div>
</div>
<div class="ladder pal p-paper">
<div class="name">C2 Monoline</div>
<div class="item"><svg width="128" height="128" viewBox="0 0 120 120"><use href="#binderLine"/></svg><div class="n">128</div></div>
<div class="item"><svg width="64" height="64" viewBox="0 0 120 120"><use href="#binderLine"/></svg><div class="n">64</div></div>
<div class="item"><svg width="40" height="40" viewBox="0 0 120 120"><use href="#binderLine"/></svg><div class="n">40</div></div>
<div class="item"><svg width="24" height="24" viewBox="0 0 120 120"><use href="#binderLine"/></svg><div class="n">24</div></div>
<div class="item"><svg width="16" height="16" viewBox="0 0 120 120"><use href="#binderLine"/></svg><div class="n">16</div></div>
</div>
</section>
<!-- ============ 4. APP ICON ============ -->
<section id="s4">
<span class="tag">04</span>
<h2>App icon</h2>
<p class="sub">iOS squircle at 120pt and 60pt. The mark is set at ~62% of the tile — Apple's grid wants generous margins, and every one of these was drawn edge-to-edge in its own box.</p>
<div class="icons">
<div class="icoWrap"><div class="icon pal p-paper"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#binderB"/></svg></div><span>A1 · navy</span></div>
<div class="icoWrap"><div class="icon pal p-warm"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#slabMdeep"/></svg></div><span>B1 · yellow</span></div>
<div class="icoWrap"><div class="icon pal p-ink"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#ribbonM"/></svg></div><span>B3 · ink</span></div>
<div class="icoWrap"><div class="icon pal p-blue"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#threeRibbons"/></svg></div><span>C1 · blue</span></div>
<div class="icoWrap"><div class="icon pal p-dark"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#binderB"/></svg></div><span>A1 · dark</span></div>
<div class="icoWrap"><div class="icon pal p-orig"><svg width="76" height="76" viewBox="0 0 120 120"><use href="#binderB"/></svg></div><span>A1 · reference</span></div>
</div>
<div class="icons" style="margin-top:22px">
<div class="icoWrap"><div class="icon sm pal p-paper"><svg width="38" height="38" viewBox="0 0 120 120"><use href="#binderB"/></svg></div><span>60pt</span></div>
<div class="icoWrap"><div class="icon sm pal p-warm"><svg width="38" height="38" viewBox="0 0 120 120"><use href="#slabMdeep"/></svg></div><span>60pt</span></div>
<div class="icoWrap"><div class="icon sm pal p-ink"><svg width="38" height="38" viewBox="0 0 120 120"><use href="#ribbonM"/></svg></div><span>60pt</span></div>
<div class="icoWrap"><div class="icon sm pal p-blue"><svg width="38" height="38" viewBox="0 0 120 120"><use href="#threeRibbons"/></svg></div><span>60pt</span></div>
<div class="icoWrap"><div class="icon sm pal p-dark"><svg width="38" height="38" viewBox="0 0 120 120"><use href="#binderB"/></svg></div><span>60pt</span></div>
<div class="icoWrap"><div class="icon sm pal p-orig"><svg width="38" height="38" viewBox="0 0 120 120"><use href="#binderB"/></svg></div><span>60pt</span></div>
</div>
</section>
<!-- ============ 5. LOCKUP ============ -->
<section>
<span class="tag">05</span>
<h2>Lockups</h2>
<p class="sub">Wordmark set in the app's own voice — serif for the name, mono letterspaced for the descriptor — rather than the reference's geometric sans, which belongs to a different product.</p>
<div class="lockup pal p-paper">
<svg width="66" height="66" viewBox="0 0 120 120"><use href="#binderB"/></svg>
<div><div class="wm ink">Marks</div><div class="tl">Everything you saved</div></div>
</div>
<div class="lockup pal p-ink">
<svg width="66" height="66" viewBox="0 0 120 120"><use href="#ribbonM"/></svg>
<div><div class="wm ink">Marks</div><div class="tl">Everything you saved</div></div>
</div>
<div class="lockup pal p-warm">
<svg width="66" height="66" viewBox="0 0 120 120"><use href="#slabMdeep"/></svg>
<div><div class="wm ink">Marks</div><div class="tl">Everything you saved</div></div>
</div>
<div class="lockup pal p-dark">
<svg width="66" height="66" viewBox="0 0 120 120"><use href="#threeRibbons"/></svg>
<div><div class="wm ink">Marks</div><div class="tl" style="color:#F1ECE1;opacity:.5">Everything you saved</div></div>
</div>
</section>
<!-- ============ 6. PATTERN ============ -->
<section style="border-bottom:none">
<span class="tag">06</span>
<h2>Pattern test</h2>
<p class="sub">The reference proves itself by tiling. Same test here — if the mark only works alone, it isn't a system.</p>
<div class="pattern pal p-paper" id="pat"></div>
</section>
<script>
// 13 columns x 5 rows. The row offset stops the palette cycle from lining up
// into vertical stripes, which is what makes it read as a field and not a grid.
const cycle = ['p-paper', 'p-warm', 'p-ink', 'p-blue'], COLS = 13
const NS = 'http://www.w3.org/2000/svg', pat = document.getElementById('pat')
for (let i = 0; i < COLS * 5; i++) {
const svg = document.createElementNS(NS, 'svg')
svg.setAttribute('width', 54); svg.setAttribute('height', 54)
svg.setAttribute('viewBox', '0 0 120 120')
svg.setAttribute('class', 'pal ' + cycle[(i + Math.floor(i / COLS)) % cycle.length])
const use = document.createElementNS(NS, 'use')
use.setAttribute('href', '#threeRibbons')
svg.appendChild(use); pat.appendChild(svg)
}
</script>
</body>
</html>
+72
View File
@@ -0,0 +1,72 @@
<!doctype html>
<!--
App icon artwork for Marks. Rendered to PNG by generate.sh — do not edit the
PNGs in AppIcon.appiconset directly, they are build output.
The path below is the same geometry as ScallopMark in
Marks/Views/Library/MarksMark.swift, in the same canonical 88 x 98 box. If you
change one, change the other; there is no shared source between Swift and SVG.
Three variants, per Apple's iOS 18+ app icon model:
light — full-bleed artwork on the paper ground, flattened (icons may not
carry an alpha channel).
dark — artwork only, on transparency. The system supplies the dark
background, so the bands are the palette's dark counterparts,
which are lifted rather than dimmed.
tinted — greyscale on transparency. The system maps luminance to the user's
chosen tint, so the three bands are carried as three greys whose
relative luminance matches the colour version. Flattening them to
one grey would throw away the band reading in exactly the mode
that has nothing else left.
-->
<html lang="en">
<head>
<meta charset="utf-8">
<title>Marks app icon</title>
<style>
html,body{margin:0;padding:0;background:transparent}
#tile{width:1024px;height:1024px;display:flex;align-items:center;justify-content:center}
svg{display:block}
</style>
</head>
<body>
<div id="tile"></div>
<script>
// 88 x 98 canonical box. 4 tails => 3 steps of 88/3.
const W = 88, H = 98, R = 10, DEPTH = 16, TAILS = 4
const step = W / (TAILS - 1)
let d = `M${R} 0 H${W - R} A${R} ${R} 0 0 1 ${W} ${R} V${H}`
for (let i = 1; i < TAILS; i++) {
d += ` L${(W - step * (i - 0.5)).toFixed(3)} ${H - DEPTH}`
d += ` L${(W - step * i).toFixed(3)} ${H}`
}
d += ` V${R} A${R} ${R} 0 0 1 ${R} 0 Z`
const VARIANTS = {
// navy, vermilion, yellow on the paper sheet
light: {ground: '#F8F5EF', bands: ['#001A55', '#ED663F', '#FECD00']},
// the palette's dark counterparts, lifted so they carry on a dark ground
dark: {ground: null, bands: ['#5B8FD0', '#E07A5C', '#D8AD10']},
// greys ordered by the colour version's relative luminance
tinted: {ground: null, bands: ['#737373', '#B8B8B8', '#F2F2F2']},
}
const v = VARIANTS[new URLSearchParams(location.search).get('v') || 'light']
// Mark occupies 62% of the tile's height, which lands it inside Apple's grid
// with the margin the squircle mask wants.
const markH = 1024 * 0.62
const svg = [`<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024">`]
if (v.ground) svg.push(`<rect width="1024" height="1024" fill="${v.ground}"/>`)
svg.push(`<defs><clipPath id="mark"><path d="${d}"/></clipPath></defs>`)
svg.push(`<g transform="translate(${512 - markH * W / H / 2} ${512 - markH / 2}) scale(${markH / H})">`)
svg.push(`<g clip-path="url(#mark)">`)
v.bands.forEach((color, i) => {
svg.push(`<rect x="${(W / 3 * i).toFixed(3)}" y="0" width="${(W / 3).toFixed(3)}" height="${H}" fill="${color}"/>`)
})
svg.push(`</g></g></svg>`)
document.getElementById('tile').innerHTML = svg.join('')
</script>
</body>
</html>
+38
View File
@@ -0,0 +1,38 @@
#!/usr/bin/env bash
# Renders the three app-icon variants from appicon.html into the asset catalog.
#
# scripts/appicon/generate.sh
#
# Chrome is the rasteriser because it is the only thing on a stock Mac that
# renders SVG the same way the browser preview sheets in docs/ do. The PNGs it
# writes are build output — edit appicon.html, not them.
set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
OUT="$HERE/../../Marks/Assets.xcassets/AppIcon.appiconset"
CHROME="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
[ -x "$CHROME" ] || { echo "Google Chrome not found at $CHROME" >&2; exit 1; }
command -v magick >/dev/null || { echo "ImageMagick (magick) not found" >&2; exit 1; }
render() {
local variant=$1 name=$2
"$CHROME" --headless=new --disable-gpu --hide-scrollbars \
--default-background-color=00000000 \
--screenshot="$OUT/$name" --window-size=1024,1024 \
--virtual-time-budget=2000 \
"file://$HERE/appicon.html?v=$variant" >/dev/null 2>&1
echo " $name"
}
echo "rendering app icon ->"
render light AppIcon.png
render dark AppIcon-Dark.png
render tinted AppIcon-Tinted.png
# The light icon is the one shipped as the home-screen artwork, and iOS rejects
# an alpha channel there. The dark and tinted variants are composited by the
# system over a background it supplies, so they must keep theirs.
magick "$OUT/AppIcon.png" -background '#F8F5EF' -alpha remove -alpha off "$OUT/AppIcon.png"
echo "done"