Commit Graph
37096 Commits
Author SHA1 Message Date
ShaneandClaude Sonnet 5 bab129dfd4 Reorganize editor: full-width Format selector, Layout moves to a canvas carousel
Format was a small pill row buried alongside Brand — promoted it to
full-width stacked rows (label + dimensions) since it's the decision
that determines everything downstream (canvas size, available
templates, content rules).

Layout picker moves out of the sidebar entirely into a horizontally
scrollable thumbnail carousel floating above the bottom toolbar, so
browsing layouts feels like flipping through visual samples instead of
scanning a static sidebar grid. Still uses the existing CSS-diagram
LayoutThumb visuals for now — swapping in real rendered examples is a
deferred polish pass, not blocking this reorg.

Sidebar now reads Brand -> Format -> Content, with Content's fields
already gated on a layout being selected (showContentControls), so the
flow reads as sequential steps rather than three parallel groups.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 14:59:59 -05:00
ShaneandClaude Sonnet 5 23ebafcc95 Default to single OG view on load; rename Blog format to OG; X preview label
- Renamed the "og" format's label from "Blog" to "OG" — the view
  toggle picks this up automatically since its labels are derived from
  format.label.
- Default view state changed from 'both' to 'og' so the app loads
  showing just the primary render instead of the side-by-side pair.
- In-context preview toggle: "Twitter / X" -> "X".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 14:51:48 -05:00
ShaneandClaude Sonnet 5 2c9d120808 Bump wordmark display size to match its native 220x43
Was rendering at 36px height (~184px wide); bumped to 43px height,
its native SVG size, landing width at ~220px. Lossless since it's an
embedded SVG, not a raster asset — no 2x source needed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 14:16:09 -05:00
ShaneandClaude Sonnet 5 3d980b9c87 Nudge logo-center-left's logo above dead-center
Was mathematically centered on the vertical midpoint, which read as
mechanically centered against the bottom-anchored text below. Lifts it
40px (1x) higher via a new LOGO_CENTER_LIFT_1X constant.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 14:13:20 -05:00
ShaneandClaude Sonnet 5 f3898c3f96 Shrink eyebrow to 20px and bump pill weight to 500
Compared 400 vs 500 at the new 20px size; 500 reads better at this
smaller size. Also register EYEBROW_PILL_WEIGHT in satoriFonts (was
only loading HEADLINE.weight) so the requested pill weight actually
has its own font file loaded instead of silently falling back.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 13:49:04 -05:00
ShaneandClaude Sonnet 5 0925748a3a Add the PostgreSQL example back to the brackets instruction
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 13:42:02 -05:00
ShaneandClaude Sonnet 5 7be740f3be Shorten headline instructions: drop punctuation/parens, rename brackets hint
"(ENTER) - Manual line break." -> "Manual line break"
"(BRACKETS) - Keep exact casing (e.g. type ...)." -> "Capitalize letter"

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 13:38:56 -05:00
ShaneandClaude Sonnet 5 99806e2922 Split headline instructions onto separate lines with matching icon chips
The ENTER and bracket-override instructions now each get their own line
and the same kbd-chip treatment (↵ / []) instead of being crammed into
one paragraph.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 13:34:37 -05:00
ShaneandClaude Sonnet 5 254e63217b Add Headline tooltip and restyle the manual line-break instructions
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 13:31:52 -05:00
ShaneandClaude Sonnet 5 2fbae0163d Rebuild Blog listing mockup to match supabase.com/blog; tune headline auto-fit sizing
Blog listing mockup:
- Same research approach as the Blog post mockup — read the real /blog
  index page (app/blog/page.tsx, BlogGridItem.tsx, FeaturedThumb.tsx,
  BlogClient.tsx, BlogFilters.tsx) instead of guessing.
- Real nav (extracted to a shared SiteNav component, reused by both Blog
  mockups), "Blog" H1, the real category filter pills + search box, a
  featured-post section above the grid (3/2 hero + title + author,
  matching FeaturedThumb), and a uniform 3-column grid below (1.91:1
  card images, date + category, title) matching BlogGridItem — with the
  featured post correctly excluded from the grid loop, same as the real
  page's `listPosts = initialPosts.slice(1)`.
- Widened the mockup's max-width to max-w-5xl for this mode too (was
  max-w-2xl), since the grid needs the room.

Headline auto-fit sizing:
- Two size tiers instead of one fixed 40-64 range: `default` (48-64,
  larger) when there's no icon and the headline fits in <=2 lines;
  `compact` (40-56) whenever an icon is present, and also whenever the
  headline still needs a 3rd line even at the default tier's max size.
- maxLines raised from 2 to 3 — headlines can now wrap to a 3rd line
  instead of only ever 1-2, but never more than 3.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 13:25:14 -05:00
ShaneandClaude Sonnet 5 9765f3ac4e Rebuild Blog post mockup to closely match the real supabase.com layout
The previous version took visual liberties (full-width hero, minimal
nav, wrong content width) instead of following the real page structure
in apps/www/components/Blog/BlogPostRenderer.tsx. Rebuilt to match:

- Real nav: Supabase wordmark (public/brand-logos/supabase-wordmark.svg),
  actual primary nav labels (Product/Developers/Solutions/Pricing/Docs
  from apps/www/data/nav.tsx), and the real Sign in / Start your project
  CTAs.
- `data-theme="dark"` on the mockup root scopes the real dark-theme CSS
  variables to this subtree (blog pages force dark mode via
  useForceDeepDark) — bg-background/border-default/text-foreground/
  text-brand now resolve to supabase.com's actual palette instead of an
  approximation.
- Back link, "Blog" link, title, date/read-time, and author row placed
  exactly as the real page orders them.
- The two-column grid now matches proportions: hero image lives in the
  7/12 content column (not full width), with a 5/12 sidebar column
  showing the eyebrow as a tag pill + "Share this article" — mirroring
  the real page's content/sidebar split instead of a single full-bleed
  image.
- Widened the mockup's max-width to max-w-5xl (matching the real page's
  title-block max-w-5xl) only for this mode, so the wider two-column
  layout doesn't get clipped by the other modes' narrower max-w-2xl.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 13:05:46 -05:00
ShaneandClaude Sonnet 5 7feaffcfd6 Add a Blog post in-context preview mockup
Modeled on the real supabase.com/blog/[slug] page (BlogPostRenderer.tsx):
sticky nav, back-to-blog link, title, date/read-time, author, then the
hero image — cropped to a fixed 1.91:1 box (object-cover), same as the
live site does regardless of the source image's own aspect ratio, so
this shows exactly how much the Thumb image gets cropped rather than
assuming it survives untouched. Distinct from the existing "Blog
listing" grid mockup, which approximates the index page instead of a
single post.

Wires thumbImgUrl through so the mockup can prefer the Thumb render
(matching the real page's imgThumb-over-imgSocial priority) even when
the OG/Thumb toggle itself isn't currently showing Thumb.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 11:24:44 -05:00
ShaneandClaude Sonnet 5 a13eeaf27c Add native maxLength to headline textarea
Eyebrow already had maxLength as a belt-and-suspenders alongside the
onChange clamp; headline's textarea was missing it. Blog format was
already the default (og format id, label "Blog") — confirmed via a
fresh reload, no change needed there.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-09 18:09:15 -05:00
ShaneandClaude Sonnet 5 2d92c8426d Tighten headline limit to 50 chars and eyebrow to 20 chars
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-09 17:58:39 -05:00
ShaneandClaude Sonnet 5 6703a1f9fa Enforce headline/eyebrow character limits instead of just displaying them
The headline counter (70 hard limit) and eyebrow were previously
uncapped — the counter just changed color past the limit but never
stopped input. Added a clampChars helper that truncates on every
keystroke (grapheme-aware, matching the existing [...s].length
counters), applied to both fields, plus a native maxLength as a
belt-and-suspenders measure. Eyebrow gets a new 25-char limit and
counter, matching the headline's existing counter UI. Also capped
eyebrow at 25 chars server-side in /api/og for parity with headline's
existing server-side slice.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-09 17:56:31 -05:00
ShaneandClaude Sonnet 5 d6fe800806 Disable the eyebrow for the Logo center, text bottom layout
Added a noEyebrow flag alongside the existing noIcon one — mirrors the
same pattern: hides the Eyebrow control in the sidebar and omits the
eyebrow pill from the render for this template specifically.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-09 17:53:05 -05:00
ShaneandClaude Sonnet 5 a1d59d5676 Fix logo-left-text-right alignment; group Layout picker by category
- Layout 2's text was fully right-aligned (each line's own right edge
  flush). Changed to left-aligned text within a right-anchored block —
  the bounding box (sized to the widest line) still butts up against
  the column's right edge, but each line now starts from the same left
  edge (ragged-right), matching normal left-aligned paragraph text.
- Added a `category` field to Template and grouped the sidebar's Layout
  picker into labeled sections (e.g. "Icon layouts", "Logo layouts")
  instead of one flat grid — needed since the tile count keeps growing
  per format.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-09 17:49:26 -05:00
ShaneandClaude Sonnet 5 fe97080720 Add three no-icon Blog layouts using the Supabase wordmark
Blog format gains three new layouts that render the fixed Supabase
wordmark instead of the user-selectable icon system:

- Logo top-left: wordmark pinned top-left, headline/eyebrow bottom-left.
- Logo left, text right: two-column layout — narrow left column with
  the wordmark centered vertically/left-aligned, wider right column
  with the headline centered vertically and right-aligned (max-width
  580px).
- Logo center, text bottom: wordmark vertically centered/left-aligned,
  headline/eyebrow anchored bottom-left.

The wordmark is a static brand asset (public/brand-logos/
supabase-wordmark.svg, embedded as a data URI via lib/design/logo.ts),
not a user-uploaded icon/logo asset, so these templates carry a
`noIcon` flag that hides the Icon control in the sidebar. Extended
Template.textAlign/anchorX with a 'right' option and TemplateParts with
logoEl/logoHeight so templates.tsx (shared with the client bundle)
never imports the server-only fs-reading logo module directly — route.tsx
computes the wordmark element and passes it down instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-09 17:42:55 -05:00
ShaneandClaude Sonnet 5 45ebd49672 Fix zoomed single-image view left-aligning instead of centering
Centering a zoomed card via items-center/justify-center clips the
leading edge once it overflows the scroll container, so zoom>100 was
switching to items-start — but that left-aligned the card even when it
still fit comfortably at modest zoom levels like 125%.

Replaced the alignment approach with mx-auto/my-auto on the card
wrapper: flexbox auto margins center the item when there's room and
automatically collapse to 0 once it overflows, so the card stays
centered at any zoom level that fits and only becomes start-aligned
(scrollable, no clipping) once it doesn't.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-09 17:23:25 -05:00
ShaneandClaude Sonnet 5 894b527557 Give Social format its own Twitter/X/LinkedIn + Instagram view options
Social previously reused the generic OG/Thumb/Both view toggle with a
classic icon-only Thumb crop. It needed a real second full composition
instead: Instagram's 1080x1350 feed size, rendered with the same
headline/eyebrow/icon/template recipe as the 1200x627 Twitter/X/LinkedIn
primary, not a stripped-down icon crop.

- formats.ts: add optional `primaryLabel` (view-toggle label override) and
  `secondary` (a second full-composition size distinct from `thumb`) to
  the Format interface; Social's twitter format now sets both.
- route.tsx: support `variant=secondary`, swapping in the format's
  secondary width/height while reusing the existing template-driven
  render path untouched by the icon-only thumb branch.
- page.tsx: view options are now format-derived (hasThumb vs hasSecondary
  vs hasSecondSlot) instead of a static OG/Thumb/Both list; fixes a bug
  where viewing Instagram alone hid the Layout/Eyebrow/Headline sidebar
  controls (it's a full composition, not an icon-only crop, so those
  controls should stay visible).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-09 17:17:07 -05:00
Shane 82cf64115a Tighten zoom controls in the bottom toolbar
Group the zoom-tool/out/percentage/in buttons in their own gap-0.5 flex
instead of inheriting the toolbar's wider gap-3, and shrink them
slightly (h-7->h-6, 14px->13px icons, narrower percentage readout) so
the cluster reads as one compact control instead of four loosely
spaced buttons.
2026-07-09 17:02:22 -05:00
Shane 3801af7cb5 Merge zoom controls into the single bottom toolbar
One centered bar now holds safe-area guide, Preview (view-in-context),
and zoom controls (divided by the same vertical-rule pattern already
used between sections), instead of two separate floating boxes. Zoom
still only shows when inContext === 'none', matching its prior visibility.
2026-07-09 16:53:41 -05:00
Shane 555be90167 Move zoom control to the right, vertically center it, add Z zoom tool
- Zoom control moved from bottom-left to bottom-right (mirrors the
  Preview toolbar's left/right anchoring pattern) and now matches its
  height/padding so the two visually center-align on the shared bottom-6
  offset (verified via bounding-rect centers, within 1px)
- New "Z" keyboard shortcut toggles an Illustrator/Photoshop-style zoom
  tool: cursor becomes zoom-in over the canvas, click to zoom in one
  step, Alt+click (or hold Alt) to zoom out instead — cursor and the
  zoom-control icon both reflect the Alt state live. Escape cancels the
  tool; typing "z" in a text field is correctly ignored
2026-07-09 16:49:41 -05:00
Shane f59827cd2a Add canvas zoom in/out for the image previews
- New bottom-left zoom control (-, percentage, +) scales the rendered
  OG/Thumb image cards from 25% to 200% in 25% steps — only the images
  resize, not the surrounding toolbar/panel controls
- Fixed a real overflow bug this surfaced: centering flex content wider
  than its scroll container clips the left-side overflow with no way to
  reach it via scroll in most browsers. Above 100% zoom the row switches
  from center- to start-aligned so the full width stays reachable via
  horizontal scroll

Also resolves a leftover git-stash merge-conflict in the local dev
launch.json (og-generator and www configs had collided into one entry).
2026-07-09 16:36:37 -05:00
Shane cb9bb7abe0 Unify brand background to #121212, remove divider under Layout tiles
- All 4 brands' bg.primary now use the same #121212 (was a slightly
  different near-black per brand) — verified via pixel sampling of
  rendered OG images
- Content group no longer has a top divider, removing the line that sat
  directly under the Layout tiles (same noDivider pattern used for Layout
  itself against the Format buttons above it)
2026-07-07 22:12:50 -05:00
Shane cdd79e8437 Give Social its own Instagram / Twitter-X layout set
Same pattern as Newsletter: Social (id "twitter") now swaps the standard
4-tile Layout section for two dedicated tiles — Instagram (centered,
tighter box for square-cropped feeds) and Twitter / X (classic
bottom-left card layout, matching how X actually presents link
previews) — with the same reset-to-valid-default effect on format change.
2026-07-07 22:09:27 -05:00
Shane 78873e61fc Remove divider line between Format buttons and Layout section
Group now takes an optional noDivider prop; the Layout group (which
immediately follows Brand & format) uses it so there's no border line
sitting right under the Format button set.
2026-07-07 22:02:37 -05:00
Shane bbae8c4353 Rename Export button to green "Export", dual-theme Twitter/light-only LinkedIn
- "Export images" button renamed to "Export" and recolored to the brand
  green (matches Download's styling elsewhere)
- Twitter/X in-context preview now always shows both light and dark cards
  side by side instead of a toggle — no reason to hide either
- LinkedIn in-context preview is light mode only (LinkedIn's default and
  by far its most common surface); the light/dark theme selector is
  removed entirely
2026-07-07 21:58:44 -05:00
Shane f32044de81 Add Oriole/Supasquad brands, Newsletter/Luma formats, preview titles
- Two new placeholder brands (Oriole: orange accent, Supasquad: magenta
  accent) following the same pattern as Multigres — real colors TBD
- Format renames: "OG image" -> "Blog", "Twitter / X post" -> "Social"
- Two new formats: Newsletter (1200x600, no Thumb) and Luma (1600x900,
  Luma's standard event-cover size, no Thumb)
- Newsletter gets its own two-tile Layout set (Newsletter cover / Section
  header) instead of the standard 4 templates, swapped automatically when
  the format changes, with a reset effect so the selected template is
  always valid for the active format
- Fixed a latent bug where switching to a Thumb-less format while View was
  "Both" still rendered an empty Thumb card
- In-context previews (Twitter/X, LinkedIn, Blog listing) now show a title
  above the mockup, matching the OG/Thumb preview card labels
2026-07-07 21:51:47 -05:00
Shane 62c275e97d Add Supaimage logo as the Thumb's icon-less background
Drops the Supaimage/Supabase lightning-bolt mark into public/backgrounds/
as thumb-default.png — picked up automatically by
randomBackgroundDataUri() (single file today, so it's deterministic; more
can be added later without code changes).
2026-07-07 21:30:34 -05:00
Shane 46523a2908 Dropdown polish, matching toolbar corners, drop OG/layout backgrounds
- Icon dropdown now opens upward (bottom-full) instead of pushing the
  panel taller downward, and closes on outside click like a real dropdown
- Floating toolbars use rounded-md to match the button-set corners instead
  of rounded-full
- Remove the headline/WCAG info bar under the OG preview and the
  no-icon warning under the Thumb preview
- Remove the random background-image fallback from OG/layout renders
  entirely (flat color only, as templates originally worked) — kept only
  for the icon-less Thumb, whose asset folder is now empty pending a
  real image to drop in (see public/backgrounds/README.md)
2026-07-07 21:22:59 -05:00
Shane 057619cae0 Anchor toolbars, add accurate Twitter/X + LinkedIn light/dark previews
- View toggle and the Preview/safe-area toolbar are now siblings of the
  scrollable canvas (main), not children of it, so they stay anchored in
  place instead of scrolling away with the content
- Twitter/X and LinkedIn in-context mockups use each platform's real hex
  palette (not our app's semantic tokens) with a Light/Dark toggle —
  X's true-black "Lights out" dark mode, LinkedIn's #1b1f23 dark surface,
  accurate borders/secondary-text colors, verified badge, Follow button
- When a social preview is active, the OG/Thumb canvases are hidden and the
  mockup takes over the full canvas view instead of sitting below the OG
  card
2026-07-07 21:08:30 -05:00
Shane b8b92bd4f3 Move Copy URL / Download into an Export images modal
- Remove per-card Copy URL / Download buttons from the OG and Thumb
  previews; the header's "Export @2x" checkbox becomes an "Export images"
  button that opens a modal
- Modal holds the @2x toggle plus one row per image: a read-only URL field,
  a copy button (clipboard icon), and a download button (down-arrow icon)
- Bottom toolbar: add a "Preview" label ahead of the view-in-context
  segmented control, and move the safe-area guide button to the other end
2026-07-07 20:55:24 -05:00
Shane 91e60178d9 Fix editor not filling full viewport height in embedded/iframe previews
Chain height:100% from html -> body -> the app's root div instead of relying
on 100vh alone (min-h-screen on body, h-screen on the root). 100vh can end
up taller or shorter than the actual visible area in embedded iframe
previews (the same class of bug as mobile browser chrome), whereas an
unbroken height:100% percentage chain sizes correctly to whatever the host
gives the iframe.
2026-07-07 20:33:46 -05:00
Shane 2e27ff8a95 Toolbar/layout polish: icon-button safe area, drop template patterns
- Safe area toggle in the bottom toolbar is now a dotted-box icon button
  instead of a checkbox + label; the on-image guide is dotted to match
- Bottom toolbar now aligns to the same center line as the View toggle
  above it (both toolbars sit in main's padded content box, but absolute
  positioning ignores ancestor padding, so the offsets are repeated)
- Eyebrow style selector removed — always renders as a pill now
- Reduced the eyebrow pill's font weight one step (500 -> 400)
- Removed each template's default background pattern (dots/grid/hlines/
  vlines) entirely, along with the now-dead grid-snap alignment math;
  icon-less compositions still get the random image background
- Removed the "Template" label above the layout tiles
2026-07-07 20:29:21 -05:00
Shane a8304396ae Editor polish: pill eyebrows, always-on sentence case, random no-icon backgrounds
- Remove platform-crop previews (too small to be useful)
- View (OG/Thumb/Both) moves back to the top of the canvas, separate from
  the other tools; the floating bottom toolbar now holds the safe-area
  guide toggle and View-in-context
- Eyebrow defaults to the pill style, now white background / black text,
  set in IBM Plex Mono (self-hosted alongside Manrope for satori + fontkit)
- Sentence case is now always on; wrap text in [brackets] to preserve its
  exact casing instead of a manual on/off toggle
- Remove the manual font-size override — always auto-fit
- Remove the "No headline..." caption under the Thumb preview
- Icon-less compositions get a random background image from
  public/backgrounds/ (new asset folder, 3 starter SVGs) instead of a flat
  fill, on both the OG template root and the Thumb
2026-07-07 20:15:06 -05:00
Shane 221a9b50ab Simplify editor UI: button sets, tile layouts, floating view toolbar
- Brand and Format are now button sets instead of dropdowns
- Removed the AI art direction panel for now
- Templates render as visual tiles (like the icon grid) instead of a <select>
- Icon picker is now a dropdown with a thumbnail preview, moved to the end
  of Content (so it's still reachable in Thumb-only view)
- Removed the Thumb icon-size control and the Background pattern controls —
  Thumb uses the format default, patterns use each template's default
- View and View-in-context are now a single floating toolbar, bottom-center
  of the canvas, instead of split between the canvas top and the sidebar
- Safe-area guide is now a uniform 80px inset on every edge
2026-07-07 19:51:09 -05:00
Shane 3531b49911 Add Twitter/LinkedIn/blog-listing in-context preview mockups
Today's platform-crop preview only showed a cropped rectangle, not what the
image looks like in actual use. Adds a "View in context" selector (View
group) with rough mockups of a Twitter/X post, a LinkedIn post, and a
supabase.com blog listing card, each embedding the currently-rendered image
so switching Brand/Format shows how it reads in context.

Also fixes the local dev launch config's pnpm filter for the og-generator ->
supaimage package rename.
2026-07-02 14:12:15 -05:00
Shane 6025737ef4 Rename OG Image Generator to Supaimage
Reflects the tool's expanded scope beyond Supabase blog OG images (package
name, page title, sidebar header, README). The apps/og-generator folder path
is intentionally left as-is for now, to avoid turning this PR's diff into a
delete+add and losing review history — a clean fast-follow after merge.
2026-07-02 14:06:16 -05:00
Shane b8ff5965a8 Add multi-brand, multi-format support (Supaimage phase 1)
Splits design tokens into Brand (colors, illustration stroke) and Format
(canvas size, icon/thumb sizing) abstractions, threaded explicitly through
the render route, asset library, and editor UI instead of hidden globals.
Adds Multigres as a second (placeholder-color) brand and a Twitter/X post
format alongside the existing OG/Thumb. Brand/Format selectors now lead the
editor panel; uploaded assets are scoped per brand via a new `brand` column.
2026-07-02 14:04:00 -05:00
ShaneandClaude Opus 4.8 c50903c5f0 fix(og-generator): vertical grid-snap now aligns to the visible glyph, not the box
Confirmed via pixel measurement that grid-snap for top/bottom-anchored
headlines (stacked, bottom-left templates) was aligning to padY/H-padY — the
invisible CSS layout-box edge — while the visible glyph ink sat well inside
it due to line-height leading. Measured a 13px gap at fontSize=64 on the
'stacked' template (grid line at y=72, visible text top at y=85).

Fixed using the actual Manrope font metrics (already loaded via fontkit for
the auto-fit measurement) rather than a guessed pixel constant, so the
correction stays accurate at every font size auto-fit picks:
  visualTopInset = halfLeading + ascent - capHeight   (box top -> cap-height top)
  visualBottomInset = halfLeading + descent           (box bottom -> baseline)

Re-measured after the fix: top-anchor gap closed from 13px to 0px, bottom-anchor
to within 1px. Center-anchored templates (split-right, centered) were already
within 1.5-4px with no leading-driven bias -- left unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 12:24:39 -05:00
ShaneandClaude Opus 4.8 ed32f16fcd fix(og-generator): switching templates no longer resets the background pattern
changeTemplate() used to overwrite patternType/Scale/Opacity with the new
template's defaultPattern on every switch. Template and background are meant
to be independent controls, so it now only sets the template. AI suggestions
are unaffected — applySuggestion() already sets the pattern separately, right
after calling changeTemplate(), when the suggestion includes one. Verified:
set pattern to vlines, switched template to centered (whose default is dots),
pattern select stayed on vlines.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 20:17:39 -05:00
ShaneandClaude Opus 4.8 463e88b160 fix(og-generator): scope the 65% preview width to single-panel view only
Round #19 applied 65% width unconditionally. Per follow-up: it should only
apply when viewing a single image (OG or Thumb alone) — "Both" reverts to
full width. Row width is now `view === 'both' ? 'w-full' : 'w-[65%]'`.
Verified via getBoundingClientRect at 1700px: Both = 100.0%, OG-only = 65.0%,
Thumb-only = 65.0%, zero console errors in all three states.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 20:06:44 -05:00
ShaneandClaude Opus 4.8 4e5ce33c59 fix(og-generator): match WCAG: label styling to Headline:, narrow preview to 65%
- Split "WCAG:" into its own font-medium text-foreground span, matching
  "Headline:" (previously the whole segment, including the label, was
  colored by pass/fail state)
- Preview row: w-full -> w-[65%], with the wrapper's horizontal centering
  made unconditional (was @4xl-only) so the narrower row stays centered in
  both stacked and side-by-side layouts. Verified via getBoundingClientRect:
  exactly 65.0% of the canvas's usable width, with equal left/right margins
  (225.4px each) at 1700px.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 20:03:10 -05:00
ShaneandClaude Opus 4.8 da0c4eab9d fix(og-generator): actually vertically center the preview row + info panel labels
The previous round's items-start fix aligned the two cards relative to EACH
OTHER (proven equal, but only proves top===center===bottom trivially) — it
never addressed the real complaint: the whole OG+Thumb row sat pinned near
the top of the canvas with dead space below on wide/tall screens. Wrapped the
row in a flex-1 container that centers it (@4xl:items-center
@4xl:justify-center) within the space remaining below the view toggle. Since
flex items don't shrink below their content size, an overflowing row still
just grows the wrapper rather than clipping (avoids the classic
flex-center-clips-on-overflow bug). Verified via getBoundingClientRect: 186.1px
of space above and below the row, exactly equal.

OG info panel: prefixed "Headline:" at the start, moved the fit-mode
indicator to right after the font size and reworded it to "(auto on)" (shown
only when auto-fit is active), and prefixed "WCAG:" before the contrast
score. Reads as "Headline: 64px (auto on) · 2 lines · WCAG: 16.3:1 AAA".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 19:59:19 -05:00
ShaneandClaude Opus 4.8 75ce4a5dac fix(og-generator): truly floating panel, top-align previews, remove zoom, frame info pill
- Panel is now position:absolute (not a flex sibling) over a full-bleed
  absolute-inset canvas — one continuous dot-grid surface behind it, instead
  of two boxes split by a shared flex edge. Canvas content gets pr-[380px]
  so it doesn't render under the panel.
- Preview row: items-center -> items-start. OG and Thumb render at identical
  size (same aspect ratio, equal flex-basis) with identical-height label rows
  above them, so top-aligning is what actually keeps the IMAGES aligned;
  items-center was aligning the outer card blocks (image + variable-height
  caption below), which visibly offset the images from each other. Verified
  via computed styles: both cards now start at the exact same top regardless
  of caption height.
- Removed the zoom control entirely (state, buttons, ZOOM_MIN/MAX, the
  style={{zoom}} CSS zoom) — it scaled the whole card including buttons/
  labels, not just the rendered image.
- OG fit-info pill: added a real border + solid background (was
  bg-surface-200 with no border, blending into the canvas) so it reads as a
  distinct panel.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 19:47:30 -05:00
ShaneandClaude Opus 4.8 b27bdcd51d feat(og-generator): floating tool panel on the right, centered preview row
- Package the top bar (title + Export @2x) into the sidebar itself, as a
  sticky header strip inside the panel rather than a separate full-width bar
- Move the panel from the left to the right, and restyle it as a floating
  card (rounded corners, shadow, inset margin) over the canvas instead of a
  full-height docked sidebar
- Preview row: add @4xl:items-center so the OG and Thumb cards align by
  vertical center when shown side by side on wide screens (they already
  stacked at medium widths); verified via computed styles (align-items:
  center, cards offset by exactly half their height difference)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 19:29:54 -05:00
ShaneandClaude Opus 4.8 166826620a fix(og-generator): logo fitBox() returned {w,h} instead of {width,height}
Spreading the result straight onto <img> as JSX props meant satori saw
w={..} h={..} instead of width/height — no intrinsic size, so the logo
filled its entire container instead of fitting a small box. Caught via a
real end-to-end upload test against the live Supabase project (secret key +
migrations now applied): a color logo rendered full-canvas instead of boxed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 16:12:42 -05:00
ShaneandClaude Opus 4.8 5ec0a89f5d feat(og-generator): custom color logos for partnerships/acquisitions
Line icons are stroke-only by brand rule, but a partner's logo (Grafana,
Multigres, etc.) needs to render in ITS OWN colors — a second asset kind
alongside the existing icon system, not a replacement for it.

- supabase/migrations/0002_logo_assets.sql: add width/height to `assets`
  (kind + storage_path already existed from 0001)
- lib/assets/sanitize-svg.ts: sanitizeLogoSvg() — a separate allowlist that
  PRESERVES fill/stroke/gradients/style color declarations (unlike the icon
  sanitizer, which strips them for the stroke-only treatment), while still
  blocking scripts, event handlers, and external references (internal `#id`
  refs are kept so gradient defs/<use> keep working). Unit-tested against a
  mixed color+attack payload.
- lib/supabase/assets.ts: insertLogoAsset() uploads to the og-assets Storage
  bucket (raster-safe, unlike inline SVG bodies) and records client-measured
  width/height; rowToIcon exposes kind/url/width/height for both kinds.
- app/api/assets/route.ts: POST branches on a `kind` field ('icon', default,
  or 'logo'); logo accepts SVG/PNG/JPEG/WebP up to 2MB, requires width+height.
- app/api/og/route.tsx: icon slots render logos as-is (no stroke
  normalization), fit to their natural aspect ratio via a new fitBox() helper.
- app/page.tsx: a second "+ Upload logo (color)" control measures the file's
  natural dimensions in-browser (Image().onload) before POSTing; the icon
  picker renders logo entries as real <img> (actual colors) instead of the
  forced-stroke SVG redraw.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 16:03:02 -05:00
ShaneandClaude Opus 4.8 a15601fe9c docs(og-generator): simplify reference-examples intake to a single links file
Drop a plain figma-links.txt (one URL per line) instead of pairing every
image with a .figma.txt file — Claude Code pulls the screenshot/design
context per link directly via the Figma integration. Also notes this same
drop zone doubles as the intake for a future image-generation training set.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 15:34:54 -05:00