From d3f402c1a367e6897f9e456c73bf903891ce6e07 Mon Sep 17 00:00:00 2001 From: Shakeel Mohamed Date: Mon, 8 Jun 2026 06:54:04 -0700 Subject: [PATCH 1/6] add initial smoke tests --- docs/projects/clock/index.html | 8 +-- package.json | 4 +- src/projects/clock/index.liquid | 3 +- tests/smoke/data.test.js | 35 ++++++++++ tests/smoke/smoke.test.js | 114 ++++++++++++++++++++++++++++++++ 5 files changed, 157 insertions(+), 7 deletions(-) create mode 100644 tests/smoke/data.test.js create mode 100644 tests/smoke/smoke.test.js diff --git a/docs/projects/clock/index.html b/docs/projects/clock/index.html index a3e1b55..4aec3ef 100644 --- a/docs/projects/clock/index.html +++ b/docs/projects/clock/index.html @@ -4,7 +4,7 @@ 10k24 Studio | Clocks - + @@ -15,14 +15,14 @@ - + - + @@ -105,7 +105,7 @@

Clocks

-

Ticking down the year on a 32x32 pixel grid.

+

Clocks based on a 32x32 pixel grid.

diff --git a/package.json b/package.json index a9daeaa..3533167 100644 --- a/package.json +++ b/package.json @@ -26,8 +26,8 @@ "lint:fix": "bunx stylelint --fix src/*.css", "fix": "bun run lint:fix", "optimize": "node scripts/optimize.js", - "test:words": "bunx cspell lint 'docs/**/*.html' --no-progress --no-summary --quiet && echo 'test:words: clean'", - "test": "bun run test:words" + "posttest": "bunx cspell lint 'docs/**/*.html' --no-progress --no-summary --quiet && echo 'posttest: clean'", + "test": "bun test" }, "private": true, "devDependencies": { diff --git a/src/projects/clock/index.liquid b/src/projects/clock/index.liquid index 994a6c9..2d2207d 100644 --- a/src/projects/clock/index.liquid +++ b/src/projects/clock/index.liquid @@ -7,6 +7,7 @@ tags: - creative coding cover: cover.png +description: "Clocks based on a 32x32 pixel grid." media: Creative Coding location: Los Angeles, CA @@ -17,6 +18,6 @@ hideCover: false -

Ticking down the year on a 32x32 pixel grid.

+

Clocks based on a 32x32 pixel grid.

{% include "_includes/partials/square-p5-sketch", sketch:"./clock.js" %} diff --git a/tests/smoke/data.test.js b/tests/smoke/data.test.js new file mode 100644 index 0000000..35eea6f --- /dev/null +++ b/tests/smoke/data.test.js @@ -0,0 +1,35 @@ +const { describe, expect, test } = require("bun:test"); +const { readFileSync } = require("node:fs"); +const { resolve, join } = require("node:path"); + +const DATA_DIR = resolve(__dirname, "../..", "src/_data"); + +function readJson(filename) { + return JSON.parse(readFileSync(join(DATA_DIR, filename), "utf8")); +} + +describe("metadata.json", () => { + const data = readJson("metadata.json"); + + test("baseURL is a non-empty string", () => { + expect(typeof data.baseURL).toBe("string"); + expect(data.baseURL.length).toBeGreaterThan(0); + }); + + test("author.name is a non-empty string", () => { + expect(typeof data.author?.name).toBe("string"); + expect(data.author.name.length).toBeGreaterThan(0); + }); + + test("author.email is a non-empty string", () => { + expect(typeof data.author?.email).toBe("string"); + expect(data.author.email.length).toBeGreaterThan(0); + }); + + test("social links are non-empty strings", () => { + for (const [key, value] of Object.entries(data.social || {})) { + expect(typeof value, `social.${key} must be a string`).toBe("string"); + expect(value.length, `social.${key} must not be empty`).toBeGreaterThan(0); + } + }); +}); diff --git a/tests/smoke/smoke.test.js b/tests/smoke/smoke.test.js new file mode 100644 index 0000000..ca4ba11 --- /dev/null +++ b/tests/smoke/smoke.test.js @@ -0,0 +1,114 @@ +const { describe, expect, test } = require("bun:test"); +const { readdirSync, readFileSync, existsSync } = require("node:fs"); +const { resolve, join, relative } = require("node:path"); + +const DOCS = resolve(__dirname, "../..", "docs"); +const SITEMAP_PATH = join(DOCS, "sitemap.txt"); +const ORIGIN = "https://10k24.com"; + +const EXPECTED_SITEMAP_URLS = [ + `${ORIGIN}`, + `${ORIGIN}/projects/planetary-society/`, + `${ORIGIN}/projects/salgirah-festival/`, + `${ORIGIN}/projects/midjourney/`, +]; + +function collectHtmlFiles(dir) { + const files = []; + for (const entry of readdirSync(dir, { withFileTypes: true })) { + const full = join(dir, entry.name); + if (entry.isDirectory()) { + files.push(...collectHtmlFiles(full)); + } else if (entry.name === "index.html") { + files.push(full); + } + } + return files; +} + +function sitemapUrlToPath(url) { + const pathname = new URL(url).pathname; + const relativePath = pathname === "/" ? "index.html" : join(pathname.slice(1), "index.html"); + return join(DOCS, relativePath); +} + +describe("sitemap", () => { + test("contains expected URLs in order", () => { + const content = readFileSync(SITEMAP_PATH, "utf8"); + const urls = content.trim().split("\n"); + expect(urls).toEqual(EXPECTED_SITEMAP_URLS); + }); + + test("no empty lines or trailing whitespace", () => { + const content = readFileSync(SITEMAP_PATH, "utf8"); + const lines = content.split("\n"); + for (let i = 0; i < lines.length; i++) { + expect(lines[i], `line ${i + 1} has trailing whitespace`).toBe(lines[i].trimEnd()); + } + expect(content.endsWith("\n")).toBe(true); + }); + + test("all URLs share the same origin", () => { + const content = readFileSync(SITEMAP_PATH, "utf8"); + const urls = content.trim().split("\n"); + for (const url of urls) { + expect(new URL(url).origin).toBe(ORIGIN); + } + }); +}); + +describe("route-to-file mapping", () => { + test("every sitemap URL has a corresponding index.html", () => { + const content = readFileSync(SITEMAP_PATH, "utf8"); + const urls = content.trim().split("\n"); + for (const url of urls) { + const filePath = sitemapUrlToPath(url); + expect(existsSync(filePath), `Missing file for ${url}`).toBe(true); + } + }); +}); + +describe("HTML integrity", () => { + const htmlFiles = collectHtmlFiles(DOCS); + + test("at least one HTML file exists", () => { + expect(htmlFiles.length).toBeGreaterThan(0); + }); + + for (const file of htmlFiles) { + const rel = relative(DOCS, file); + + test(`${rel} has doctype`, () => { + const html = readFileSync(file, "utf8"); + expect(html.startsWith("")).toBe(true); + }); + + test(`${rel} has non-empty title`, () => { + const html = readFileSync(file, "utf8"); + const match = html.match(/([^<]*)<\/title>/); + expect(match, "Missing <title>").not.toBeNull(); + expect(match[1].trim().length).toBeGreaterThan(0); + }); + + test(`${rel} has non-empty description meta`, () => { + const html = readFileSync(file, "utf8"); + const match = html.match(/<meta\s+name="description"\s+content="([^"]*)"/); + expect(match, 'Missing <meta name="description">').not.toBeNull(); + expect(match[1].trim().length).toBeGreaterThan(0); + }); + } +}); + +describe("static assets", () => { + const assets = [ + "styles.css", + "robots.txt", + "img/opengraph.png", + ]; + + for (const asset of assets) { + test(`${asset} exists`, () => { + expect(existsSync(join(DOCS, asset)), `Missing ${asset}`).toBe(true); + }); + } +}); From 07bceb3406bbd7784d2f9d1f33acc1b034e029fa Mon Sep 17 00:00:00 2001 From: Shakeel Mohamed <contact@shakeel.xyz> Date: Mon, 8 Jun 2026 07:01:13 -0700 Subject: [PATCH 2/6] Add broken link test + github action test config --- .github/workflows/test.yml | 11 ++ tests/smoke/links.test.js | 199 +++++++++++++++++++++++++++++++++++++ 2 files changed, 210 insertions(+) create mode 100644 .github/workflows/test.yml create mode 100644 tests/smoke/links.test.js diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..80fae07 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,11 @@ +name: test +on: [push, pull_request] +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v2 + - run: bun install + - run: bun run build + - run: bun test diff --git a/tests/smoke/links.test.js b/tests/smoke/links.test.js new file mode 100644 index 0000000..f378d46 --- /dev/null +++ b/tests/smoke/links.test.js @@ -0,0 +1,199 @@ +const { describe, expect, test } = require("bun:test"); +const { readdirSync, readFileSync, existsSync } = require("node:fs"); +const { resolve, join, relative, dirname } = require("node:path"); + +const DOCS = resolve(__dirname, "../..", "docs"); + +function collectHtmlFiles(dir) { + const files = []; + for (const entry of readdirSync(dir, { withFileTypes: true })) { + const full = join(dir, entry.name); + if (entry.isDirectory()) { + files.push(...collectHtmlFiles(full)); + } else if (entry.name === "index.html") { + files.push(full); + } + } + return files; +} + +function isExternal(value) { + return /^(https?:|mailto:|tel:|javascript:|#)/.test(value) || value.startsWith("//"); +} + +function isAnchorOnly(value) { + return value.startsWith("#"); +} + +function hrefsFromHtml(html) { + const hrefs = []; + const re = /<a\b[^>]*\bhref=["']([^"']+)["']/gi; + let match; + while ((match = re.exec(html)) !== null) { + hrefs.push(match[1]); + } + return hrefs; +} + +function assetsFromHtml(html) { + const assets = []; + const patterns = [ + /<img\b[^>]*\bsrc=["']([^"']+)["']/gi, + /<link\b[^>]*\bhref=["']([^"']+)["']/gi, + /<script\b[^>]*\bsrc=["']([^"']+)["']/gi, + /<source\b[^>]*\bsrc=["']([^"']+)["']/gi, + /<video\b[^>]*\bsrc=["']([^"']+)["']/gi, + /<dotlottie-wc\b[^>]*\bsrc=["']([^"']+)["']/gi, + /data-sketch-src=["']([^"']+)["']/gi, + ]; + for (const re of patterns) { + let match; + while ((match = re.exec(html)) !== null) { + assets.push(match[1]); + } + } + return assets; +} + +function idsFromHtml(html) { + const ids = []; + const re = /\sid=["']([^"']+)["']/gi; + let match; + while ((match = re.exec(html)) !== null) { + ids.push(match[1]); + } + return ids; +} + +function resolveRef(ref, sourceFile) { + if (ref.startsWith("#")) { + return { kind: "anchor", target: ref }; + } + if (ref.startsWith("/")) { + const rootRelative = join(DOCS, ref); + if (existsSync(rootRelative)) { + return { kind: "file", file: rootRelative }; + } + if (!ref.endsWith("/") && !ref.includes(".")) { + const withIndex = join(DOCS, ref, "index.html"); + if (existsSync(withIndex)) { + return { kind: "file", file: withIndex }; + } + } + if (!ref.includes(".")) { + const withIndex = join(DOCS, ref.slice(1), "index.html"); + if (existsSync(withIndex)) { + return { kind: "file", file: withIndex }; + } + } + return { kind: "file", file: rootRelative }; + } + if (ref.startsWith(".")) { + const resolved = resolve(dirname(sourceFile), ref); + return { kind: "file", file: resolved }; + } + const resolved = resolve(dirname(sourceFile), ref); + return { kind: "file", file: resolved }; +} + +function isInComment(html, index) { + const before = html.slice(0, index); + const lastCommentStart = before.lastIndexOf("<!--"); + const lastCommentEnd = before.lastIndexOf("-->"); + if (lastCommentStart === -1) return false; + return lastCommentStart > lastCommentEnd; +} + +function findRefPositions(html, ref) { + const positions = []; + let idx = 0; + while (true) { + idx = html.indexOf(ref, idx); + if (idx === -1) break; + positions.push(idx); + idx += ref.length; + } + return positions; +} + +function isInAnyComment(html, ref) { + const positions = findRefPositions(html, ref); + for (const pos of positions) { + if (!isInComment(html, pos)) return false; + } + return positions.length > 0; +} + +describe("internal links", () => { + const htmlFiles = collectHtmlFiles(DOCS); + const broken = []; + + for (const file of htmlFiles) { + const html = readFileSync(file, "utf8"); + const hrefs = hrefsFromHtml(html); + const rel = relative(DOCS, file); + + for (const href of hrefs) { + if (isExternal(href)) continue; + if (isInAnyComment(html, href)) continue; + + const result = resolveRef(href, file); + if (result.kind === "file" && !existsSync(result.file)) { + broken.push(`${rel}: ${href} → ${relative(DOCS, result.file)} (not found)`); + } + } + } + + test("all internal hrefs resolve to existing files", () => { + expect(broken, `Broken internal links:\n${broken.join("\n")}`).toEqual([]); + }); +}); + +describe("anchor fragments", () => { + const htmlFiles = collectHtmlFiles(DOCS); + + for (const file of htmlFiles) { + const html = readFileSync(file, "utf8"); + const ids = new Set(idsFromHtml(html)); + const hrefs = hrefsFromHtml(html); + const rel = relative(DOCS, file); + + const anchors = hrefs + .map(h => h.match(/^#(.+)/)) + .filter(Boolean) + .map(m => m[1]); + + const missingAnchors = [...new Set(anchors.filter(id => !ids.has(id)))]; + + test(`${rel} anchor fragments exist`, () => { + expect(missingAnchors, + `${rel} missing anchor targets: ${missingAnchors.join(", ")}` + ).toEqual([]); + }); + } +}); + +describe("asset references", () => { + const htmlFiles = collectHtmlFiles(DOCS); + const broken = []; + + for (const file of htmlFiles) { + const html = readFileSync(file, "utf8"); + const refs = assetsFromHtml(html); + const rel = relative(DOCS, file); + + for (const ref of refs) { + if (isExternal(ref)) continue; + if (isInAnyComment(html, ref)) continue; + + const result = resolveRef(ref, file); + if (result.kind === "file" && !existsSync(result.file)) { + broken.push(`${rel}: ${ref} → ${relative(DOCS, result.file)} (not found)`); + } + } + } + + test("all internal asset references resolve to existing files", () => { + expect(broken, `Broken asset references:\n${broken.join("\n")}`).toEqual([]); + }); +}); From c886b03151c30fe0874c9b74ff9be265eb0556de Mon Sep 17 00:00:00 2001 From: Shakeel Mohamed <contact@shakeel.xyz> Date: Mon, 8 Jun 2026 07:02:58 -0700 Subject: [PATCH 3/6] todo --- TODO.md | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/TODO.md b/TODO.md index d2ccdea..167c124 100644 --- a/TODO.md +++ b/TODO.md @@ -24,9 +24,8 @@ addressed. - [ ] logo animation should be vertically centered at top of page - [ ] maybe indicate down to hint for scroll, z-index 9999 - [ ] experiment with lottiefile animation as a permanent background underneath content hmm -- [ ] 404 page +- [ ] 404 page - similar motion idea to homepage animation - [ ] ideas list page -- [ ] broken link + other smoke tests - [ ] idea individual pages (obv with coherent thoughts) - [ ] for ideas pages, generate og images at build time - see: https://github.com/vercel/satori#documentation - [ ] Workflow - each new project (especially the technical ones), get 2 posts: project page and idea/blog page announcing it. That then gets pushed to socials, etc. Tight workflow, and should help with SEO @@ -79,16 +78,6 @@ addressed. === -10k24 Studio Positioning: -Homepage (KPI framing): -"10k24 builds technical creative systems for organizations that need distinctive brands without traditional agency overhead. -We specialize in: +About page notes -Visual identity + technical implementation (design + code) -Generative design tools for creative teams -Brand systems for cultural organizations and startups - -Recent work: International festival reaching 1M+ people across 21 countries, luxury interiors brand, wellness expansion, consumer tech launch." -About page (KPI story): "Founded by Shakeel Mohamed, 10k24 combines 10 years of software engineering across developer tools, performance with graduate-level design training to create brand systems that work at scale. -Our approach: Technical rigor meets design intention. We build tools, not just deliverables." From 38005b7f5232cebdd92232990c58e62562cd9e9f Mon Sep 17 00:00:00 2001 From: Shakeel Mohamed <contact@shakeel.xyz> Date: Mon, 8 Jun 2026 07:10:00 -0700 Subject: [PATCH 4/6] Potential fix for pull request finding 'Unused variable, import, function or class' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> --- tests/smoke/links.test.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/smoke/links.test.js b/tests/smoke/links.test.js index f378d46..7a78f5f 100644 --- a/tests/smoke/links.test.js +++ b/tests/smoke/links.test.js @@ -21,10 +21,6 @@ function isExternal(value) { return /^(https?:|mailto:|tel:|javascript:|#)/.test(value) || value.startsWith("//"); } -function isAnchorOnly(value) { - return value.startsWith("#"); -} - function hrefsFromHtml(html) { const hrefs = []; const re = /<a\b[^>]*\bhref=["']([^"']+)["']/gi; From b838ce51ecf3a01fcb76fd7ba60f8c797accc5a1 Mon Sep 17 00:00:00 2001 From: Shakeel Mohamed <contact@shakeel.xyz> Date: Mon, 8 Jun 2026 07:27:15 -0700 Subject: [PATCH 5/6] update test infra --- .github/workflows/test.yml | 3 +-- .stylelintrc | 1 + bunfig.toml | 2 ++ docs/styles.css | 12 ------------ package.json | 1 + src/styles.css | 12 ------------ 6 files changed, 5 insertions(+), 26 deletions(-) create mode 100644 bunfig.toml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 80fae07..f627bab 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,5 +7,4 @@ jobs: - uses: actions/checkout@v4 - uses: oven-sh/setup-bun@v2 - run: bun install - - run: bun run build - - run: bun test + - run: bun run test diff --git a/.stylelintrc b/.stylelintrc index cfcd676..38c6d5d 100644 --- a/.stylelintrc +++ b/.stylelintrc @@ -15,6 +15,7 @@ "alpha-value-notation": null, "at-rule-no-vendor-prefix": null, "selector-max-id": 0, + "no-descending-specificity": null, "declaration-no-important": true } } diff --git a/bunfig.toml b/bunfig.toml new file mode 100644 index 0000000..98123b5 --- /dev/null +++ b/bunfig.toml @@ -0,0 +1,2 @@ +[test] +root = "./tests" diff --git a/docs/styles.css b/docs/styles.css index 7c3c051..feec310 100644 --- a/docs/styles.css +++ b/docs/styles.css @@ -37,7 +37,6 @@ a { text-decoration: none; } -/* TODO: bring back once project pages are added */ a:hover { text-decoration: underline; text-underline-offset: 0.25rem; @@ -179,8 +178,6 @@ section { .section-item { max-width: 50%; - - /* margin-bottom: 1.5rem; */ margin-bottom: 4rem; } @@ -191,7 +188,6 @@ section { } .hero { - /*min-height: 100vh;*/ margin-top: -4rem; } @@ -249,10 +245,6 @@ h3.project-title, h3.idea-title { text-decoration: none; } -/* h3.project-title:hover { - text-decoration: underline; -} */ - p.project-meta, p.idea-meta { margin-top: 0.125rem; margin-bottom: 0.125rem; @@ -271,10 +263,6 @@ p.project-meta, p.idea-meta { max-width: 60vw; } -.portfolio-img { - width: 100%; -} - .video-16-9 { width: 100%; height: auto; diff --git a/package.json b/package.json index 3533167..17135c1 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "lint:fix": "bunx stylelint --fix src/*.css", "fix": "bun run lint:fix", "optimize": "node scripts/optimize.js", + "pretest": "bun run build && bun run lint", "posttest": "bunx cspell lint 'docs/**/*.html' --no-progress --no-summary --quiet && echo 'posttest: clean'", "test": "bun test" }, diff --git a/src/styles.css b/src/styles.css index 7c3c051..feec310 100644 --- a/src/styles.css +++ b/src/styles.css @@ -37,7 +37,6 @@ a { text-decoration: none; } -/* TODO: bring back once project pages are added */ a:hover { text-decoration: underline; text-underline-offset: 0.25rem; @@ -179,8 +178,6 @@ section { .section-item { max-width: 50%; - - /* margin-bottom: 1.5rem; */ margin-bottom: 4rem; } @@ -191,7 +188,6 @@ section { } .hero { - /*min-height: 100vh;*/ margin-top: -4rem; } @@ -249,10 +245,6 @@ h3.project-title, h3.idea-title { text-decoration: none; } -/* h3.project-title:hover { - text-decoration: underline; -} */ - p.project-meta, p.idea-meta { margin-top: 0.125rem; margin-bottom: 0.125rem; @@ -271,10 +263,6 @@ p.project-meta, p.idea-meta { max-width: 60vw; } -.portfolio-img { - width: 100%; -} - .video-16-9 { width: 100%; height: auto; From d25edfb2be90754d41565f556b8b7aa0110977a4 Mon Sep 17 00:00:00 2001 From: Shakeel Mohamed <contact@shakeel.xyz> Date: Mon, 8 Jun 2026 07:36:55 -0700 Subject: [PATCH 6/6] language --- docs/projects/midjourney/index.html | 10 +++++----- docs/projects/planetary-society/index.html | 8 ++++---- docs/projects/salgirah-festival/index.html | 2 +- src/projects/midjourney/index.liquid | 10 +++++----- src/projects/planetary-society/index.liquid | 8 ++++---- src/projects/salgirah-festival/index.liquid | 2 +- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/docs/projects/midjourney/index.html b/docs/projects/midjourney/index.html index 3b8c0b5..c065cd6 100644 --- a/docs/projects/midjourney/index.html +++ b/docs/projects/midjourney/index.html @@ -104,9 +104,9 @@ <h1>Midjourney</h1> <div class="grid-2"> <div> - <p>Midjourney has received tremendous press since 2021, yet non-technical audiences are often discouraged by their visual communication. This project positions them to remain competitive in a rapidly growing AI landscape by making the brand more approachable.</p> + <p>Midjourney has received tremendous press since 2021, yet non-technical audiences are often discouraged by their visual communication. This project positions them to remain competitive in a rapidly growing AI landscape by making the identity more approachable.</p> <br> - <p>The strategy draws from the founder’s quotes and mission statement. Light refraction serves as the central metaphor — symbolizing the expansive creativity their tools make possible. Brand colors connect across cultures, ages, and industries, distinguishing Midjourney from the sea of tech-company blue.</p> + <p>The strategy draws from the founder’s quotes and mission statement. Light refraction serves as the central metaphor — symbolizing the expansive creativity their tools make possible. Visual identity colors connect across cultures, ages, and industries, distinguishing Midjourney from the sea of tech-company blue.</p> </div> <div> <p>The new logo is an evolution of the previous sailboat symbol — distilled to its simplest abstraction.</p> @@ -115,13 +115,13 @@ <h1>Midjourney</h1> <div class="grid-1"> <img src="img/midjourney-003.png" alt="" class="portfolio-img"> - <p class="caption">The Sail. Through extensive identity exploration, this was the simplest abstraction of their sailboat logo.</p> + <p class="caption">The Sail. Through extensive logo exploration, this was the simplest abstraction of their sailboat logo.</p> </div> <div class="grid-2"> <div> <img src="img/midjourney-007.png" alt="" class="portfolio-img"> - <p class="caption">Brand colors emerged from light refraction, symbolizing expansive creativity possible with their products.</p> + <p class="caption">Visual identity colors emerged from light refraction, symbolizing expansive creativity possible with their products.</p> </div> <div> <img src="img/midjourney-008.png" alt="" class="portfolio-img"> @@ -130,7 +130,7 @@ <h1>Midjourney</h1> </div> <div class="grid-1"> - <img src="img/midjourney-009.png" alt="Logo family expressed through all brand colors: vertical lockup, icon, wordmark." class="portfolio-img"> + <img src="img/midjourney-009.png" alt="Logo family expressed through all visual identity colors: vertical lockup, icon, wordmark." class="portfolio-img"> </div> <div class="grid-2"> diff --git a/docs/projects/planetary-society/index.html b/docs/projects/planetary-society/index.html index 8374a61..9461404 100644 --- a/docs/projects/planetary-society/index.html +++ b/docs/projects/planetary-society/index.html @@ -106,10 +106,10 @@ <h1>Planetary Society</h1> <div class="grid-2"> <div> - <p>The Planetary Society is a non-profit advocacy organization for space exploration, based in Pasadena, California. The identity expands the audience beyond enthusiasts — exploring the profound human experience of our relationship to the cosmos. A primary serif typeface gives every brand touchpoint an elevated, timeless feeling. Removing “The” from the name created a cleaner shorthand.</p> + <p>The Planetary Society is a non-profit advocacy organization for space exploration, based in Pasadena, California. The identity expands the audience beyond enthusiasts — exploring the profound human experience of our relationship to the cosmos. A primary serif typeface gives every touchpoint an elevated, timeless feeling. Removing “The” from the name created a cleaner shorthand.</p> </div> <div> - <p>A special brand style was designed for the Society’s 45th anniversary, including a custom modular variable typeface inspired by planetary orbits. The anniversary identity envisions a weekend celebration on Catalina Island for members and donors.</p> + <p>A special campaign style was designed for the Society’s 45th anniversary, including a custom modular variable typeface inspired by planetary orbits. The anniversary identity envisions a weekend celebration on Catalina Island for members and donors.</p> </div> </div> @@ -125,7 +125,7 @@ <h1>Planetary Society</h1> </div> <div> <img src="img/Planetary_Society_ID_overview.png" alt="" class="portfolio-img"> - <p class="caption">Overview of the identity system. A serif typeface breaks the retro-sci-fi standard in aerospace, giving all brand messaging a timeless, contemplative quality.</p> + <p class="caption">Overview of the identity system. A serif typeface breaks the retro-sci-fi standard in aerospace, giving all messaging a timeless, contemplative quality.</p> </div> </div> @@ -159,7 +159,7 @@ <h1>Planetary Society</h1> <hr> <p><strong>45th Anniversary Campaign</strong></p> <br> -<p class="caption">The 45th anniversary identity carries a distinct brand and typographic voice. The highlight of the year would be a retreat on Catalina Island for members and donors, “Under the Stars.”</p> +<p class="caption">The 45th anniversary identity carries a distinct look and typographic voice. The highlight of the year would be a retreat on Catalina Island for members and donors, “Under the Stars.”</p> <div class="grid-1"> <img src="img/TPS - 4.png" alt="" class="portfolio-img"> diff --git a/docs/projects/salgirah-festival/index.html b/docs/projects/salgirah-festival/index.html index dcf2dd8..44ab61e 100644 --- a/docs/projects/salgirah-festival/index.html +++ b/docs/projects/salgirah-festival/index.html @@ -121,7 +121,7 @@ <h1>Salgirah Festival</h1> <br> <hr> -<p><strong>Global Brand Activation</strong></p> +<p><strong>Global Activation</strong></p> <br> <div class="grid-1"> diff --git a/src/projects/midjourney/index.liquid b/src/projects/midjourney/index.liquid index da5ba02..9986dea 100644 --- a/src/projects/midjourney/index.liquid +++ b/src/projects/midjourney/index.liquid @@ -12,9 +12,9 @@ client: Conceptual <div class="grid-2"> <div> - <p>Midjourney has received tremendous press since 2021, yet non-technical audiences are often discouraged by their visual communication. This project positions them to remain competitive in a rapidly growing AI landscape by making the brand more approachable.</p> + <p>Midjourney has received tremendous press since 2021, yet non-technical audiences are often discouraged by their visual communication. This project positions them to remain competitive in a rapidly growing AI landscape by making the identity more approachable.</p> <br> - <p>The strategy draws from the founder’s quotes and mission statement. Light refraction serves as the central metaphor — symbolizing the expansive creativity their tools make possible. Brand colors connect across cultures, ages, and industries, distinguishing Midjourney from the sea of tech-company blue.</p> + <p>The strategy draws from the founder’s quotes and mission statement. Light refraction serves as the central metaphor — symbolizing the expansive creativity their tools make possible. Visual identity colors connect across cultures, ages, and industries, distinguishing Midjourney from the sea of tech-company blue.</p> </div> <div> <p>The new logo is an evolution of the previous sailboat symbol — distilled to its simplest abstraction.</p> @@ -23,13 +23,13 @@ client: Conceptual <div class="grid-1"> <img src="img/midjourney-003.png" alt="" class="portfolio-img"> - <p class="caption">The Sail. Through extensive identity exploration, this was the simplest abstraction of their sailboat logo.</p> + <p class="caption">The Sail. Through extensive logo exploration, this was the simplest abstraction of their sailboat logo.</p> </div> <div class="grid-2"> <div> <img src="img/midjourney-007.png" alt="" class="portfolio-img"> - <p class="caption">Brand colors emerged from light refraction, symbolizing expansive creativity possible with their products.</p> + <p class="caption">Visual identity colors emerged from light refraction, symbolizing expansive creativity possible with their products.</p> </div> <div> <img src="img/midjourney-008.png" alt="" class="portfolio-img"> @@ -38,7 +38,7 @@ client: Conceptual </div> <div class="grid-1"> - <img src="img/midjourney-009.png" alt="Logo family expressed through all brand colors: vertical lockup, icon, wordmark." class="portfolio-img"> + <img src="img/midjourney-009.png" alt="Logo family expressed through all visual identity colors: vertical lockup, icon, wordmark." class="portfolio-img"> </div> <div class="grid-2"> diff --git a/src/projects/planetary-society/index.liquid b/src/projects/planetary-society/index.liquid index eb337e7..d407279 100644 --- a/src/projects/planetary-society/index.liquid +++ b/src/projects/planetary-society/index.liquid @@ -12,10 +12,10 @@ client: The Planetary Society (proposed) <div class="grid-2"> <div> - <p>The Planetary Society is a non-profit advocacy organization for space exploration, based in Pasadena, California. The identity expands the audience beyond enthusiasts — exploring the profound human experience of our relationship to the cosmos. A primary serif typeface gives every brand touchpoint an elevated, timeless feeling. Removing “The” from the name created a cleaner shorthand.</p> + <p>The Planetary Society is a non-profit advocacy organization for space exploration, based in Pasadena, California. The identity expands the audience beyond enthusiasts — exploring the profound human experience of our relationship to the cosmos. A primary serif typeface gives every touchpoint an elevated, timeless feeling. Removing “The” from the name created a cleaner shorthand.</p> </div> <div> - <p>A special brand style was designed for the Society’s 45th anniversary, including a custom modular variable typeface inspired by planetary orbits. The anniversary identity envisions a weekend celebration on Catalina Island for members and donors.</p> + <p>A special campaign style was designed for the Society’s 45th anniversary, including a custom modular variable typeface inspired by planetary orbits. The anniversary identity envisions a weekend celebration on Catalina Island for members and donors.</p> </div> </div> @@ -31,7 +31,7 @@ client: The Planetary Society (proposed) </div> <div> <img src="img/Planetary_Society_ID_overview.png" alt="" class="portfolio-img"> - <p class="caption">Overview of the identity system. A serif typeface breaks the retro-sci-fi standard in aerospace, giving all brand messaging a timeless, contemplative quality.</p> + <p class="caption">Overview of the identity system. A serif typeface breaks the retro-sci-fi standard in aerospace, giving all messaging a timeless, contemplative quality.</p> </div> </div> @@ -65,7 +65,7 @@ client: The Planetary Society (proposed) <hr> <p><strong>45th Anniversary Campaign</strong></p> <br> -<p class="caption">The 45th anniversary identity carries a distinct brand and typographic voice. The highlight of the year would be a retreat on Catalina Island for members and donors, “Under the Stars.”</p> +<p class="caption">The 45th anniversary identity carries a distinct look and typographic voice. The highlight of the year would be a retreat on Catalina Island for members and donors, “Under the Stars.”</p> <div class="grid-1"> <img src="img/TPS - 4.png" alt="" class="portfolio-img"> diff --git a/src/projects/salgirah-festival/index.liquid b/src/projects/salgirah-festival/index.liquid index 7ed6aae..5e9ab15 100644 --- a/src/projects/salgirah-festival/index.liquid +++ b/src/projects/salgirah-festival/index.liquid @@ -27,7 +27,7 @@ client: The Ismaili Community <br> <hr> -<p><strong>Global Brand Activation</strong></p> +<p><strong>Global Activation</strong></p> <br> <div class="grid-1">