What a page needs, and what it must never do. Every rule here came out of a real
problem found on this site, and every one of them is checked automatically by
node scripts/media-map.mjs and shown in the Page Audit.
Build new page types against this list and the pass does not have to happen twice.
How to work — read this first
Known false "fact" — never reinstate: "one bait per gallon" as a livewell density rule. Steve (a working captain): "one bait per gallon is a FANTASY." A round well on a strong pump carries far more than that; what keeps bait alive is FLOW (GPH) and reading the bait, not a per-gallon headcount. It had propagated to 10 spots across the three bait pages — all pivoted to flow. Do not write a per-gallon density number again.
The patterns to strike, all found on this site and all now fixed:
- Absolutes built from tendencies. "only", "never", "always", "sinks out of reach" — when the source said "usually", "best", "easiest".
- Guarantees. "guaranteed viewing", "near-guaranteed dolphins". Nothing in nature is guaranteed and the site must never imply it. The honest version already existed on the dolphin page and is the model: "Sightings aren't guaranteed; here, they're close."
- Self-praise. "The Complete Masterclass", "the complete field guide", "the ultimate no-boat species". The page either is thorough or it isn't; saying so is a tell that it isn't.
- Unverifiable superlatives. "the cheapest world-class fly fishing in Florida", "the best natural bait on the Gulf coast", "nothing beats them", "hands down".
- Ranking stated as fact. "the single best X" appeared ten times. A hedge — "arguably", "one of" — is honest and stays; an unhedged ranking is an opinion wearing a fact's clothes.
- Invented sociology. "the one that sells out first", "plenty of locals never tie on anything else". Nobody counted.
- Emotional promises. "changes how you fish forever", "the most addictive way to fish this coast", "make-or-break".
- Backwards causation — a human habit described as animal behaviour. The site said whitebait "is a first-light game" and that by mid-morning it "sees you and sinks". Steve: "you can catch bait all day — people catch it early because they fish early. That's not accurate." The dawn pattern was the anglers' schedule, not the bait's. Before writing that fish or bait "do" something at a time of day, ask whether that is the animal or the fisherman. The tell is a habit that lines up suspiciously well with when people leave the dock.
caye-caulker) tarpon in a Sarasota gallery, a Bali beach in a Gulf set. Filenames carry
place names — read them. If the frame shows a skyline, a mountain, or coral that is not Gulf coast,
it does not go on a Sarasota page.Running a section as its own session
Each section (species, charters, areas, techniques, resources, months) can be worked in a fresh session. The order that works:
npx astro build, thennode scripts/page-rhythm.mjs dist/<path>/index.htmlfor each page in the section — that gives you the layout defects as numbers before you touch anything.node scripts/media-map.mjs→ open Page Audit and Media Map for repeats, clusters, hero-fit and unused assets.- Fix defects only unless Steve has given notes for that section: false facts, wrong species, wrong geography, watermarks, duplicates, walls and fragments, stacked visuals.
- Rebuild, re-measure, confirm the numbers moved, then commit with the before/after.
What a page needs
| Slot | Rule |
|---|---|
| Hero | One, landscape, and about the page's subject. A species page leads with that fish; a month page leads with what is biting that month. |
| Breaker | At least one full-bleed band with an eyebrow + headline that matches the picture and the page. Video beats a still where a clip exists. |
| Body | Something visual roughly every 400–600 words. Under ~2 assets per 1,000 words a page reads as a wall of text. |
| Video | Aim for at least one moving image per page. 18 pages currently have none. |
| Mix | A fish, an underwater scene, a weather or water pad, and a person. Not all four every time — but not four of the same thing. |
| Gallery | Optional, and usually wrong. See below. |
dupe-of-used. When you find a pair: pick the better file, repoint
every reference to it, git rm the loser's variants, and note the swap. Do not leave
an orphan behind "just in case".Phone first — the hero has to work at both sizes
- The fish must be in frame on the phone, above the buttons. The angler too if it can be had, but the fish is not optional.
- Tune it with
heroFocusMobile, which takes its own X and Y — e.g.heroFocusMobile: "28% center".heroFocushandles desktop. Both now drive hero video as well as stills. - Ultra-wide panoramas make poor heroes. A 3:1 pano shows roughly a quarter of its width on a phone. Use one as a body element or a breaker instead.
- If no crop works, the picture is wrong for that slot. Flag it and use something else — a different frame, a second image for mobile, or the shot the homepage uses. Do not ship a hero that only reads on one device.
hero 📱/🖥 column, flag hero-fit below 40%. Where a
better crop exists the audit prints the exact heroFocusMobile to paste; where no crop
works it says so, and that page needs a different picture.Page rhythm — the sections must be roughly equal
page-rhythm.mjs only treats section.sf-cine, figure,
picture, video and img as visuals. A map renders as a div,
and it emits a <noscript> SEO fallback listing every spot — 1,025 words on the artificial-reef
page, 1,952 on the fishing-spots page. The script counts all of it as copy that no reader ever sees. Both
pages measured a 1,000w+ “wall” that does not exist. Strip the map div before measuring, or subtract the
noscript by hand, and say which you did.190w → picture → 33w → picture → 52w → picture
→ 1137w → picture → 95w → picture picture → 389w. Two-sentence
fragments wedged between stacked pictures at the top, then the entire body dumped in one wall with nothing
in it. Cause: every breaker sat in the template before <Content />, so the article
itself got none.The same page after redistributing the breakers into the content at section boundaries:
275w → 302w → 270w → 159w → 236w → 265w → 389w — min 159, max 389.
That is the target shape.
| Check | Rule |
|---|---|
| Copy block | 150–350 words between visuals; ~250 is the sweet spot |
| Wall | Never a block over 500 words with no visual in it |
| Fragment | Never a block under ~120 words wedged between two full-bleed visuals |
| Stacked | Never two visuals adjacent with no copy between |
| Evenness | Largest block ÷ smallest block ≤ ~3 |
.astro and the article renders
through <Content />. If every breaker is placed before that call, the article gets zero
visuals no matter how many the page has. Convert the .md to .mdx and place breakers
at ## boundaries so the body splits into even chunks.node scripts/page-rhythm.mjs dist/<path>/index.html prints the actual
sequence of visuals and the word count of the copy between them, plus min/median/max. Run it after a build;
if the max block is over 500 or the min is under 120, the page is not done.Hard rules
repeat.barracuda… is a grouper. One called …spanish-mackerel… is spotted seatrout. One called colorful-sea-life… is a school of tarpon. Open the file before you write the alt text.Distribution — spread, not ban
cluster; the map shows sections per asset.- Months are their own section. Twelve pages, twelve distinct sets. Nothing repeats across them.
- Nothing is banned outright — including photos of the owner. Use them sparingly and spread out: at most one or two per section.
- Slot matters as much as frequency. A frame that is wrong as a hero can be right in a gallery. The hand-fed tarpon is a fine nature-tour gallery picture and never a hero.
- Prefer unused. ~300 assets have never been placed. Check the map's never used filter before reaching for something already on five pages.
Type & overlays — how a breaker must render
<p>, and every template that renders markdown carries
its own paragraph rule — .of-md p, .in-md p, .svc-md p,
.sf-prose p — at (0,2,1) specificity, while the component's scoped rule is only (0,2,0).
So a breaker inside a rendered <Content /> silently lost its type and came out
Lato 300 at 16.8px, while the same component outside the markdown body rendered correctly at 54px.
components.css now re-asserts the type at (0,3,0). If you ever see a small thin headline,
that guard has been broken.rgba(5,13,28,…), rgba(7,19,40,…)), ocean blue
(rgba(17,74,120,…)) and teal (rgba(127,214,230,…)). The worst was in
underwater.css — a teal sun-glow radial plus a 50% ocean-blue gradient applied to
every .sf-hero--underwater background, which is most heroes on the site. They were sitting
on hero backgrounds and breaker bands. They desaturate the photograph — the colour gets sucked out and
the image goes hard to see. Every photo overlay is now neutral black and bottom-weighted, purely
so text has a base to sit on. The breaker's flat tint layer and radial wash are gone entirely;
legibility comes from the headline's own text-shadow. If you ever need more contrast, reach for the
shadow, never a coloured wash. Body figures and inline video carry no overlay at all.tint prop. Stacked too heavy the photo turns into a
blue-black smear. Current settings: radial peaks at 0.34, flat tint 0.25–0.35 typical. The headline
carries its own text-shadow, so reach for the shadow before more tint. Body figures get no
overlay at all — if an inline photo looks blue and murky, that is the footage, not the site.margin-left: calc(50% - 50vw) centres on the wrong point and the band shifts left,
clipping its own headline off-screen. prose.css re-escapes from the container geometry and
adds margin-block: 2.75rem so the band does not butt against the paragraph above.Craft
- Breaker copy: short, factual, no hype. Eyebrow is a label (2–4 words); headline is one plain sentence that carries real fishing content, not a description of the frame.
- Boats show two anglers, not one. A topwater wake zig-zags — it does not run straight.
- No burned-in watermarks. The old BIG SHOTS / BigShotsArt stamp is on a number of library files; crop it before use.
node scripts/media-audit.mjsfinds them. - AI art is a last resort and never a hero on a page where a real photograph exists. 37 pages still carry some.
- Every video needs a poster (
.webpbeside the.mp4) or it flashes black. - Vertical clips are inline figures, never breakers. A 720×960 clip in a wide band survives as a strip. Run it at 380–460px with a caption, and it can still be the biggest thing on the screen.
- Check the fish name before you write it. Every regulation, slot, bag limit, season and record on this site is verified against
myfwc.comandcatchafloridamemory.com, dated. If you cannot source a number, cut it — do not soften it. - It is a snook stamp, not a permit, in every piece of copy on this site. FWC's own site calls it the snook permit; that is noted once, in the regulations table, and nowhere else.
- heroImage doubles as the social card, so it must be a jpeg — some crawlers will not render webp.
Technical notes
- Variants are
-400 / -800 / -1600 / -full. Body images use-800, heroes-1600or-full. stock-new/has no.avif.ResponsivePictureandPhotoGalleryalways emit an avif source, so either generate the avif variants or use a hand-written<picture>.- Bespoke pages (inshore, offshore, nearshore, tarpon-charters, night-fishing, dolphin-tours, shark-tooth-dives, tarpon, snook) render from their own
.astroand ignore the content file's imagery fields. Editing the.mddoes nothing. - A
.mdfile cannot host a<CinematicBreaker>. Rename to.mdx, import after the frontmatter, and leave a blank line before the prose or the build fails. heroFocusnow drives the hero video crop as well as the poster.
Known bad assets — do not re-litigate these
Every one of these was opened, zoomed and confirmed by eye. Four separate sessions have now independently rediscovered items on this list and spent real time on it. If you are about to place one of these, stop. If you confirm a new bad asset, add it here — that is the whole point of the list.
Wrong ocean / wrong geography
| File | What it actually is |
|---|---|
stock-new/sunset-sky-clouds-over-sea-in-the-evening-with-ora | Mountain ridge across the horizon. Was live on sunset-cruises directly under copy reading "the horizon with no condos in it." |
stock-new/horizon-sea-sky-background-sunset-sky-clouds-over | Land on a horizon that should be open Gulf. Retired from the breaker pool. |
stock-new/vibrant-underwater-seagrass-posidonia-habitat-in | Mediterranean Posidonia — long strap blades, brown basal sheaths. Not Florida turtle grass. Was captioned "the staple food of manatees on the Sarasota flats." |
stock-new/neptune-grass-field-cabo-cope-puntas-del-calnegre | Same Posidonia, Spain. |
stock-new-vid/golden-fish-swimming-in-seagrass-in-ocean | Posidonia with sea bream. Mediterranean. |
stock-new/dramatic-colorful-sunset-sky-over-mediterranean-se | Fine, despite the name. Sky and empty water only — nothing in the frame contradicts the Gulf. Left in use deliberately. |
scenes-pano/img-2078 (downtown skyline) | Real, but it is downtown Sarasota. Do not put it on a page whose copy says undeveloped. |
Belize / Caribbean tarpon: caye-caulker, cayman, xcalak | Palms and reef. Still live as heroes on the tarpon pages — flagged for Steve, not changed. |
Filename lies about the species
| File | What is actually in it |
|---|---|
stock-new-vid/fast-caribbean-reef-shark | Tarpon. Confirmed three times independently. Was the breaker on the shark-tooth diving page. |
barracuda-fish-and-other-fish | Grouper. |
boy-at-night-…-spanish-mackerel | Two seatrout. |
large-green-fish-caught-with-plastic-lure | Mahi. |
striking-close-up-of-striped-shrimp | Mantis shrimp. |
shark-chasing-a-large-bait-ball | No shark in frame. |
backgrounds/big-sky | Mangrove creek, and portrait. |
stock-new/colorful-sea-life-underwater-shallow-underwater-s | Tarpon in open blue water. No “colorful sea life” in it at all. |
stock-new/large-grouper-and-school-of-fish-underwater | Aquarium. Tank seam visible top-left, flat gradient backdrop, and the fish is a goliath. Was the featureImage on the artificial-reefs page until Steve called it. |
species-mixed/img-2161 | Real gag grouper, but the angler is at a dock with a marina behind him — inshore, not reef fishing. Steve pulled it off the artificial-reefs page for exactly that. |
Checked and CORRECT — do not re-flag: kids/kid-pompano-dock was once reported as jacks or blue runners.
Zoomed on the deck fish, they are Florida pompano: deep compressed near-circular body, blunt rounded snout, small mouth,
yellow wash on the throat and anal fin, and — decisively — no black opercular spot and no bony scutes on the
caudal peduncle. A jack crevalle has all three. It is correct as the featureImage on the pompano species page.
The lesson generalises: a claim that a filename lies is itself a claim, and it needs the same zoom-in evidence as any other.
Burned-in text and watermarks
| File | Problem |
|---|---|
video/snook-redfish-inshore-action | Vertical 960×1706 with a "SARASOTA FLORIDA / DECEMBER INSHORE FISHING" title card. Never a breaker. |
species-mixed/live-bait | Burned-in "LIVE BAIT FISHING" title card. Removed from the live-bait page. |
scenes/2014-7-10-osprey-at-squadron-sunrise | SCHADT.COM watermark. Uncroppable — the stamp is 131px tall and the bird's head starts at 148px. Retired from the pool. |
scenes-pano/downtown | 6:1 sliver, too thin to crop the stamp off. Retired. |
scenes-pano/myakka-lake | BIG SHOTS / Steve Schadt Photography / BigShotsArt.com stamp, top-left, on plain storm sky. Croppable (~22% off the left, or ~14% off the top). Pulled off sarasota-fishing-spots 2026-07-23; still live on resources/rivers-and-backcountry and in breaker-pool.json. |
scenes/2012-7-17-myakka-weeds-lake-hdr | Same stamp, bottom-left this time — which is why a top-corner-only check missed it. Croppable. Check both left corners on every scenes* file. |
snook-flagship/snook-dock-light-night | Heavy grain, blown flash, and a burned-in torn/Instagram border. |
abstract/storm-sky | Banned by Steve — "don't ever use that cloud picture anywhere." Green-cast HDR storm cloud pano with a burned-in torn/Instagram border. Was the grouper-season hero; removed. Its breaker-pool slot was swapped in place (not deleted — deleting an element reshuffles every page) for stock-new/radiant-sky-at-sunset-with-golden-orange-colors. |
spots/mote-fleet-night | FIXED. Had a BIG SHOTS / Steve Schadt Photography stamp, but on plain sky at the left — cropped 345px off the left edge, all variants regenerated. Now clean and usable. |
spots/bait-shop-new-pass | FIXED. Cropped. Was live as a page image on two pages. |
Unusable geometry
scenes-pano/srq-night-shannon-moore-600-x-3000-for-website— the "full" file is 1000×83, a 12:1 sliver. No slot on the site can use it. Retire.scenes-pano/srq-harvest-moon-hdr-28x72— downtown Sarasota skyline under a full moon. Two problems: it is the skyline, and the dock-light page's own copy says the best nights are dark new-moon nights, so it argues with the text it would sit next to.
Is it Steve?
Confirmed him: video/redfish-video-loop-nice-simple-works-anywhere, video/redfish-loop-cool-simple (also has a house roofline).
Confirmed not him: video/tarpon-night-jump-sm and its matching still tarpon/tarpon-night-silver — full head of light/reddish hair, clean-shaven, same man and same fish in both.
Reads as him, so treated as him: video/tarpon-night-boatside (bald, camo, smiling with a night tarpon) and
species-gallery-redfish/12872012-04-06 (bald under a cap, 2012 filename). Both are otherwise good frames; if Steve confirms either is a customer they go straight back in.
Duplicate pairs — pick one, repoint, git rm the loser
video/dive-boat≡video/stock-scuba-divers-jump-off-boat-for-deep-ocean-dive-in— same shot, confirmed side by side.stock-new/tarpon-and-silversides-cayman-palms-cayman-islan≡stock-new/tarpon-hunting-silversides-cayman-palms-cayman-i— different md5, identical photograph. One is the/species/tarpon/breaker, the other is the tarpon-charters hero and the catch-and-release hero. Retire one and repoint.video/red-grouper-loop-sm≡video/red-grouper-simple-loop-for-rg-pgs— same shot, confirmed side by side. Neither is underwater reef footage: it is a man in a cap and sunglasses holding two grouper heads at a ramp, and he may be Steve.video/manatees-pair≈video/stock-pair-of-manatees-swimming-in-clear-shallow-waters— same top-down aerial of the same two animals, differing only in zoom. Counted as two assets across three pages.kids/kids-3≡people-family/kids-good— same photograph, identical torn border.spots/bait-shop-placida≈scenes/bait-shop-nice-boat-placida-boca-grande— probable pair, unconfirmed.
Bad posters (the clip is fine, the .webp is not)
video/aerial-grass-flats-works-for-anything-inshore-breaker-header-hero— poster is frame 0, a murky green smear. Pull a new one from ~3s where the sandbar and potholes read.backgrounds/abstract-sunset-ripples— ships a-1600.jpgonly, no.webp.video/looping-bait-livewell-breaker-for-techniques-and-chrters-etc— washed-out, grainy, low-contrast livewell clip; the band crop lands on a murky wall of indistinct fish and reads as garbage (Steve: "bullshit"). Usevideo/looping-baitwell-simple-breaker-goodinstead — bright orange dip net + aerator ball over a bait-packed well, reads instantly. The murky one is still live oncharters/inshore.
The photo plan — what the library can and cannot do
| Situation | What to do |
|---|---|
| Page needs a fish photo | Use a real one. Check it is that species by eye first — filenames lie constantly. |
| Only portrait frames exist for that fish | It cannot be a hero or a breaker. Use the featureImage slot (renders large and uncropped) or a sized inline figure. Do not force it into a wide band. |
| No real photo exists at all | AI is the last resort, and never a hero where a real photograph exists. Six pages still run AI heroes purely because every real frame of that fish is portrait — sheepshead, flounder, black drum, gag grouper, king mackerel, mangrove snapper. Real landscape photos of those six is the single highest-value shoot list. |
| Asset is unused and looks good | That is not a reason to place it. See the working agreement. |
| Asset is overused | Reduce by swapping it out of the weakest placement, not by adding a near-duplicate. |
| Two files look like the same photo | They probably are. Keep the better file, repoint every reference, git rm the loser. Three duplicate pairs were found this way. |
| Frame has a BIG SHOTS / BigShotsArt stamp | Crop it before use. Nine were found stamped, four of them live as page heroes. Slivers too thin to crop get retired instead. |
Clip has no .webp poster | Generate one, or it flashes black. Pick a frame where the subject actually reads, not frame 0. |
Holes in the library — verified by search, not assumed
These were each looked for properly and are genuinely not there. Do not spend another session hunting. Every one is a page whose central subject has no honest photograph behind it.
| Missing | Which page it breaks |
|---|---|
| A real kayak on the water. The only kayak assets are one AI hero, one AI underwater illustration, and a snapshot of two kids in a wooden canoe. | The whole kayak-fishing page. Steve believed there were more; there are not. |
| Any real photograph of a person wading. The only wading frame on the site is AI. | Wade fishing — its hero is currently a man on a boat deck, on a page whose premise is leaving the boat. |
| A stingray. Zero ray assets of any kind. | The stingray shuffle is the wade page's loudest safety instruction and has no image. |
| A dock light shot from outside the halo. Every halo asset is shot from directly above or below the light. | Dock lights — the page turns on "the shadow edge, not the bright centre", and nothing in the library shows the edge. |
| A red headlamp at work on deck. | Dock lights — repeated three times in the copy, pictured zero times. |
| A tailing redfish on a drained flat. Closest is an underwater redfish. | Bokeelia, sight fishing, and every flats page. |
| A bald eagle. We have ospreys, egrets, herons, spoonbills, cormorants, skimmers, pelicans. | Bokeelia — Steve named eagles specifically. |
| The US-41 twin spans from the water (Barron Collier / Gilchrist). All bridge footage is Sarasota or a bascule drawbridge. | Punta Gorda — the first landmark Steve named for that page. |
| A largemouth bass on a Florida river. The only real bass frames are Lake Okeechobee, portrait, one angler. | Punta Gorda — the salt-to-fresh gradient is the page's best story and has no picture. |
video/stock-aerial-view-of-men-fishing-from-boat is exactly that shot: one poling from the platform, one on the bow, mangrove shoreline, no buildings. Verified by eye. | Struck. Was wrong when written. |
The tools
| Command | What it answers |
|---|---|
node scripts/page-rhythm.mjs dist/<path>/index.html | The visual/copy rhythm of a built page — walls, fragments, stacked visuals, min/median/max. |
node scripts/media-map.mjs | Rebuilds the usage map: repeats, section clusters, duplicates, hero-fit, unused assets. |
| /media-audit.html | Page-by-page audit with the flags. |
| /media-map.html | Every asset, where it is used, notes and TODOs. |
node scripts/media-audit.mjs | Finds watermarks and sideways frames. |
Before you call a page done
- Run
node scripts/media-map.mjsand check the page in the audit: norepeat, notext-heavy, a hero and a breaker, at least one video. - Confirm every path exists on disk. The build does not fail on a missing image — it ships a broken one.
- Look at each picture you placed at full size. Not the thumbnail.
Derived from the July 2026 media pass. Keep it current: when a new rule is
learned, add it here and add the check to scripts/media-map.mjs so the audit
enforces it rather than relying on memory.