diff --git a/assets/apple-touch-icon.png b/assets/apple-touch-icon.png new file mode 100644 index 0000000..616a72b Binary files /dev/null and b/assets/apple-touch-icon.png differ diff --git a/assets/favicon-16.png b/assets/favicon-16.png new file mode 100644 index 0000000..5ae763a Binary files /dev/null and b/assets/favicon-16.png differ diff --git a/assets/favicon-32.png b/assets/favicon-32.png new file mode 100644 index 0000000..71a39be Binary files /dev/null and b/assets/favicon-32.png differ diff --git a/assets/favicon-48.png b/assets/favicon-48.png new file mode 100644 index 0000000..2f7a5cd Binary files /dev/null and b/assets/favicon-48.png differ diff --git a/assets/favicon.ico b/assets/favicon.ico new file mode 100644 index 0000000..f4b7158 Binary files /dev/null and b/assets/favicon.ico differ diff --git a/assets/favicon.svg b/assets/favicon.svg new file mode 100644 index 0000000..d092d00 --- /dev/null +++ b/assets/favicon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/fonts/Inter-OFL.txt b/assets/fonts/Inter-OFL.txt new file mode 100644 index 0000000..9b2ca37 --- /dev/null +++ b/assets/fonts/Inter-OFL.txt @@ -0,0 +1,92 @@ +Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION AND CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/assets/fonts/InterDisplay-ExtraBold.otf b/assets/fonts/InterDisplay-ExtraBold.otf new file mode 100644 index 0000000..a49c52e Binary files /dev/null and b/assets/fonts/InterDisplay-ExtraBold.otf differ diff --git a/assets/fonts/InterDisplay-SemiBold.otf b/assets/fonts/InterDisplay-SemiBold.otf new file mode 100644 index 0000000..df09797 Binary files /dev/null and b/assets/fonts/InterDisplay-SemiBold.otf differ diff --git a/assets/forge-icon-1024.png b/assets/forge-icon-1024.png new file mode 100644 index 0000000..f579750 Binary files /dev/null and b/assets/forge-icon-1024.png differ diff --git a/assets/forge-mark-1024.png b/assets/forge-mark-1024.png new file mode 100644 index 0000000..72ac2e5 Binary files /dev/null and b/assets/forge-mark-1024.png differ diff --git a/assets/forge.icns b/assets/forge.icns new file mode 100644 index 0000000..e53edc3 Binary files /dev/null and b/assets/forge.icns differ diff --git a/assets/forge.png b/assets/forge.png new file mode 100644 index 0000000..4ffc601 Binary files /dev/null and b/assets/forge.png differ diff --git a/assets/forge.svg b/assets/forge.svg new file mode 100644 index 0000000..8126d4c --- /dev/null +++ b/assets/forge.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + diff --git a/assets/logo.svg b/assets/logo.svg new file mode 100644 index 0000000..9e2a553 --- /dev/null +++ b/assets/logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/crates/forge_cli/assets/forge-icon-1024.png b/crates/forge_cli/assets/forge-icon-1024.png new file mode 100644 index 0000000..528ff1f Binary files /dev/null and b/crates/forge_cli/assets/forge-icon-1024.png differ diff --git a/crates/forge_cli/src/bundler/icons.rs b/crates/forge_cli/src/bundler/icons.rs index 6f8fe18..4d6916e 100644 --- a/crates/forge_cli/src/bundler/icons.rs +++ b/crates/forge_cli/src/bundler/icons.rs @@ -18,11 +18,19 @@ //! - Or specify path in manifest: `[bundle] icon = "path/to/icon"` use anyhow::{bail, Context, Result}; -use image::{imageops::FilterType, DynamicImage, ImageFormat, Rgba, RgbaImage}; +use image::{imageops::FilterType, DynamicImage, ImageFormat}; +// `Rgba`/`RgbaImage` are only used by the Windows-only `save_wide` below. +#[cfg(windows)] +use image::{Rgba, RgbaImage}; use std::fs; use std::path::{Path, PathBuf}; use std::process::Command; +/// The Forge brand icon (1024x1024 ember plate + emblem + "Forge" wordmark), +/// embedded at build time. Used as the default art produced by +/// `forge icon create`. Regenerate via `scripts/generate-brand-icon.sh`. +const FORGE_BRAND_ICON: &[u8] = include_bytes!("../../assets/forge-icon-1024.png"); + /// Minimum recommended icon size pub const MIN_ICON_SIZE: u32 = 512; /// Optimal icon size for best quality across all platforms @@ -274,46 +282,24 @@ impl IconProcessor { } } - /// Create a placeholder icon (for `forge icon create` command) + /// Create the default Forge-branded icon at the requested size + /// (for the `forge icon create` command). + /// + /// Decodes the embedded 1024x1024 Forge brand master ([`FORGE_BRAND_ICON`]) + /// and resizes it to `size`x`size`. The master is square and carries an + /// alpha channel (the rounded-corner plate), so the result satisfies the + /// square + transparency parts of [`validate`](Self::validate); a `size` + /// below [`MIN_ICON_SIZE`] still fails the minimum-size check, exactly as a + /// custom icon of that size would. + /// + /// This is intentionally the real Forge logo rather than a generic + /// placeholder, so scaffolded apps start with a polished identity. Apps are + /// expected to replace it with their own icon before release. pub fn create_placeholder(size: u32) -> Self { - let mut img = RgbaImage::new(size, size); - - // Create a blue-purple gradient - for y in 0..size { - for x in 0..size { - let gradient_x = x as f32 / size as f32; - let gradient_y = y as f32 / size as f32; - let gradient = (gradient_x + gradient_y) / 2.0; - - // Blue to purple gradient - let r = (60.0 + gradient * 100.0) as u8; - let g = (80.0 + gradient * 60.0) as u8; - let b = (180.0 + gradient * 40.0) as u8; - - // Simple circular mask for rounded appearance - let cx = size as f32 / 2.0; - let cy = size as f32 / 2.0; - let dx = x as f32 - cx; - let dy = y as f32 - cy; - let dist = (dx * dx + dy * dy).sqrt(); - let radius = size as f32 * 0.45; - - let alpha = if dist < radius { - 255 - } else if dist < radius + 20.0 { - // Smooth edge - ((radius + 20.0 - dist) / 20.0 * 255.0) as u8 - } else { - 0 - }; - - img.put_pixel(x, y, Rgba([r, g, b, alpha])); - } - } - - Self { - source_image: DynamicImage::ImageRgba8(img), - } + let master = image::load_from_memory(FORGE_BRAND_ICON) + .expect("embedded Forge brand icon must be a valid PNG"); + let source_image = master.resize_exact(size, size, FilterType::Lanczos3); + Self { source_image } } /// Get the source image dimensions diff --git a/crates/forge_cli/src/main.rs b/crates/forge_cli/src/main.rs index 374f138..90bdabe 100644 --- a/crates/forge_cli/src/main.rs +++ b/crates/forge_cli/src/main.rs @@ -1097,7 +1097,7 @@ fn icon_usage() { fn cmd_icon_create(output_path: &Path) -> Result<()> { use bundler::{IconProcessor, RECOMMENDED_ICON_SIZE}; - println!("Creating placeholder icon at {}", output_path.display()); + println!("Creating Forge-branded icon at {}", output_path.display()); // Check if file already exists if output_path.exists() { @@ -1112,14 +1112,15 @@ fn cmd_icon_create(output_path: &Path) -> Result<()> { let processor = IconProcessor::create_placeholder(RECOMMENDED_ICON_SIZE); processor.save(output_path)?; - println!("\nPlaceholder icon created!"); + println!("\nForge-branded icon created!"); println!(" Path: {}", output_path.display()); println!( " Size: {}x{} pixels", RECOMMENDED_ICON_SIZE, RECOMMENDED_ICON_SIZE ); println!(); - println!("IMPORTANT: Replace this placeholder with your actual app icon before release."); + println!("IMPORTANT: This is the Forge brand icon, provided as a starting point."); + println!("Replace it with your own app icon before release — do not ship the Forge logo."); println!(); println!("Icon Requirements:"); println!(" • Format: PNG with transparency (RGBA)"); diff --git a/examples/example-deno-app/assets/icon.png b/examples/example-deno-app/assets/icon.png index 2338ae2..528ff1f 100644 Binary files a/examples/example-deno-app/assets/icon.png and b/examples/example-deno-app/assets/icon.png differ diff --git a/examples/svelte-app/assets/icon.png b/examples/svelte-app/assets/icon.png index 2338ae2..528ff1f 100644 Binary files a/examples/svelte-app/assets/icon.png and b/examples/svelte-app/assets/icon.png differ diff --git a/examples/system-monitor/assets/icon.png b/examples/system-monitor/assets/icon.png index 2338ae2..528ff1f 100644 Binary files a/examples/system-monitor/assets/icon.png and b/examples/system-monitor/assets/icon.png differ diff --git a/examples/text-editor/assets/icon.png b/examples/text-editor/assets/icon.png index 2338ae2..528ff1f 100644 Binary files a/examples/text-editor/assets/icon.png and b/examples/text-editor/assets/icon.png differ diff --git a/examples/todo-app/assets/icon.png b/examples/todo-app/assets/icon.png index 2338ae2..528ff1f 100644 Binary files a/examples/todo-app/assets/icon.png and b/examples/todo-app/assets/icon.png differ diff --git a/examples/weather-app/assets/icon.png b/examples/weather-app/assets/icon.png index 2338ae2..528ff1f 100644 Binary files a/examples/weather-app/assets/icon.png and b/examples/weather-app/assets/icon.png differ diff --git a/scripts/generate-brand-icon.sh b/scripts/generate-brand-icon.sh new file mode 100755 index 0000000..0f7f31a --- /dev/null +++ b/scripts/generate-brand-icon.sh @@ -0,0 +1,158 @@ +#!/usr/bin/env bash +# +# generate-brand-icon.sh — Build the Forge brand icon master + favicon set. +# +# Source of truth is assets/forge.png (the black crossed-hammers + anvil emblem +# on a transparent background). That art is non-square (392x310), too small for +# bundling, and invisible on dark docks. This script composites it onto an +# ember-gradient rounded-rect "app icon template" plate to produce a single +# 1024x1024 square master, then derives the web favicon variants from it. +# +# The 1024 master is the only input the rest of Forge's pipeline needs: forge's +# IconProcessor (crates/forge_cli/src/bundler/icons.rs) fans it out to .icns, +# MSIX tiles, and Linux hicolor sizes by resizing. +# +# Requires: ImageMagick (`magick`), and on macOS `iconutil` for the .icns. +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +SRC="$ROOT/assets/forge.png" +OUT="$ROOT/assets" + +SIZE=1024 # master canvas (square) +MARGIN=44 # transparent inset around the plate (macOS-template style) +EMBLEM_W=430 # emblem width painted on the canvas (raised to make room for the wordmark) +EMBLEM_Y=232 # emblem top offset from the plate top (gravity north) + +# "Forge" wordmark. InterDisplay is the optical-size cut meant for large/display +# use (logos, headlines); the plain Inter cut is for body text and reads loose here. +FONT="$ROOT/assets/fonts/InterDisplay-SemiBold.otf" +WORDMARK="Forge" +WORD_PT=176 # wordmark point size +WORD_Y=596 # wordmark top offset from the plate top (gravity north) +WORD_FILL="#000000" # black wordmark fill +WORD_STROKE="#ffffff" # white keyline around the wordmark +WORD_STROKE_W=2 # stroke thickness in px + +PLATE_SIZE=$((SIZE - 2 * MARGIN)) +PLATE_X1=$((SIZE - MARGIN)) +PLATE_Y1=$((SIZE - MARGIN)) +# Apple's continuous-corner ratio (~0.2237 of the side length). +RADIUS=$(awk "BEGIN{printf \"%d\", 0.2237 * $PLATE_SIZE}") + +[ -f "$SRC" ] || { echo "error: source emblem not found: $SRC" >&2; exit 1; } +command -v magick >/dev/null || { echo "error: ImageMagick 'magick' not found" >&2; exit 1; } + +TMP="$(mktemp -d)" +trap 'rm -rf "$TMP"' EXIT + +echo "==> Building ember plate (${SIZE}x${SIZE}, inset ${MARGIN}px, radius ${RADIUS}px)" + +# 1. Ember linear gradient: bright forge-orange at top -> deep red at the base. +magick -size ${SIZE}x${SIZE} gradient:'#ffb15a-#5e0d05' "$TMP/base.png" + +# 2. Soft warm radial highlight, composited over the gradient. This lifts the +# black emblem off the dark lower half of the gradient so the anvil reads. +magick -size ${SIZE}x${SIZE} radial-gradient:'#fff0d8a0-#fff0d800' "$TMP/glow.png" +magick "$TMP/base.png" "$TMP/glow.png" -compose over -composite "$TMP/bg.png" + +# 3. Rounded-rect "app icon" mask, applied to the gradient via CopyOpacity so +# the corners (and inset margin) become transparent. +magick -size ${SIZE}x${SIZE} xc:none -fill white \ + -draw "roundrectangle ${MARGIN},${MARGIN},${PLATE_X1},${PLATE_Y1} ${RADIUS},${RADIUS}" \ + "$TMP/mask.png" +magick "$TMP/bg.png" "$TMP/mask.png" \ + -alpha off -compose CopyOpacity -composite "$TMP/plate.png" + +[ -f "$FONT" ] || { echo "error: Inter font not found: $FONT" >&2; exit 1; } + +# 4. A white silhouette of the emblem, blurred into a soft halo. Sits behind the +# black emblem to guarantee contrast on the darkest part of the plate. +magick "$SRC" -resize ${EMBLEM_W}x \ + -channel RGB -evaluate set 100% +channel \ + "$TMP/white.png" + +# 5. Compose the lockup: plate <- halo <- raised emblem <- "Forge" wordmark. +# The emblem sits in the upper portion; the wordmark sits below it. +magick "$TMP/plate.png" \ + \( "$TMP/white.png" -blur 0x10 -channel A -evaluate multiply 0.5 +channel \) \ + -gravity north -geometry +0+${EMBLEM_Y} -composite \ + \( "$SRC" -resize ${EMBLEM_W}x \) \ + -gravity north -geometry +0+${EMBLEM_Y} -composite \ + -font "$FONT" -pointsize ${WORD_PT} -gravity north \ + -fill "${WORD_STROKE}" -stroke "${WORD_STROKE}" -strokewidth $((WORD_STROKE_W * 2)) \ + -annotate +0+${WORD_Y} "${WORDMARK}" \ + -fill "${WORD_FILL}" -stroke none \ + -annotate +0+${WORD_Y} "${WORDMARK}" \ + "$OUT/forge-icon-1024.png" + +echo "==> Wrote $OUT/forge-icon-1024.png (emblem + \"${WORDMARK}\" wordmark, Inter Display SemiBold)" + +# --------------------------------------------------------------------------- +# Brand MARK: the emblem on the same ember plate, WITHOUT the wordmark, with the +# emblem centered and a touch larger. Used for the docs navbar logo, where the +# adjacent "Forge" title text already supplies the word. Reuses the same plate +# so the mark and the full icon share background, corners, and glow exactly. +# --------------------------------------------------------------------------- +MARK_EMBLEM_W=600 +magick "$SRC" -resize ${MARK_EMBLEM_W}x -channel RGB -evaluate set 100% +channel "$TMP/markwhite.png" +magick "$TMP/plate.png" \ + \( "$TMP/markwhite.png" -blur 0x10 -channel A -evaluate multiply 0.5 +channel \) \ + -gravity center -composite \ + \( "$SRC" -resize ${MARK_EMBLEM_W}x \) \ + -gravity center -composite \ + "$OUT/forge-mark-1024.png" + +# logo.svg: vector wrapper around a 128px raster of the mark (matches the favicon +# approach). The navbar renders it at ~32px (64px hi-dpi), so 128px stays crisp +# while keeping the file lightweight. +MARK_B64="$(magick "$OUT/forge-mark-1024.png" -resize 128x128 png:- | base64 | tr -d '\n')" +cat > "$OUT/logo.svg" < + + +SVG +echo "==> Wrote $OUT/forge-mark-1024.png + $OUT/logo.svg (emblem-only mark)" + +# --------------------------------------------------------------------------- +# Favicon set, derived from the same master so the brand matches pixel-for-pixel. +# --------------------------------------------------------------------------- +MASTER="$OUT/forge-icon-1024.png" + +echo "==> Building favicon set" +magick "$MASTER" -resize 16x16 "$OUT/favicon-16.png" +magick "$MASTER" -resize 32x32 "$OUT/favicon-32.png" +magick "$MASTER" -resize 48x48 "$OUT/favicon-48.png" +magick "$MASTER" -resize 180x180 "$OUT/apple-touch-icon.png" +# Multi-resolution .ico for legacy browsers. +magick "$OUT/favicon-16.png" "$OUT/favicon-32.png" "$OUT/favicon-48.png" "$OUT/favicon.ico" + +# favicon.svg: a vector wrapper around a small (64px) raster of the master. Tab +# favicons render at ~16-32px, so 64px keeps the file lightweight while matching +# the brand exactly. The emblem art is raster (assets/forge.png embeds no clean +# vector), so a baked raster is the faithful choice over re-vectorizing. Larger +# raster needs (bookmarks, home-screen) are served by favicon.ico / apple-touch. +B64="$(magick "$MASTER" -resize 64x64 png:- | base64 | tr -d '\n')" +cat > "$OUT/favicon.svg" < + + +SVG +echo " favicon.svg ($(wc -c < "$OUT/favicon.svg" | tr -d ' ') bytes), favicon.ico, favicon-{16,32,48}.png, apple-touch-icon.png" + +# --------------------------------------------------------------------------- +# macOS .icns (optional; needs iconutil). Brand-level artifact in assets/. +# --------------------------------------------------------------------------- +if command -v iconutil >/dev/null; then + echo "==> Building forge.icns (macOS)" + ICONSET="$TMP/forge.iconset" + mkdir -p "$ICONSET" + for s in 16 32 128 256 512; do + magick "$MASTER" -resize ${s}x${s} "$ICONSET/icon_${s}x${s}.png" + magick "$MASTER" -resize $((s*2))x$((s*2)) "$ICONSET/icon_${s}x${s}@2x.png" + done + iconutil -c icns -o "$OUT/forge.icns" "$ICONSET" + echo " $OUT/forge.icns" +fi + +echo "==> Done. Master: $MASTER" diff --git a/site/astro.config.mjs b/site/astro.config.mjs index 0870063..f52ae33 100644 --- a/site/astro.config.mjs +++ b/site/astro.config.mjs @@ -21,6 +21,10 @@ export default defineConfig({ src: './src/assets/logo.svg', replacesTitle: false, }, + // Brand favicon (Forge emblem on the ember plate). Starlight injects the + // primary for this; the head[] entries below add the .ico fallback + // for legacy browsers and the apple-touch-icon for iOS home screens. + favicon: '/favicon.svg', expressiveCode: { shiki: { // Custom language grammars @@ -76,6 +80,22 @@ export default defineConfig({ content: 'https://forge-deno.com/og-image.png', }, }, + { + tag: 'link', + attrs: { + rel: 'icon', + href: '/favicon.ico', + sizes: '16x16 32x32 48x48', + }, + }, + { + tag: 'link', + attrs: { + rel: 'apple-touch-icon', + href: '/apple-touch-icon.png', + sizes: '180x180', + }, + }, ], }), tailwind({ applyBaseStyles: false }), diff --git a/site/public/apple-touch-icon.png b/site/public/apple-touch-icon.png new file mode 100644 index 0000000..d92d1f9 Binary files /dev/null and b/site/public/apple-touch-icon.png differ diff --git a/site/public/favicon-32.png b/site/public/favicon-32.png new file mode 100644 index 0000000..f13bcc1 Binary files /dev/null and b/site/public/favicon-32.png differ diff --git a/site/public/favicon.ico b/site/public/favicon.ico new file mode 100644 index 0000000..f4b7158 Binary files /dev/null and b/site/public/favicon.ico differ diff --git a/site/public/favicon.svg b/site/public/favicon.svg index b79e831..20f4862 100644 --- a/site/public/favicon.svg +++ b/site/public/favicon.svg @@ -1,4 +1,3 @@ - - - + + diff --git a/site/src/assets/logo.svg b/site/src/assets/logo.svg index b79e831..9e2a553 100644 --- a/site/src/assets/logo.svg +++ b/site/src/assets/logo.svg @@ -1,4 +1,3 @@ - - - + +