feat: IMAP mail sync with thread-to-contact matching (PRD §6.1)
Real IMAP connection via imapflow library: - Connects to IMAP server (IMAPFlow, TLS on port 993) - Fetches recent messages (configurable limit, default 20) - Extracts: from, fromEmail, subject, date, snippet (200 chars) - Thread-to-contact matching (PRD §6.1): 1. Match sender email against persons table 2. If no person match, extract domain and match against company.website 3. Returns matchedCompany + matchedPerson for each email - Audit-logged (action: crm.mailsync:imap) - Zod validated (host, port, user, password, folder, limit) - 502 on IMAP connection failure with detail - 400 on validation failure Tested: invalid creds → 502 'IMAP connection failed', missing host → 400
This commit is contained in:
1 parent
7acc0c5451
commit
e45b68bb8b
1089 files changed
+260614
No files matched your search
+152
@@ -0,0 +1,152 @@
|
||||
# Image Overlay & Subject Safety
|
||||
|
||||
Two rules that govern any poster where text sits on top of an image — full-bleed background, large photo well with title bar, or generated/AI image with text overlay.
|
||||
|
||||
A composition can pass HTML linting and still be unreadable. Both rules below must clear before you ship.
|
||||
|
||||
## Rule 1 — Selection first, mask only if selection fails
|
||||
|
||||
The default editorial-magazine answer is **no mask**. Real magazine covers (Kinfolk, Cereal, Apartamento, Monocle) almost never lay a uniform black gradient over a hero photo. They rely on **photo selection + composition** so text lands in a quiet zone naturally. If you cannot find such a photo, the answer is usually to swap the photo, not to caulk over a bad one with a 70%-black scrim.
|
||||
|
||||
Apply the steps in order. Stop at the first one that passes.
|
||||
|
||||
### Step 1 — Photo selection (the main lever)
|
||||
|
||||
Before picking a mask, verify the photo qualifies for full-bleed treatment. Both tests must pass:
|
||||
|
||||
**Quiet-zone test**: at least one band of ≥30% canvas (full width × ≥30% height, or full height × ≥30% width) is **low-detail / low-contrast / uniform**. Examples: out-of-focus background, deep shade, fog, calm water, plain sky, blurred grass. This band is where the title will land.
|
||||
|
||||
**Light test**: the photo carries **atmospheric / restrained light** — overcast, dawn fog, golden hour, forest understory, film softness, dusk silhouette. Reject high-saturation noon shots, on-camera flash, "tourist trap" selfies, generic stock cheerfulness.
|
||||
|
||||
If either test fails, **the photo is wrong for M16**. Fall back to M01 (split-layout cover with photo in a frame), or shoot/source again. Do not "fix it with a mask."
|
||||
|
||||
### Step 2 — Compose without a mask first
|
||||
|
||||
Place the title inside the qualified quiet zone. Run the thumbnail contrast check (Step 4). If it reads clean, ship as-is. Most well-selected editorial photos pass at this step.
|
||||
|
||||
A no-mask cover signals craft. A heavily masked cover signals "we couldn't find the right photo." Try Step 2 every time before reaching for Step 3.
|
||||
|
||||
### Step 3 — Localized, image-toned tint (fallback only)
|
||||
|
||||
Only when Step 2 fails the contrast check. Three rules:
|
||||
|
||||
1. **Localized, not full-canvas.** Tint only the title region. Use `radial-gradient` centered on the title block, or a one-sided linear gradient that fades to fully transparent past the title. Never `inset: 0` over the whole image with a uniform alpha curve.
|
||||
2. **Image-toned, not black.** Sample a dark tone already in the photo and use it as the tint color. Forest → deep moss `#1a2818`. Dusk → tea-rose dusk `#4a2638`. Snow → cool grey-blue `#2a3438`. Pure black `#000` reads as "annotation layer," not "atmospheric depth."
|
||||
3. **Soft, not opaque.** Peak alpha 0.15-0.30 in the title region, falling to 0 outside. If you need >0.40 to read, the photo failed Step 1 — go back.
|
||||
|
||||
```css
|
||||
/* Example: title in lower-left, image-toned soft tint */
|
||||
.hero-bleed::after {
|
||||
content: "";
|
||||
position: absolute; inset: 0;
|
||||
background: radial-gradient(
|
||||
60% 45% at 25% 80%, /* centered on title block */
|
||||
rgba(26, 40, 24, 0.28) 0%, /* forest moss tone, soft peak */
|
||||
rgba(26, 40, 24, 0.0) 100%);
|
||||
pointer-events: none;
|
||||
}
|
||||
```
|
||||
|
||||
### Step 4 — Thumbnail contrast check (always)
|
||||
|
||||
Whether or not you applied a tint:
|
||||
|
||||
- Render the PNG, downscale to 360 px wide in Preview / a browser tab, and look.
|
||||
- Title strokes must be legible without zoom.
|
||||
- If title looks like it's "fighting" the photo → swap photo or shift title to a different quiet zone, **not** strengthen the mask.
|
||||
- If photo looks dead grey under uniform dark → mask is way too heavy or covers wrong area; restart from Step 1.
|
||||
|
||||
### Banned
|
||||
|
||||
- **Uniform full-canvas vertical falloff** (the `rgba(0,0,0,.55) → .10 → .10 → .80` pattern from earlier versions of this skill). That is game-key-art treatment, not editorial.
|
||||
- **Pure black mask color**. Always image-toned (forest, dusk, snow, ink-blue, sepia).
|
||||
- Flat black/white rectangle behind text — reads like screenshot annotation.
|
||||
- `mix-blend-mode: difference` for readability — wrecks skin tones, breaks on print.
|
||||
- `img { opacity: .6 }` — kills the photo's depth, the whole point of full-bleed.
|
||||
- Reaching for Step 3 before honestly attempting Step 1 with a better photo.
|
||||
|
||||
---
|
||||
|
||||
## Rule 2 — Place text away from subject / face zones
|
||||
|
||||
Posters in 旅行 / 游戏 / 影视 / 穿搭 / 美食 frequently use a real photo as the hero. The photo has a subject — a face, a hand, a product, a peak. Text that overlaps the subject reads as graffiti, not editorial.
|
||||
|
||||
### Subject zone discovery — multimodal first
|
||||
|
||||
Before designing the title position on a hero photo, look at the image. Open it with the Read tool and observe in plain language:
|
||||
|
||||
1. Where is the **primary subject's face / focal feature**? (e.g. "the climber's face is in the upper-right third around 70% x 30% y")
|
||||
2. Where is the **edge of the subject's silhouette**? (e.g. "left edge ends at ~40% x; below that is sky")
|
||||
3. Where is the **largest open / low-detail area**? (e.g. "the lower-left quadrant is uniform fog")
|
||||
|
||||
Record the answers as a comment in the HTML next to the hero block:
|
||||
|
||||
```html
|
||||
<!-- subject map (Wukong cover hero):
|
||||
face/skull centerpiece: 50% x 45% y, occupies ~30% of frame
|
||||
left-side staff & weapons: 15-40% x, full height
|
||||
safe text zone: top band (0-15% y) and bottom band (65-100% y)
|
||||
-->
|
||||
```
|
||||
|
||||
This is the input the layout uses. It's deterministic and reviewable.
|
||||
|
||||
### Safe-zone placement rules
|
||||
|
||||
Given the subject map, place text in this order of preference:
|
||||
|
||||
1. **Above + below** the subject (kicker top, title bottom). 90% of full-bleed covers should do this.
|
||||
2. **One side** — if subject occupies one vertical column (e.g. portrait shot on right side), text fills the opposite column. Combine with side tint from Rule 1 Step 3 only if needed.
|
||||
3. **Diagonal corner** — only when the subject is in one corner and the opposite corner is genuinely empty. Rare.
|
||||
|
||||
Never place display titles **across** the face. A 90 px Chinese title cutting through a person's eyes is destructive even with a mask.
|
||||
|
||||
### Composition discipline (editorial look)
|
||||
|
||||
These four rules are what separates an editorial poster from a generic image-with-text:
|
||||
|
||||
- **Asymmetric placement.** Titles offset to one side / one corner read more confident than dead-centered ones. The Kinfolk / Cereal habit.
|
||||
- **Generous negative space.** Title should occupy ≤40% of the canvas. The photo and the empty quiet zone are doing the design work.
|
||||
- **Title in one quiet zone, only one.** Splitting the title into two zones (top + bottom) is fine; splitting it into three or wrapping around the subject is busy.
|
||||
- **Title never overlaps the subject silhouette.** If the only safe placement requires crossing the subject's edge, switch modes (move to a frame, diptych, or off-image title) — do not enlarge the mask.
|
||||
|
||||
### Crop guards
|
||||
|
||||
The face-avoidance rule should also drive `object-position`. If the subject is in the upper third, do not use `object-position: center top` (that pushes the face into the title band).
|
||||
|
||||
| Subject location in raw image | Recommended `object-position` for 3:4 crop |
|
||||
| ----------------------------- | ------------------------------------------ |
|
||||
| Face / focus in upper third | `center 25%` (lifts face higher, gives bottom room for title) |
|
||||
| Face / focus in middle third | `center center` (default; text goes top + bottom) |
|
||||
| Face / focus in lower third | `center 70%` (drops face down, top room for title) |
|
||||
| Wide landscape, no single subject | `center 35%` (slight upward bias to retain horizon line) |
|
||||
| Vertical portrait, full body | `center top` |
|
||||
|
||||
### When the photo cannot accommodate text
|
||||
|
||||
If running the subject map shows there is no safe zone wider than ~30% of the poster (subject fills the frame, e.g. tight portrait), do not retrofit. Options:
|
||||
|
||||
- Move the photo into a **smaller frame** (use `.frame-img.r-3x4` or `.r-1x1` inside the layout — give it a defined slot, surround it with whitespace and text).
|
||||
- **Two-image diptych** with one tight subject + one negative-space image, run titles over the negative-space one.
|
||||
- **Move text off the image entirely** — title above, photo below. The Yading cover already does this.
|
||||
|
||||
A photo that demands the full canvas is not a sign that text should fight it. It is a sign that text should sit elsewhere.
|
||||
|
||||
---
|
||||
|
||||
## Checklist before delivery
|
||||
|
||||
Run this for every poster that has text touching an image:
|
||||
|
||||
- [ ] Photo passes quiet-zone test (≥30% canvas low-detail band) AND light test (atmospheric, not high-saturation noon).
|
||||
- [ ] Tried no-mask composition first. Tint only added if Step 4 contrast check failed.
|
||||
- [ ] If tinted: localized (`radial-gradient` or one-sided), image-toned color (not pure black), peak alpha ≤ 0.30.
|
||||
- [ ] No full-canvas vertical falloff present (`linear-gradient(180deg, rgba(0,0,0,.X) → ...)` over `inset:0`).
|
||||
- [ ] Subject map documented as a comment near the hero block.
|
||||
- [ ] No display title (≥ 72 px) overlaps a face / hand / key product feature / subject silhouette.
|
||||
- [ ] Title occupies ≤40% of canvas. Asymmetric placement preferred over centered.
|
||||
- [ ] `object-position` chosen from the table above, not left at default `center top` for face shots.
|
||||
- [ ] Thumbnail test passed (downscale to 360 px wide; title still legible without strain).
|
||||
- [ ] No flat black/white text plate behind the title.
|
||||
|
||||
If any item fails, fix it before exporting the final PNG. Do not deliver and "we can adjust later" — the QA window for social cards is the moment of upload.
|
||||
Reference in new issue
Block a user