Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions assets/branding/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Identidad de Generador de QR

`logo.svg` es la fuente vectorial: tres módulos y una etiqueta diagonal, redibujados a partir del concepto C generado con `image_gen`. Usa Sello (`#0F4C5C`). Funciona sin depender del color y sin texto a 16 px. No representa un QR escaneable.

Se eligió C por su silueta simple y sus espacios abiertos. A pierde detalle interno al reducirse; B se parece a un comando genérico de impresión. El símbolo no usa globos, teléfonos, cámaras, letras de Google ni apretones de manos. La revisión visual no encontró parecido con WhatsApp, Instagram, Google o Mercado Pago; no es una búsqueda exhaustiva de marcas registradas.

No se necesita versión horizontal en este issue: la cabecera queda para el siguiente. El SVG tiene fondo transparente para ese uso futuro; todos los PNG de instalación llevan fondo Papel opaco (`#F7F6F2`) y sin esquinas recortadas.

## Regenerar íconos

Desde la raíz (Python 3 y biblioteca de sistema Cairo):

```sh
python3 -m venv /tmp/qr-branding-venv
/tmp/qr-branding-venv/bin/pip install CairoSVG==2.9.1
/tmp/qr-branding-venv/bin/python scripts/generate_branding.py
flutter test test/branding_test.dart
```

El script sólo rasteriza el SVG local: no genera imágenes con IA, no llama APIs ni requiere claves. Produce favicon de 32 px, íconos de 192/512 px, maskable de 192/512 px y Apple Touch de 180 px. El navegador reduce el favicon a 16 px.

En maskable, el dibujo se escala al 72% y se centra: su esquina extrema queda a menos del 38,2% del lado desde el centro, dentro del círculo seguro de radio 40%. El test verifica los píxeles de tinta, dimensiones y opacidad de los archivos entregados.

## Evidencia

[Registro y prompts](../../docs/issue-28/generation.md), [lámina](../../docs/issue-28/identity-board.png), [pestaña real de Chromium](../../docs/issue-28/favicon-browser.png) y [simulación de iPhone](../../docs/issue-28/iphone-simulation.png).

Los tres originales generados quedan en `docs/issue-28/concepts/`, fuera de los assets de la app. Para explorar la lámina y ver el logo en tamaño real:

```sh
python3 -m http.server 8828
```

Abrí http://localhost:8828/docs/issue-28/preview.html.

Esta entrega define el logo sobre la paleta existente del issue #26. La frase histórica de `PRODUCT.md` sobre ausencia de identidad describe el estado previo a esta entrega; no cambia el nombre funcional ni los principios del producto.
8 changes: 8 additions & 0 deletions assets/branding/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/issue-28/concepts/a-etiqueta.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/issue-28/concepts/b-impresion.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/issue-28/concepts/c-modulos.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/issue-28/favicon-browser.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
44 changes: 44 additions & 0 deletions docs/issue-28/generation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Generación de conceptos

Herramienta integrada `image_gen`; sin CLI ni claves API. Tres llamadas, un intento por concepto, sin regeneraciones.

## Concepto A
Tiempo observado: 39.5 s.

```text
Use case: logo-brand. Create one original flat logo concept for Generador de QR, a practical tool producing printed QR labels for small Argentine shops. Concept A: a compact square paper label with a single cut corner at bottom right and three bold square voids arranged like QR registration modules, extremely reduced geometry. Dark petroleum blue #0F4C5C symbol on warm paper #F7F6F2. Centered single symbol, no text, no shadows, no gradients, no mockup. Must remain distinct and readable at 16 pixels and in pure black and white. Thick shapes and generous negative space. No speech bubbles, phones, cameras, letter G, multicolor motifs, handshakes, or resemblance to WhatsApp Instagram Google Mercado Pago. Vector-friendly design, square canvas.
```

## Concepto B
Tiempo observado: 46.0 s.

```text
Use case: logo-brand. One original logo concept B for Generador de QR, a professional tool for printing QR labels in Argentine shops. A minimalist paper strip exiting a horizontal printing slot: two chunky horizontal geometric bars and one square cutout in the paper, compact balanced silhouette. Single dark petroleum #0F4C5C on warm paper #F7F6F2. Single centered mark without text, flat vector-like geometry, no gradients or shadows. Legible at 16px and pure black and white; very few thick shapes. Avoid speech bubbles, phones, cameras, G letters, colorful branding, handshake, and resemblance to WhatsApp Instagram Google Mercado Pago. Square canvas.
```

## Concepto C
Tiempo observado: 44.0 s.

```text
Use case: logo-brand. Create one original concept C for Generador de QR, a practical QR label printing app for Argentine shops. Abstract stamp: three solid chunky squares in an L arrangement, with a short diagonal rectangular block occupying bottom-right, suggesting a label being applied. Just four separate bold shapes, no enclosing border, no tiny QR details. Single dark petroleum blue #0F4C5C on warm paper #F7F6F2. Flat vector-like symbol centered on square canvas. No text, shadows, gradients, speech bubbles, phones, camera outline, G letters, handshake, multicolor or resemblance to WhatsApp Instagram Google Mercado Pago. Must work at 16px and in black and white.
```

## Resultado y limitaciones

Se eligió C y se redibujó con cuatro primitivas SVG, ajustando proporciones, radios y espacios para una grilla de 64 unidades. El resultado generado era raster y traía variaciones sutiles de tono; el SVG fija el color exacto y elimina esas variaciones. No se usó autotrace ni se tomó el raster como ícono final. A conserva su silueta a 16 px pero pierde detalle: esa limitación motivó descartarlo, sin gastar otra generación. B conserva la silueta de impresión pero es menos propio del producto.

Las imágenes originales se copiaron desde `$CODEX_HOME/generated_images/` sin modificarlas. La herramienta tardó aproximadamente 39, 46 y 44 segundos por concepto (tiempo de llamada observado, no de GPU). No hubo errores ni reintentos. Los prompts completos de arriba son los enviados a la herramienta integrada. No se usaron CLI de generación ni API keys.

El README de `maxiar-org/ai-dev-team` consultado durante la tarea no contenía la sección «Generación de imágenes»; se aplicaron las instrucciones del issue y de la skill `imagegen`.

## Verificación

- TDD: primero fallaron los tests nuevos por la paleta Flutter y el tamaño del favicon; luego pasaron con los assets y metadatos nuevos.
- `flutter analyze`: sin observaciones. `flutter test`: 88 tests en verde. `flutter build web --release`: correcto.
- Playwright, viewport 390 × 844: inicio → WhatsApp / Instagram → datos → etiqueta → botón Copiar link. Capturas de ambas vistas previas junto a esta nota. No se verificó el portapapeles del sistema ni una instalación física en iPhone.
- Pestaña real de Chromium con interfaz visible en Xvfb, capturada desde el servidor del build; no es una pestaña dibujada en HTML.
- Lámina en navegador con SVG a 16/32 px y negro puro, y simulación de inicio iOS que usa el PNG real. No es una captura de un iPhone físico.
- Detector Impeccable sobre los HTML modificados: sin hallazgos. No evalúa los widgets Flutter ni certifica el logo; la revisión de la identidad es visual.
- Impeccable avisó que `PRODUCT.md` dice web aunque hay proyecto iOS. Se conserva el alcance PWA explícito del producto; no se cambia la plataforma documentada en este issue.

Revisión final independiente de identidad: **ship**, sin hallazgos materiales. Revisión documental: paleta y alcance coherentes; se alineó la versión de CairoSVG del script con las instrucciones de reproducción.
Binary file added docs/issue-28/identity-board.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/issue-28/instagram-preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/issue-28/iphone-simulation.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions docs/issue-28/preview.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="es-AR"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Identidad · Generador de QR</title>
<style>
@font-face{font-family:Plex;src:url('../../assets/fonts/IBMPlexSans-Variable.ttf')}*{box-sizing:border-box}body{margin:0;background:#F7F6F2;color:#1B2430;font:16px Plex,sans-serif}main{max-width:1080px;margin:auto;padding:40px}h1{font-size:32px;margin:0 0 12px}h2{font-size:22px;margin:32px 0 16px}p{max-width:65ch;line-height:1.5}.concepts{display:flex;gap:24px}.concepts figure{margin:0;flex:1}.concepts img{width:100%;background:#F7F6F2}.final{display:flex;align-items:center;gap:40px;flex-wrap:wrap}.final>img{width:220px}.samples{display:flex;gap:24px;align-items:center}.samples figure{margin:0}.samples figcaption{margin-top:10px}.mono{filter:grayscale(1) brightness(0)}.phone{width:300px;height:540px;border:8px solid #1B2430;border-radius:44px;background:#D8DEDC;padding:20px;color:#1B2430}.status{display:flex;justify-content:space-between;font-weight:600}.island{width:82px;height:22px;background:#1B2430;border-radius:20px}.apps{padding-top:45px;display:flex;gap:20px;align-items:start}.app{width:95px;text-align:center;font-size:12px}.app img{width:64px;height:64px;border-radius:15px;display:block;margin:0 auto 8px}.placeholder{width:64px;height:64px;background:#F7F6F2;border-radius:15px;margin:0 auto 8px}.home{width:100px;height:4px;background:#1B2430;border-radius:4px;margin:300px auto 0}.install{display:flex;gap:48px;align-items:center;flex-wrap:wrap}a{color:#0F4C5C}@media(max-width:650px){main{padding:24px}.concepts{flex-direction:column}}
</style><main>
<h1>Una marca para la etiqueta</h1><p>Generador de QR · Propuestas y aplicación del concepto elegido. Paleta Sello y Papel; geometría simple para reconocer la herramienta de Maxi.</p>
<h2>Tres conceptos generados con image_gen</h2><div class="concepts">
<figure><img src="concepts/a-etiqueta.png" alt="Etiqueta con tres localizadores QR"><figcaption>A · Etiqueta. Demasiados detalles a 16 px.</figcaption></figure>
<figure><img src="concepts/b-impresion.png" alt="Papel saliendo de una impresora"><figcaption>B · Impresión. Puede leerse como una impresora genérica.</figcaption></figure>
<figure><img src="concepts/c-modulos.png" alt="Tres cuadrados y una pieza diagonal"><figcaption>C · Módulos. Elegido por su lectura simple.</figcaption></figure></div>
<h2>Logo final redibujado en SVG</h2><div class="final"><img src="../../assets/branding/logo.svg" alt="Logo final"><div><p>Tres módulos remiten al QR; la pieza diagonal sugiere una etiqueta que se aplica. Sin letras ni colores prestados de los canales.</p><div class="samples"><figure><img src="../../assets/branding/logo.svg" width="16" height="16" alt="Logo a 16 píxeles"><figcaption>16 px</figcaption></figure><figure><img src="../../assets/branding/logo.svg" width="32" height="32" alt="Logo a 32 píxeles"><figcaption>32 px</figcaption></figure><figure><img class="mono" src="../../assets/branding/logo.svg" width="64" height="64" alt="Logo negro"><figcaption>Blanco y negro</figcaption></figure><figure><img class="mono" src="../../assets/branding/logo.svg" width="16" height="16" alt="Logo negro a 16 píxeles"><figcaption>16 px B/N</figcaption></figure></div></div></div>
<h2>Agregar a inicio en iPhone</h2><div class="install"><div class="phone"><div class="status"><span>9:41</span><div class="island"></div><span>100%</span></div><div class="apps"><div class="app"><img src="../../web/icons/apple-touch-icon.png" alt="Ícono instalado">Generador de QR</div><div class="app"><div class="placeholder"></div>Otra app</div></div><div class="home"></div></div><p>Simulación ilustrativa, no captura de iOS. Usa el archivo real de 180 × 180 px sobre fondo opaco. El sistema aplica las esquinas redondeadas; el PNG se entrega cuadrado.</p></div>
<p><a href="generation.md">Prompts, tiempos y registro de generación</a></p></main></html>
Binary file added docs/issue-28/whatsapp-preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 42 additions & 0 deletions scripts/generate_branding.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""Rasterize the canonical SVG. Run from any directory with CairoSVG 2.9.1."""

from pathlib import Path
import xml.etree.ElementTree as ET

import cairosvg

ROOT = Path(__file__).resolve().parents[1]
SOURCE = ROOT / "assets/branding/logo.svg"
PAPER = "#F7F6F2"


def render(size, *, maskable=False):
source = ET.parse(SOURCE).getroot()
# The artwork spans 48/64. Scaling to 72% places even the corners
# inside radius 40%: sqrt(2) * 0.375 * 0.72 = 0.382 < 0.4.
scale = 0.72 if maskable else 1
inset = (64 - 64 * scale) / 2
children = "".join(ET.tostring(child, encoding="unicode") for child in source)
svg = f'''<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<rect width="64" height="64" fill="{PAPER}"/>
<g fill="{source.attrib['fill']}" transform="translate({inset} {inset}) scale({scale})">
{children}
</g>
</svg>'''
return cairosvg.svg2png(bytestring=svg.encode(), output_width=size, output_height=size)


def main():
outputs = {"favicon.png": (32, False), "icons/apple-touch-icon.png": (180, False)}
for size in (192, 512):
outputs[f"icons/Icon-{size}.png"] = (size, False)
outputs[f"icons/Icon-maskable-{size}.png"] = (size, True)
for name, (size, maskable) in outputs.items():
path = ROOT / "web" / name
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(render(size, maskable=maskable))
print(path.relative_to(ROOT))


if __name__ == "__main__":
main()
68 changes: 68 additions & 0 deletions test/branding_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import 'dart:convert';
import 'dart:io';
import 'dart:ui' as ui;

import 'package:flutter_test/flutter_test.dart';

void main() {
TestWidgetsFlutterBinding.ensureInitialized();

test(
'installation metadata uses the brand palette and dedicated Apple icon',
() {
final manifest = jsonDecode(File('web/manifest.json').readAsStringSync());
expect(manifest['background_color'], '#F7F6F2');
expect(manifest['theme_color'], '#0F4C5C');
expect(
File('web/index.html').readAsStringSync(),
contains('href="icons/apple-touch-icon.png"'),
);
expect(File('assets/branding/logo.svg').existsSync(), isTrue);
},
);

test(
'installation icons have correct dimensions, opacity and safe artwork',
() async {
final sizes = {
'favicon.png': 32,
'icons/Icon-192.png': 192,
'icons/Icon-512.png': 512,
'icons/Icon-maskable-192.png': 192,
'icons/Icon-maskable-512.png': 512,
'icons/apple-touch-icon.png': 180,
};
for (final entry in sizes.entries) {
final codec = await ui.instantiateImageCodec(
File('web/${entry.key}').readAsBytesSync(),
);
final image = (await codec.getNextFrame()).image;
expect(image.width, entry.value, reason: entry.key);
expect(image.height, entry.value, reason: entry.key);
final bytes = (await image.toByteData())!;
var inkPixels = 0;
for (var y = 0; y < image.height; y++) {
for (var x = 0; x < image.width; x++) {
final offset = (y * image.width + x) * 4;
expect(bytes.getUint8(offset + 3), 255, reason: entry.key);
if (bytes.getUint8(offset) < 120) {
inkPixels++;
if (entry.key.contains('maskable')) {
final dx = x + 0.5 - image.width / 2;
final dy = y + 0.5 - image.height / 2;
expect(
dx * dx + dy * dy,
lessThanOrEqualTo(image.width * image.width * 0.16),
reason: 'Artwork must fit the central 80% circle',
);
}
}
}
}
expect(inkPixels, greaterThan(image.width * image.height * 0.1));
image.dispose();
codec.dispose();
}
},
);
}
Binary file modified web/favicon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified web/icons/Icon-192.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified web/icons/Icon-512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified web/icons/Icon-maskable-192.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified web/icons/Icon-maskable-512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added web/icons/apple-touch-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="Generador de QR">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<link rel="apple-touch-icon" sizes="180x180" href="icons/apple-touch-icon.png">

<meta name="theme-color" content="#0F4C5C">

<!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/>
Expand Down
4 changes: 2 additions & 2 deletions web/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
"short_name": "Generador de QR",
"start_url": ".",
"display": "standalone",
"background_color": "#0175C2",
"theme_color": "#0175C2",
"background_color": "#F7F6F2",
"theme_color": "#0F4C5C",
"description": "Cartelería con códigos QR para tu comercio.",
"orientation": "portrait-primary",
"prefer_related_applications": false,
Expand Down
Loading