<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Engineer — Brand — Portfolio and notes</title>
    <link>https://brand.engineer.company/</link>
    <description>Engineer ApS — software development &amp; IT consulting in Copenhagen, Denmark. A portfolio of proven engineering achievements across software, data, cloud and IT.</description>
    <language>en</language>
    <copyright>Copyright © 2025 – present · Engineer ApS</copyright>
    <generator>Hugo 0.166.0</generator>
    <docs>https://www.rssboard.org/rss-specification</docs>
    <ttl>60</ttl>
    <lastBuildDate>Sun, 13 Sep 2026 01:45:55 +0200</lastBuildDate>
    <atom:link href="https://brand.engineer.company/index.xml" rel="self" type="application/rss+xml" />
    <image>
      <url>https://brand.engineer.company/assets/icons/apple/apple-touch-icon-144x144.png</url>
      <title>Engineer — Brand — Portfolio and notes</title>
      <link>https://brand.engineer.company/</link>
    </image>
    <item>
      <title>Increased brand popularity by 200x through successful brand development across offline and online platforms.</title>
      <link>https://brand.engineer.company/portfolio/increased-brand-popularity-by-200x-through-successful-brand-1/</link>
      <guid isPermaLink="true">https://brand.engineer.company/portfolio/increased-brand-popularity-by-200x-through-successful-brand-1/</guid>
      <pubDate>Sun, 13 Sep 2026 01:45:50 +0200</pubDate>
      <description>Grew brand reach roughly 200x across offline and online platforms, turning an unknown newcomer into a recognised name.</description>
      <category domain="https://brand.engineer.company/categories/">Brand &amp; Marketing</category>
      <category domain="https://brand.engineer.company/categories/">Product &amp; Requirements</category>
      <category domain="https://brand.engineer.company/categories/">Stakeholder &amp; Reporting</category>
      <category domain="https://brand.engineer.company/services/">Brand, Marketing &amp; SEO</category>
      <category domain="https://brand.engineer.company/services/">Product Strategy &amp; Requirements</category>
      <content:encoded><![CDATA[<p><strong>Situation.</strong> Engineer ApS was a brand‑new consultancy with real technical depth and almost nobody who&rsquo;d heard of it. That&rsquo;s a specific kind of frustrating: the skill is there, the work would be good, but none of it matters if the clients and partners who&rsquo;d want it don&rsquo;t know you exist. A young firm has to be seen before it can win anything.</p>
<p><strong>Task.</strong> The task was to build a brand people would actually recognise — across both the offline and online sides — and to turn the firm&rsquo;s engineering credibility into visible market presence, rather than leaving it as a well‑kept secret.</p>
<p><strong>Action.</strong> The brand was built deliberately and kept consistent. It started with a clear identity — a voice, a visual language, and a portfolio that led with concrete engineering outcomes instead of the vague &ldquo;we deliver value&rdquo; language everyone else uses. Then it ran across the channels that matter for this kind of firm — the website, LinkedIn, GitHub, in‑person events — with every touchpoint saying the same thing rather than each drifting off on its own. The throughline was leading with real case studies and real results, so the credibility was something you could see evidence for, not just a claim.</p>
<p><strong>Result.</strong> Brand reach grew around 200‑fold across the offline and online platforms — an unknown newcomer turned into something people actually recognised. And it wasn&rsquo;t vanity reach; the visibility started producing a steady flow of inbound conversations and opportunities that simply hadn&rsquo;t been there before.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Drove brand engagement and loyalty by 100% through market trend analysis and consumer behavior insights.</title>
      <link>https://brand.engineer.company/portfolio/drove-brand-engagement-and-loyalty-by-100-through-2/</link>
      <guid isPermaLink="true">https://brand.engineer.company/portfolio/drove-brand-engagement-and-loyalty-by-100-through-2/</guid>
      <pubDate>Sun, 13 Sep 2026 01:45:50 +0200</pubDate>
      <description>Doubled brand engagement and loyalty (&#43;100%) using market-trend analysis and consumer-behaviour insight to win a returning audience.</description>
      <category domain="https://brand.engineer.company/categories/">Brand &amp; Marketing</category>
      <category domain="https://brand.engineer.company/categories/">Data Analytics</category>
      <category domain="https://brand.engineer.company/categories/">Product &amp; Requirements</category>
      <category domain="https://brand.engineer.company/categories/">Stakeholder &amp; Reporting</category>
      <category domain="https://brand.engineer.company/services/">Brand, Marketing &amp; SEO</category>
      <category domain="https://brand.engineer.company/services/">Data Analytics &amp; BI Dashboards</category>
      <category domain="https://brand.engineer.company/services/">Product Strategy &amp; Requirements</category>
      <content:encoded><![CDATA[<p><strong>Situation.</strong> As the visibility climbed, Engineer ApS was reaching more people — but the engagement was thin. People noticed and moved on; the early interest wasn&rsquo;t turning into relationships that lasted. Reach without engagement is just noise, and the brand was making noise more than connections.</p>
<p><strong>Task.</strong> The aim was to deepen the engagement and the loyalty, and to do it by actually looking at what the market and the audience were responding to rather than trusting gut instinct.</p>
<p><strong>Action.</strong> So the brand became data‑informed instead of intuition‑led. That meant looking at the market trends and how the audience actually behaved across the channels — not what anyone assumed they&rsquo;d like, but what they demonstrably engaged with. It surfaced the topics and formats that drew real attention, and the content and outreach got steered toward those. The important part was tightening the loop: watch what landed, publish more of that shape next time, and let each cycle be a bit better‑aimed than the last.</p>
<p><strong>Result.</strong> Engagement and loyalty doubled — a 100% improvement — with an audience that came back and engaged rather than glancing once and leaving, and noticeably stronger relationships with prospects and partners. The brand stopped broadcasting into the void and started building something that returned.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Drove client web success by combining custom website development and design with SEO, content strategy and copywriting.</title>
      <link>https://brand.engineer.company/portfolio/drove-client-web-success-by-combining-custom-website-91/</link>
      <guid isPermaLink="true">https://brand.engineer.company/portfolio/drove-client-web-success-by-combining-custom-website-91/</guid>
      <pubDate>Sun, 13 Sep 2026 01:45:50 +0200</pubDate>
      <description>Drove client web success by combining custom website development and design with SEO, content strategy and copywriting.</description>
      <category domain="https://brand.engineer.company/categories/">Brand &amp; Marketing</category>
      <category domain="https://brand.engineer.company/categories/">Frontend Engineering</category>
      <category domain="https://brand.engineer.company/categories/">Product &amp; Requirements</category>
      <category domain="https://brand.engineer.company/categories/">UX / UI Design</category>
      <category domain="https://brand.engineer.company/categories/">Web Development</category>
      <category domain="https://brand.engineer.company/services/">Brand, Marketing &amp; SEO</category>
      <category domain="https://brand.engineer.company/services/">UI/UX Design &amp; Design Systems</category>
      <category domain="https://brand.engineer.company/services/">Website Development &amp; CMS</category>
      <content:encoded><![CDATA[<p><strong>Situation.</strong> Clients didn&rsquo;t really want a website; they wanted the thing a website is supposed to do for them — to be found, to bring in customers, to actually work as a channel. A beautiful site nobody can find is a failure that looks like a success.</p>
<p><strong>Task.</strong> Combining the build and the growth side into one offering was the task, rather than handing over a site and wishing them luck.</p>
<p><strong>Action.</strong> The two halves were put together — custom website development and design on one side, SEO, content strategy and copywriting on the other — so a client got something that was both well‑built and actually discoverable. Those usually get treated as separate jobs, which is how you end up with a gorgeous site that ranks nowhere, or a well‑optimised site that&rsquo;s unpleasant to use. Doing both meant the site was designed from the start to be found, not optimised as an afterthought.</p>
<p><strong>Result.</strong> Clients ended up with stronger visibility and more engagement — their sites working as genuine growth channels rather than online brochures. Building the thing and making it findable in one go is what turned a website from a cost into something that actually earned its keep.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Replaced 27 rendered font sizes whose nearest neighbours were 0.6% apart with a six‑step Major Third scale, and wrote the linter that fails the twenty‑eighth.</title>
      <link>https://brand.engineer.company/portfolio/replaced-27-font-sizes-with-a-six-step-scale-129/</link>
      <guid isPermaLink="true">https://brand.engineer.company/portfolio/replaced-27-font-sizes-with-a-six-step-scale-129/</guid>
      <pubDate>Sun, 13 Sep 2026 01:45:50 +0200</pubDate>
      <description>Six sizes where there were twenty-seven, with a stated ratio, a stated measure and a check that refuses the next unplanned value.</description>
      <category domain="https://brand.engineer.company/categories/">Automation &amp; CI/CD</category>
      <category domain="https://brand.engineer.company/categories/">Design Systems &amp; UI</category>
      <category domain="https://brand.engineer.company/categories/">Documentation</category>
      <category domain="https://brand.engineer.company/categories/">Frontend Engineering</category>
      <category domain="https://brand.engineer.company/categories/">Testing &amp; QA</category>
      <category domain="https://brand.engineer.company/categories/">UX / UI Design</category>
      <category domain="https://brand.engineer.company/services/">Brand, Marketing &amp; SEO</category>
      <category domain="https://brand.engineer.company/services/">Frontend Development</category>
      <category domain="https://brand.engineer.company/services/">UI/UX Design &amp; Design Systems</category>
      <content:encoded><![CDATA[<p><strong>Situation.</strong> A measurement of the site&rsquo;s rendered text found twenty‑seven distinct font sizes in use. Several were separated by less than one per cent — five values between 0.8 and 0.85 of the body size were all live at once, which is a difference no reader can perceive and every future editor will add to. Two of the headings were not sized by the stylesheet at all and were falling through to the browser&rsquo;s own defaults, a ratio of 1.33 that matched nothing else on the page.</p>
<p><strong>Task.</strong> The sizes had to become a scale with a stated ratio, and something had to prevent the twenty‑eighth.</p>
<p><strong>Action.</strong> The scale is a Major Third, ratio 1.25, six named steps from fine print to display, each a token rather than a value. Headings are mapped explicitly onto steps instead of inheriting whatever the browser thinks, which is what fixed the two that had no size of their own. Only the bottom step is floored, so fine print stays legible on a phone without the whole scale being pinned. Logotypes are exempt by name rather than by accident. The linter is the part that makes it hold: it renders the site and fails on the twenty‑eighth distinct size. It has already earned its place twice — it caught a heading arriving at 1.17 of the body size, which is not a step of anything, and named the ratio in the failure message; and it caught inline code at 0.9, which is precisely the kind of value the scale exists to prevent. Alongside the scale went a measure of about seventy‑two characters, replacing an inherited fixed width that produced ninety‑one characters on a review page and a hundred and ten on the contact page.</p>
<p><strong>Result.</strong> Six sizes where there were twenty‑seven, with a stated ratio, a stated measure and a check that refuses the next unplanned value. The constraint is real and occasionally inconvenient: a design that wants a size between two steps has to move to a step or argue for changing the scale, and that argument has been had and lost more than once.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Generated 495 achievement pages across three languages from a read‑only SQLite export, with the page address authored as data so that correcting a sentence no longer moved the page and broke the link.</title>
      <link>https://brand.engineer.company/portfolio/generated-321-achievement-pages-from-a-sqlite-export-132/</link>
      <guid isPermaLink="true">https://brand.engineer.company/portfolio/generated-321-achievement-pages-from-a-sqlite-export-132/</guid>
      <pubDate>Sun, 13 Sep 2026 01:45:50 +0200</pubDate>
      <description>Four hundred and ninety-five pages generate from one source, correcting a statement costs nothing, and the addresses this site has ever published continue to…</description>
      <category domain="https://brand.engineer.company/categories/">Data Pipelines (ETL/ELT)</category>
      <category domain="https://brand.engineer.company/categories/">Databases</category>
      <category domain="https://brand.engineer.company/categories/">Frontend Engineering</category>
      <category domain="https://brand.engineer.company/categories/">Internationalization</category>
      <category domain="https://brand.engineer.company/categories/">Python</category>
      <category domain="https://brand.engineer.company/categories/">SQL</category>
      <category domain="https://brand.engineer.company/categories/">Web Development</category>
      <category domain="https://brand.engineer.company/services/">Brand, Marketing &amp; SEO</category>
      <category domain="https://brand.engineer.company/services/">Data Pipeline Development (ETL/ELT)</category>
      <category domain="https://brand.engineer.company/services/">Internationalization &amp; Localization</category>
      <category domain="https://brand.engineer.company/services/">Website Development &amp; CMS</category>
      <content:encoded><![CDATA[<p><strong>Situation.</strong> The company&rsquo;s portfolio content is authored in a separate database — the same one that produces the CV — and the website has to publish it as pages, in three languages, without the two copies drifting apart. The naive approach, writing the pages by hand and keeping them in step, fails on the first correction.</p>
<p><strong>Task.</strong> The website had to generate its content from the database as a build input, with page addresses that survive the sentences on them being rewritten.</p>
<p><strong>Action.</strong> An exporter reads the database read‑only and writes one page per achievement per language — 495 pages — plus the taxonomy and services data the templates need. It uses only the standard library, so the site&rsquo;s build has no dependency on the generator&rsquo;s environment, and the committed output means the site builds standalone. The single most consequential decision in it concerns addresses. The site used to derive a page&rsquo;s URL from the leading words of its English statement, so correcting a sentence silently moved the page and broke every link to it — a site whose argument for itself is that it corrects things, charging itself a dead link every time it did. The address is now authored as data: one row per address per achievement, the first is current and every later one is a retired address the site emits as a redirect. Two other traps are recorded from the same work. A period comparison against a text column silently matched all thirty‑two rows because of how the database assigns type affinity across a comparison. And the language list is now the single axis the write loop, the data files and the queries all derive from, so adding a fourth language is one map entry rather than a search.</p>
<p><strong>Result.</strong> Four hundred and ninety‑five pages generate from one source, correcting a statement costs nothing, and the addresses this site has ever published continue to answer. The exporter also owns exactly one presentation decision — how services group into themed sections — and that is deliberate: everything else it writes is the database&rsquo;s, and a group heading missing a language fails the export rather than rendering English over translated content.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Closed the colour system at 28 documented colours with a linter that fails a value painted but undocumented, documented but unpainted, mis‑measured, respelled as a literal, or within a perceptual distance of 0.02 of one already there.</title>
      <link>https://brand.engineer.company/portfolio/closed-the-colour-system-at-28-colours-133/</link>
      <guid isPermaLink="true">https://brand.engineer.company/portfolio/closed-the-colour-system-at-28-colours-133/</guid>
      <pubDate>Sun, 13 Sep 2026 01:45:50 +0200</pubDate>
      <description>The palette is a closed, measured set that cannot quietly grow, and the two near-duplicate spellings that prompted it are gone.</description>
      <category domain="https://brand.engineer.company/categories/">Automation &amp; CI/CD</category>
      <category domain="https://brand.engineer.company/categories/">Design Systems &amp; UI</category>
      <category domain="https://brand.engineer.company/categories/">Documentation</category>
      <category domain="https://brand.engineer.company/categories/">Frontend Engineering</category>
      <category domain="https://brand.engineer.company/categories/">Testing &amp; QA</category>
      <category domain="https://brand.engineer.company/categories/">UX / UI Design</category>
      <category domain="https://brand.engineer.company/services/">Brand, Marketing &amp; SEO</category>
      <category domain="https://brand.engineer.company/services/">Frontend Development</category>
      <category domain="https://brand.engineer.company/services/">UI/UX Design &amp; Design Systems</category>
      <content:encoded><![CDATA[<p><strong>Situation.</strong> Colour on a site with a dark default, a light theme, a print stylesheet, a forced‑colours mode and an illustrated brand does not stay a small set on its own. It had already started to drift in the way it always does: two spellings of the same colour sitting close enough that nobody could tell them apart, values re‑typed as literals beside the tokens that defined them, and documented colours that nothing painted.</p>
<p><strong>Task.</strong> The palette had to become a closed set with a stated distinctness threshold, and something had to enforce the closure.</p>
<p><strong>Action.</strong> Twenty‑eight colours, each documented with the contrast ratio it measures on the surface it appears on, and eleven brand values declared once as tokens. The threshold is numeric rather than editorial: two colours closer than a perceptual distance of 0.02 in a uniform colour space are one colour with two spellings. The linter fails five different ways — a value painted but undocumented, documented but unpainted, recorded with the wrong ratio, within the threshold of one already there, or a brand value re‑spelled as a literal. It found two immediately: a theme colour that existed in two spellings 0.018 apart, and a background colour standing in for a wall it sat 0.021 from. Transparency uses relative colour syntax rather than the mixing function, specifically because the guard cannot see through a mix and a palette guard that can be evaded is not a guard. The rule that goes with it is short: change a token rather than a rule, add a colour only when none fits, and never lower a documented ratio to make a design work.</p>
<p><strong>Result.</strong> The palette is a closed, measured set that cannot quietly grow, and the two near‑duplicate spellings that prompted it are gone. It is a constraint that occasionally says no — a design wanting a slightly different blue has to take the one that exists or make the case for a twenty‑ninth colour, and that case has to include the ratio.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Replaced 963 anonymous structured‑data blocks that restated the company 1,671 times with one linked graph of 16 types minted from stable origin identifiers.</title>
      <link>https://brand.engineer.company/portfolio/replaced-963-structured-data-blocks-with-one-graph-138/</link>
      <guid isPermaLink="true">https://brand.engineer.company/portfolio/replaced-963-structured-data-blocks-with-one-graph-138/</guid>
      <pubDate>Sun, 13 Sep 2026 01:45:50 +0200</pubDate>
      <description>One graph with stable identity replaces 963 anonymous restatements, and the pages carry less rather than more.</description>
      <category domain="https://brand.engineer.company/categories/">APIs &amp; Integration</category>
      <category domain="https://brand.engineer.company/categories/">Automation &amp; CI/CD</category>
      <category domain="https://brand.engineer.company/categories/">Brand &amp; Marketing</category>
      <category domain="https://brand.engineer.company/categories/">Data Governance</category>
      <category domain="https://brand.engineer.company/categories/">Frontend Engineering</category>
      <category domain="https://brand.engineer.company/categories/">Web Development</category>
      <category domain="https://brand.engineer.company/services/">Brand, Marketing &amp; SEO</category>
      <category domain="https://brand.engineer.company/services/">Data Governance &amp; Quality</category>
      <category domain="https://brand.engineer.company/services/">Website Development &amp; CMS</category>
      <content:encoded><![CDATA[<p><strong>Situation.</strong> The site emitted structured data the way most sites do: a block per page, each one describing the organisation again from scratch. Across the site that came to 963 blocks restating the same company 1,671 times, anonymously — no stable identifier anywhere, so nothing consuming it could tell that the organisation on one page was the organisation on another.</p>
<p><strong>Task.</strong> The structured data had to become one graph with stable identity, rather than a pile of blocks that happen to contain the same words.</p>
<p><strong>Action.</strong> Identifiers are now minted from the site&rsquo;s origin — one for the organisation, one for the site, one for the person — and every block references them rather than restating their contents. The identifiers are origin‑wide rather than per language, because the company is the same company in Danish. Sixteen types are in play, covering the service catalogue and its offers, the reviews and the work they describe, the collections and their breadcrumbs. Two decisions kept the weight down. List pages publish their items by URL only rather than inlining them, which was measured: naming them on the portfolio index would have meant 107 full statements and a 52 per cent increase in the compressed page. And the check that guards it does not merely validate syntax — it asserts that every block parses, that every identifier resolves, and that every URL the graph names was actually built, so a graph pointing at a page that does not exist fails the gate.</p>
<p><strong>Result.</strong> One graph with stable identity replaces 963 anonymous restatements, and the pages carry less rather than more. The measurement that started the work is worth keeping in mind: the site had been emitting the same organisation description 1,671 times without anything being able to join them up.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Cut the stylesheet bundle from 87 KB to 31 KB by taking a base64 font out of it, and dropped 756 KB across 22 files that were published on every deploy and referenced by nothing.</title>
      <link>https://brand.engineer.company/portfolio/cut-the-stylesheet-bundle-from-87kb-to-31kb-139/</link>
      <guid isPermaLink="true">https://brand.engineer.company/portfolio/cut-the-stylesheet-bundle-from-87kb-to-31kb-139/</guid>
      <pubDate>Sun, 13 Sep 2026 01:45:50 +0200</pubDate>
      <description>The bundle is 31 KB instead of 87, 756 KB of dead assets stopped being deployed, and each decision has a measurement attached — including the one that went the…</description>
      <category domain="https://brand.engineer.company/categories/">Automation &amp; CI/CD</category>
      <category domain="https://brand.engineer.company/categories/">Design Systems &amp; UI</category>
      <category domain="https://brand.engineer.company/categories/">Frontend Engineering</category>
      <category domain="https://brand.engineer.company/categories/">Performance Tuning</category>
      <category domain="https://brand.engineer.company/categories/">Web Development</category>
      <category domain="https://brand.engineer.company/services/">Brand, Marketing &amp; SEO</category>
      <category domain="https://brand.engineer.company/services/">Frontend Development</category>
      <category domain="https://brand.engineer.company/services/">Website Development &amp; CMS</category>
      <content:encoded><![CDATA[<p><strong>Situation.</strong> The site&rsquo;s stylesheet bundle was 87 KB, which for a document site with no framework is most of a page weight spent before any content arrives. The site also published a directory of icons on every deploy, generated once and referenced by nothing since.</p>
<p><strong>Task.</strong> The weight had to come down without the design changing, and it had to come down for a reason that could be pointed at rather than by general tidying.</p>
<p><strong>Action.</strong> Measurement first. Two thirds of the largest stylesheet was one base64‑encoded font — about 56 KB of text encoding about 42 KB of font — inlined for a first‑paint benefit that a preloaded external file gets anyway, and paid for on every page whether the weight was needed or not. Taking it out took that file from 84 KB to 28 and the whole bundle from 87 to 31. The three font weights are preloaded instead, and the third joined the list only when field data showed it closing the longest critical chain rather than because three sounded complete. Separately, an audit of what the deploy actually published found twenty‑one generated icons and a duplicate configuration file, 756 KB, shipped every time and referenced from nowhere; and the site&rsquo;s icon file itself came down from 145 KB to 15. A modern image format was evaluated for the brand mark, measured, and declined — it came out larger, and the selection mechanism picks by order rather than by size, so a modern browser would have taken the heavier file everywhere. Two checks now hold the line: no photograph may be displayed wider than half its source pixels, and every illustration must be published at a size its own pixels support at both standard and high density.</p>
<p><strong>Result.</strong> The bundle is 31 KB instead of 87, 756 KB of dead assets stopped being deployed, and each decision has a measurement attached — including the one that went the other way, which is the more useful record of the two.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Fixed a sitemap where 172 of 176 URLs shared one modification timestamp, by taking the date from git history after establishing that the export rewrites every file on every run.</title>
      <link>https://brand.engineer.company/portfolio/fixed-a-sitemap-with-one-shared-timestamp-140/</link>
      <guid isPermaLink="true">https://brand.engineer.company/portfolio/fixed-a-sitemap-with-one-shared-timestamp-140/</guid>
      <pubDate>Sun, 13 Sep 2026 01:45:50 +0200</pubDate>
      <description>A re-sync after a month of content edits now touches eight files of 186 instead of all of them, and the sitemap says something true.</description>
      <category domain="https://brand.engineer.company/categories/">Automation &amp; CI/CD</category>
      <category domain="https://brand.engineer.company/categories/">Brand &amp; Marketing</category>
      <category domain="https://brand.engineer.company/categories/">Python</category>
      <category domain="https://brand.engineer.company/categories/">Testing &amp; QA</category>
      <category domain="https://brand.engineer.company/categories/">Web Development</category>
      <category domain="https://brand.engineer.company/services/">Brand, Marketing &amp; SEO</category>
      <category domain="https://brand.engineer.company/services/">DevOps &amp; CI/CD Automation</category>
      <category domain="https://brand.engineer.company/services/">Website Development &amp; CMS</category>
      <content:encoded><![CDATA[<p><strong>Situation.</strong> The site&rsquo;s sitemap told search engines that 172 of its 176 pages had last changed at the same instant. That is not a subtle inaccuracy — a modification date is a promise to a crawler that a page&rsquo;s content changed, and a site making that promise 172 times simultaneously is either telling the truth about a full rewrite or telling nobody anything useful.</p>
<p><strong>Task.</strong> The dates had to describe the content rather than the file, without inventing precision the repository does not have.</p>
<p><strong>Action.</strong> The cause was that the date came from the file&rsquo;s modification time and the content export rewrites every file on every run, so a single sync stamped the whole corpus. The fix moves the source to the version history, with a fallback chain that tries an explicit field first, then the commit history, then the file. The trap in that fix is worth recording: the literal field name has to appear in the list or the generator never reads it, so a configuration that looks like it prefers an authored date but omits the name silently ignores every authored date. Two other date decisions came out of the same work. The database now carries when each record was written and last revised, kept deliberately separate from when the work happened — those are years apart and conflating them would date a page written this year to a decade ago. And rows in that file are optional and nothing is derived, because the repository&rsquo;s own history begins after the content did: a missing date leaves the field empty rather than recording a migration, on the principle that a precise wrong number is worse than an absent one, since only the wrong one gets believed.</p>
<p><strong>Result.</strong> A re‑sync after a month of content edits now touches eight files of 186 instead of all of them, and the sitemap says something true. The general rule went into the metadata document alongside it, because the same trap applies to every field that has a fallback chain.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Built a 48‑token design system on the platform&#39;s own glass material and wrote the linter that rejects a magic number, a hardcoded font size, or an animation that ignores the reduce‑motion preference.</title>
      <link>https://brand.engineer.company/portfolio/built-a-48-token-design-system-160/</link>
      <guid isPermaLink="true">https://brand.engineer.company/portfolio/built-a-48-token-design-system-160/</guid>
      <pubDate>Sun, 13 Sep 2026 01:45:50 +0200</pubDate>
      <description>Forty-eight tokens describe the whole interface, the appearance follows the system, and none of the three erosions can be committed.</description>
      <category domain="https://brand.engineer.company/categories/">Automation &amp; CI/CD</category>
      <category domain="https://brand.engineer.company/categories/">Design Systems &amp; UI</category>
      <category domain="https://brand.engineer.company/categories/">Frontend Engineering</category>
      <category domain="https://brand.engineer.company/categories/">Testing &amp; QA</category>
      <category domain="https://brand.engineer.company/categories/">UX / UI Design</category>
      <category domain="https://brand.engineer.company/services/">Brand, Marketing &amp; SEO</category>
      <category domain="https://brand.engineer.company/services/">Frontend Development</category>
      <category domain="https://brand.engineer.company/services/">UI/UX Design &amp; Design Systems</category>
      <content:encoded><![CDATA[<p><strong>Situation.</strong> An interface built by adding views accumulates values: a corner radius here, a fourteen‑point font there, a two‑tenths‑of‑a‑second animation somewhere else. Individually each is reasonable and collectively they are a design that cannot be changed, because there is no such thing as &ldquo;the corner radius&rdquo; to change — there are forty of them, slightly different, spread across fifty files.</p>
<p><strong>Task.</strong> The visual vocabulary had to be reduced to a named set, expressed against the platform&rsquo;s own material rather than reinvented, and defended by a check so it stays reduced.</p>
<p><strong>Action.</strong> The system is forty‑eight tokens in six groups: spacing, typography, colour, radius, elevation and motion. Colour and material are built on the platform&rsquo;s semantic colours and its glass material rather than fixed values, which is what makes the interface follow the system appearance, the accent colour and the contrast settings without any code that watches for them. Typography maps to the platform&rsquo;s text styles so it scales with the user&rsquo;s size preference instead of pinning a point value. The linter rejects three things: a numeric literal where a spacing or radius token belongs, a hardcoded font size anywhere, and an animation declared without honouring the reduce‑motion preference. The third is the one that would otherwise erode fastest, because an animation is added in a moment of polish and the preference check is the part that gets skipped — so the rule makes the animation itself impossible to write without it, rather than asking anyone to remember.</p>
<p><strong>Result.</strong> Forty‑eight tokens describe the whole interface, the appearance follows the system, and none of the three erosions can be committed. The limit is that a token system constrains consistency and not quality — everything now matches, and matching is not the same as being well designed, which is a judgement no linter in this project makes.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Built the company&#39;s icon and favicon sets from two hand‑made drawings, with every derived asset regenerated by script and a check that fails when a derived file was committed before its source.</title>
      <link>https://brand.engineer.company/portfolio/built-the-visual-identity-from-two-drawings-164/</link>
      <guid isPermaLink="true">https://brand.engineer.company/portfolio/built-the-visual-identity-from-two-drawings-164/</guid>
      <pubDate>Sun, 13 Sep 2026 01:45:50 +0200</pubDate>
      <description>Two drawings produce every published icon, regeneration is one command, and a stale asset fails the build.</description>
      <category domain="https://brand.engineer.company/categories/">Automation &amp; CI/CD</category>
      <category domain="https://brand.engineer.company/categories/">Brand &amp; Marketing</category>
      <category domain="https://brand.engineer.company/categories/">Design Systems &amp; UI</category>
      <category domain="https://brand.engineer.company/categories/">Testing &amp; QA</category>
      <category domain="https://brand.engineer.company/categories/">UX / UI Design</category>
      <category domain="https://brand.engineer.company/categories/">Web Development</category>
      <category domain="https://brand.engineer.company/services/">Brand, Marketing &amp; SEO</category>
      <category domain="https://brand.engineer.company/services/">UI/UX Design &amp; Design Systems</category>
      <category domain="https://brand.engineer.company/services/">Website Development &amp; CMS</category>
      <content:encoded><![CDATA[<p><strong>Situation.</strong> A small company&rsquo;s visual identity is usually bought, generated, or assembled from stock, and the result is an identity that belongs to nobody. The alternative problem is worse: hand‑made artwork that exists only as exported files, where the source drawing is lost, the exports are edited directly, and within a year the versions in use disagree with each other and no original remains to settle it.</p>
<p><strong>Task.</strong> The identity had to be drawn rather than sourced, and the pipeline from drawing to published asset had to be reproducible, so that every file on the site is derivable rather than kept.</p>
<p><strong>Action.</strong> Two hand‑made drawings sit at the root of the generated artwork — the company mark and the cog that became the favicon — and every icon the site publishes is produced from them. The mark is a raster drawing; the cog is a vector. From these, scripts produce every derived form: the favicon set in its required sizes, the touch icons, and the launch images at each device size. The generation is a task anyone can run, so the question &ldquo;where did this file come from&rdquo; has an answer that is a command. The staleness check is the piece that makes it hold: it compares the commit date of every derived asset against its source and fails when a derived file was committed first, which catches the exact failure this design exists to prevent — someone edits the source, forgets to regenerate, and the published site keeps showing artwork that no longer matches the original. The rule that derived files are never edited by hand is stated where the sources live.</p>
<p><strong>Result.</strong> Two drawings produce every published icon, regeneration is one command, and a stale asset fails the build. The trade‑off is a hard dependency on the toolchain: the derived files are committed so the site builds anywhere, but changing the identity requires the generation tooling to still work, and a source format that stops being readable takes the whole identity with it.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Opening a dApp inside Solana mobile wallets</title>
      <link>https://brand.engineer.company/notes/solana-mobile-wallet-deeplinks/</link>
      <guid isPermaLink="true">https://brand.engineer.company/notes/solana-mobile-wallet-deeplinks/</guid>
      <pubDate>Mon, 10 Aug 2026 00:00:00 +0000</pubDate>
      <description>Why Phantom, Solflare and Backpack browse deep links fail on mobile, and the exact formats, trigger rules and fixes that make them work.</description>
      <content:encoded><![CDATA[<p>A React dApp built on <code>@solana/wallet-adapter-react</code> connects desktop wallets
without trouble, but on a phone the same flow falls apart: the wallet has to
open the dApp inside its own in-app browser, and the deep links that should
make that happen quietly do not. Backpack lands on a &ldquo;download the app&rdquo; page;
Solflare opens the app but never the site; every variant seems to fail. We took
the problem apart, and it turned out to be four separate problems wearing one
symptom.</p>
<h2 id="the-four-problems">The four problems</h2>

<ul>
<li><strong>The Backpack link was malformed.</strong> The only documented format is
<code>https://backpack.app/ul/v1/browse/&lt;url&gt;?ref=&lt;ref&gt;</code> — a universal link with
the target URL in the path and a required <code>ref</code>. A custom-scheme guess like
<code>backpack://ul/v1/browse?url=...</code> matches no route the app registers, so the
user ends on the wallet&rsquo;s install page.</li>
<li><strong>Solflare needs its universal link too:</strong>
<code>https://solflare.com/ul/v1/browse/&lt;url&gt;?ref=&lt;ref&gt;</code>, not the bare
<code>solflare://</code> scheme. A bare scheme can launch the app without routing it —
which is exactly &ldquo;the app opens, but the site tab has to be opened by hand&rdquo;.</li>
<li><strong>Both parameters must be encoded.</strong> <code>url</code> is the full absolute dApp address
and <code>ref</code> is the requesting origin, each passed through <code>encodeURIComponent</code>.
An unencoded <code>?</code> or <code>&amp;</code> in the target corrupts the parse, and the wallet
opens on its home screen instead of the browser tab.</li>
<li><strong>The trigger matters as much as the link.</strong> Universal links only switch apps
on a navigation the operating system trusts — and they deliberately do
nothing when pasted into the address bar, which is also how a perfectly
correct link &ldquo;fails&rdquo; during testing.</li>
</ul>
<h2 id="the-documented-formats">The documented formats</h2>

<ul>
<li>Phantom: <code>https://phantom.app/ul/browse/&lt;url&gt;?ref=&lt;ref&gt;</code> — no <code>/v1</code> in this
one.</li>
<li>Solflare: <code>https://solflare.com/ul/v1/browse/&lt;url&gt;?ref=&lt;ref&gt;</code></li>
<li>Backpack: <code>https://backpack.app/ul/v1/browse/&lt;url&gt;?ref=&lt;ref&gt;</code></li>
</ul>
<p>One pattern serves all three:</p>
<pre tabindex="0"><code>const WALLET_BROWSE = {
  phantom: (url, ref) =&gt;
    `https://phantom.app/ul/browse/${url}?ref=${ref}`,
  solflare: (url, ref) =&gt;
    `https://solflare.com/ul/v1/browse/${url}?ref=${ref}`,
  backpack: (url, ref) =&gt;
    `https://backpack.app/ul/v1/browse/${url}?ref=${ref}`,
};

function walletBrowseLink(
  walletName,
  targetUrl = window.location.href,
) {
  const build = WALLET_BROWSE[walletName.toLowerCase()];
  if (!build) return null;
  return build(
    encodeURIComponent(targetUrl),
    encodeURIComponent(window.location.origin),
  );
}
</code></pre><h2 id="triggering-the-link-so-ios-and-android-accept-it">Triggering the link so iOS and Android accept it</h2>

<ul>
<li><strong>Render a real anchor, precomputed.</strong> A plain
<code>&lt;a href={walletBrowseLink('phantom')}&gt;</code> is the most reliable trigger on both
platforms.</li>
<li><strong>If it must be programmatic</strong>, assign <code>window.location.href</code> synchronously
inside the tap handler — no <code>await</code>, no <code>fetch</code>, no <code>setTimeout</code> first. After
asynchronous work the gesture context is gone, and iOS falls back to the
wallet&rsquo;s website. Never <code>window.open</code>.</li>
<li><strong>Never test by pasting into the address bar.</strong> Universal links deliberately
do not fire there; test with a tapped link or a QR code scanned by the
camera.</li>
<li><strong>Mind the messenger webviews.</strong> Opened inside Telegram&rsquo;s or Instagram&rsquo;s
in-app browser, universal links are frequently swallowed and the wallet&rsquo;s
plain website loads instead. User-agent detection is heuristic at best, so
also give users a visible escape hatch: &ldquo;open in Safari or Chrome, then
connect&rdquo;.</li>
</ul>
<h2 id="the-bigger-fix-on-android">The bigger fix on Android</h2>

<p>Hand-rolled deep links are the iOS story. On Android, Solana Mobile&rsquo;s Mobile
Wallet Adapter lets a dApp running in the mobile browser connect straight to
the installed wallet app, with no in-app-browser detour at all. Recent versions
of <code>@solana/wallet-adapter-react</code> register the mobile adapter automatically, so
upgrading the wallet-adapter packages can fix Android by itself. The target
architecture: Mobile Wallet Adapter on Android, browse universal links on iOS,
where Apple allows no equivalent.</p>
<h2 id="verifying-on-a-device">Verifying on a device</h2>

<ol>
<li>Real device, wallet installed, link opened from the system browser — not
from a messenger.</li>
<li>Tap a rendered link or scan a QR code; never paste into the address bar.</li>
<li>Confirm the wallet opens and the dApp loads in its in-app browser tab — the
second half is the part that fails.</li>
<li>Repeat without the wallet installed: the universal link should degrade to
the wallet&rsquo;s website. That page appearing while the app is installed means
the link or the trigger is still wrong.</li>
<li>Then test the messenger path, and add the &ldquo;open in browser&rdquo; hint if it
fails there.</li>
</ol>
<h2 id="sources">Sources</h2>

<ul>
<li><a href="https://docs.phantom.com/phantom-deeplinks/deeplinks-ios-and-android">Phantom: deep links on iOS and Android</a></li>
<li><a href="https://docs.solflare.com/solflare/technical/deeplinks/other-methods/browse">Solflare: the Browse deep link</a></li>
<li><a href="https://docs.backpack.app/deeplinks/other-methods/browse">Backpack: the Browse deep link</a></li>
<li><a href="https://docs.solanamobile.com/mobile-wallet-adapter/mobile-apps">Solana Mobile: Mobile Wallet Adapter</a></li>
</ul>
]]></content:encoded>
    </item>
  </channel>
</rss>
