Commit graph

19 commits

Author SHA1 Message Date
screentinker 85c1a9be7c
feat(seo): strengthen integrations internal linking + drop public status link (#178)
- Add "Integrations" to the top nav on the landing page and all 17 SEO pages
  (targeted at the nav Compare→Sign-In boundary; breadcrumbs untouched).
- Contextually cross-link the 6 integration spokes from the what-is and
  open-source guides (YouTube/RSS/Weather/Google Slides/Canva/Power BI) — each
  spoke now has 5 inbound links instead of hub-only.
- Remove the public "Status" footer link to /api/status from landing.html
  (endpoint stays for the admin panel + healthcheck; just not advertised).

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 12:11:30 -05:00
screentinker 2dc1d1279a
feat(seo): IndexNow + landing-page optimization (schema, FAQ, CWV, content) (#177)
* feat(seo): enable IndexNow (key file + submission script)

Instant re-crawl pings to Bing/Yandex/Seznam/Naver on content changes (Google ignores IndexNow
but uses the same sitemap). Hosts the ownership key at frontend/<key>.txt (served at
https://screentinker.com/<key>.txt) and adds scripts/indexnow-submit.sh which POSTs the sitemap
URLs to api.indexnow.org (DRY_RUN=1 to preview). Run after a content deploy / from CI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(seo): landing-page optimization pass (schema fixes, FAQ, content depth, CWV)

From a 3-way SEO audit (technical / structured-data / content). Highest-value fixes:

Structured data (penalty risk + rich results):
- REMOVE the fabricated aggregateRating (4.8/50) from SoftwareApplication — no visible reviews
  on the page = a "spammy structured markup" risk. Replace loose Offers with a proper AggregateOffer
  + publisher + image/screenshot.
- Add a VISIBLE FAQ section (10 Q&As) so the FAQPage schema finally has on-page content (it shipped
  4 Q&As with no visible counterpart — a mismatch); expand the FAQPage JSON-LD to mirror all 10.
- Add a WebSite entity block; add YouTube to Organization sameAs + a description.
- Fix the guides/compare BreadcrumbList position-2 target (dead /#features -> /).

Content / keywords / IA:
- Hero + Features subtitle rewritten to surface "digital signage software" / "digital signage CMS"
  / "self-host" / "free" above the fold.
- New "How It Works" (3-step) and "Use Cases / Industries" (8 verticals) sections for snippet +
  long-tail intent. Platform tiles (Android TV / Fire TV / Raspberry Pi) now link to their guides.
- FAQ answers add keyword-rich internal links to the guides + compare pages.

Technical / Core Web Vitals:
- Lazy-load the YouTube iframe (loading=lazy + youtube-nocookie + explicit width/height) — the top
  LCP/TBT win on mobile.
- Title 84->~60 chars (keyword-front), meta description ~178->~156 + CTA.
- <div> footer -> <footer> landmark; favicon sizes (192+512).
- sitemap.xml: add <lastmod> to all 10 URLs.

All JSON-LD validated (4 blocks parse; single H1; no fabricated data).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(seo): open-source/what-is pillars, Xibo+Anthias compares, integrations hub [#177]

Competitor-SEO gap-fill from the Yodeck/ScreenCloud/OptiSigns/Xibo teardowns.
11 new static pages + sitemap/landing/README wiring. All match the existing
seo-page.css template; BreadcrumbList on every page, FAQPage (visible-backed)
on guides + integration spokes. No fabricated ratings.

Pillar guides:
- guides/open-source-digital-signage.html  (head term "open source digital signage")
- guides/what-is-digital-signage.html       (TOFU definitional pillar + FAQ)

Comparisons (the open-source SERP Xibo/Anthias own):
- compare/xibo-alternative.html    (wedge: every ScreenTinker player free vs Xibo's paid Android/Tizen/webOS licences; no free plan)
- compare/anthias-alternative.html (wedge: fleet + video walls + multi-platform vs one-Pi-one-screen)

Integrations (OptiSigns' top tactic — one page per app):
- integrations/index.html hub
- google-slides / canva / power-bi  (honestly framed as the universal Webpage widget, with the X-Frame-Options / publish-vs-edit-URL caveat + Power BI public-data warning)
- youtube / rss / weather           (native widgets)

Wiring:
- sitemap.xml +11 URLs (lastmod 2026-07-13)
- landing.html Resources grid: 6 new cards (open-source, what-is, vs Xibo, vs Anthias, integrations hub)
- README.md: keyword-rich open-source/self-hosted intro + platform list + guide links (GitHub-SERP asset)
- docs/seo-directory-listings.md: off-repo G2/Capterra/AlternativeTo/awesome-selfhosted/fingoweb submission checklist + reusable kit

Validated: all JSON-LD parses, canonicals match paths, 0 broken internal links, sitemap well-formed (21 URLs).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 11:22:37 -05:00
ScreenTinker c237a6fb27 fix(landing): correct comparison-table claims, mobile image, media-query bug
- Comparison table (landing + the 3 compare pages): correct cells against each
  vendor's current pricing/docs (verified June 2026). Delete the inaccurate
  Platforms, Content Designer, and Hardware Lock-in rows; relabel "Remote
  Control" to "Live screen view + remote key presses" with an Android/permission
  caveat; fix Video Wall, Kiosk, Free tier, White Label and remote cells for
  Yodeck, ScreenCloud and OptiSigns. Add an "as of June 2026 / report errors"
  footnote with a GitHub issues link.
- Compare pages: drop the false "supports more platforms than X" claims; correct
  Yodeck (Windows/ChromeOS, web player, kiosk), OptiSigns (free tier, video wall,
  white label, remote); add the same footnote + caveat.
- Mobile fix: .screenshot img now has max-width:100% / height:auto / display:block
  so the dashboard preview no longer distorts on phones (no desktop effect).
- CSS bug: restore the dropped @media (max-width:768px) wrapper (braces were
  102 { vs 103 }) so the mobile overrides stop leaking to desktop.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 08:38:38 -05:00
ScreenTinker 8439f2bf18 fix(landing): replace broken Custom pricing card with enterprise contact form
The "Custom" tier on the public pricing page was misrendering as a
better-than-Free tier: headline "Custom", price "Free", "Unlimited
devices/storage", "Get Started" button. Root cause is in DB data,
not markup - the 'enterprise' plan row has price_monthly=0 and
max_devices/storage=-1, and the dynamic render in landing.html maps
those to "Free" + "Unlimited" with the wrong CTA.

Fix: filter the 'enterprise' plan out of the public landing render
(client-side, in landing.html only) and replace it with a hardcoded
Enterprise / Custom marketing card whose Contact Us button opens a
new lead-capture modal.

The DB row itself stays - it is actively used elsewhere:
- auth.js: first user in SELF_HOSTED=true mode is assigned to it
- settings.js: white-label feature is gated on enterprise plan
- 1 user (the dev account) is currently assigned to it
- /api/subscription/plans is also consumed by billing.js, settings.js,
  admin.js (logged-in surfaces); they keep getting the full plan list.
The filter is scoped to landing.html's render only.

The in-app billing page renders the same plan with the same cosmetic
bug; that's a logged-in admin surface, out of scope for this commit.

Other 4 cards (Free, Starter, Pro, Business) unchanged.

Frontend (landing.html):
- Filter 'enterprise' from public render
- Hardcoded Enterprise / Custom card. Uses .price class with "Let's
  talk" + empty .yearly spacer to match Free card's vertical baseline
  so the feature list aligns with the paid cards' baselines.
- Modal markup, CSS (mirrored from frontend/css/main.css conventions
  since landing.html doesn't import main.css), and inline JS for
  open/close/submit/escape/background-click.
- Honeypot field: hidden 'fax_number' input (off-screen + aria-hidden
  + tabindex=-1). Picked over the obvious 'website' name to catch
  mid-tier bots that explicitly skip the well-known honeypot names.

Backend (new server/routes/contact.js):
- POST /api/contact/enterprise, public (unauthenticated)
- Rate limited 5/min/IP+path via the existing rateLimit middleware
- Honeypot check: populated fax_number returns 200 silently, no email
- Server-side validation: required fields, email format, screens
  1-100000, multi_tenant in {single,multi}, hosting in {hosted,self,
  unsure}. Length caps prevent textarea-bomb abuse.
- Sends via existing services/email.js (Microsoft Graph) to
  dan@bytetinker.net from the support@screentinker.com Graph sender.
- Log lines: "[contact] enterprise inquiry from EMAIL (COMPANY)
  delivered" or "[contact] honeypot triggered from IP; dropping".

Wired in server.js alongside other public routes (before requireAuth).

Build-time tests passed locally:
- Module loads, server boots clean
- Validation: missing fields, bad email, bad multi_tenant, bad
  hosting, screens out of range - all return 400 with the right
  error message
- Honeypot: populated fax_number returns 200 success, no email sent,
  log line confirms drop
- Rate limit: kicks in at 6th request within a minute as expected
- Real end-to-end send: one test submission delivered to
  dan@bytetinker.net via Graph (subject "[ScreenTinker] Enterprise
  inquiry: ScreenTinker Build Verification", body formatted with all
  fields). GRAPH_DEV_RESTRICT_TO was temporarily widened to include
  the recipient for the test and restored to dw5304@gmail.com
  immediately after.
- Card render order verified against live API: Free (outline,
  Get Started) | Starter | Pro (featured, Most Popular badge) |
  Business | Enterprise / Custom (Contact Us -> modal).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 13:52:24 -05:00
ScreenTinker c4ac81c7a6 chore(discord): update Discord invite link
Old invite replaced with current permanent invite across README,
landing page, and anywhere else it appeared.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 12:26:20 -05:00
ScreenTinker a2c8ab4336 Match YouTube oEmbed embed format (revert nocookie, add referrerpolicy) to fix Error 153 2026-04-29 11:32:57 -05:00
ScreenTinker a273e5b2b6 Switch YouTube embed to youtube-nocookie.com to avoid Error 153 from tracking blockers 2026-04-29 11:28:49 -05:00
ScreenTinker a27738120a Add YouTube video embed to landing page 2026-04-29 11:25:29 -05:00
ScreenTinker 19b62fdc1b Fix landing-page comparison: ScreenTinker 15-device price is \$1,188 not \$989
The Pro plan is \$99/mo flat, so 15 devices for a year = \$1,188. The
landing page's compare table mistakenly showed \$989, which would imply
\$82.42/mo and contradicts every other place the price is quoted (the
comparison pages, the demo video, the pricing cards).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 23:23:58 -05:00
ScreenTinker 25ab1c485b SEO: add meta tags, sitemap, robots.txt, comparison pages, guides, internal linking
Landing page (frontend/landing.html):
- Title now includes "Self-Hosted" for that keyword
- Description appended "MIT licensed."
- Keywords aligned to spec (digital signage raspberry pi, digital
  signage android tv, video wall software, kiosk software, etc.)
- SoftwareApplication JSON-LD: added applicationSubCategory
  "DigitalSignage", license URL, refreshed description
- Image alt text + og:image:alt + twitter:image:alt now include
  "open-source digital signage"
- New Resources section above the CTA with 6 cards linking to all
  new guides and comparison pages
- Footer rewritten as a 5-column grid (Brand / Guides / Compare /
  Project / Legal) with the new internal links

New SEO pages, all dark-themed, mobile-responsive, ASCII-only:
- frontend/css/seo-page.css (shared nav/footer/article/table styles)
- frontend/compare/yodeck-alternative.html
- frontend/compare/screencloud-alternative.html
- frontend/compare/optisigns-alternative.html
- frontend/guides/raspberry-pi-digital-signage.html
- frontend/guides/digital-signage-android-tv.html
- frontend/guides/self-hosted-digital-signage.html

Each new page has unique title/description/canonical, OG and Twitter
card tags, BreadcrumbList JSON-LD, single h1, proper h2/h3 nesting,
visible breadcrumb, comparison table or step-by-step ordered list,
"Related guides" cross-link block, and a CTA.

Sitemap (frontend/sitemap.xml): added all 6 new URLs with appropriate
priority (0.8 for compare pages, 0.9 for guides). Existing landing
(1.0) and legal pages preserved.

Robots (frontend/robots.txt): allow /compare/ and /guides/, disallow
/player (was previously allowed by mistake).

Server (server/server.js): added explicit GET /sitemap.xml and
GET /robots.txt routes ahead of the static middleware so the
Content-Type is guaranteed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 20:54:32 -05:00
ScreenTinker 846d61a1b0 Add Discord link and refresh feature copy
- README + landing page footer now link to the community Discord
- Landing page feature grid gains Playlists, Directory Board,
  Offline Resilience, and Mobile Dashboard cards; Scheduling and
  Self-Hosted copy updated to mention group-level schedules and
  the DISABLE_REGISTRATION env var
- Structured data featureList expanded to match; Organization
  sameAs now includes Discord
- README feature list clarifies scheduling precedence, mobile
  responsiveness scope, and the auth/IDOR/XSS audit work

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-23 17:47:00 -05:00
ScreenTinker a981171c94 SEO: open-source positioning, GitHub links, OG image, semantic <main>
- Retarget primary keywords ("open-source", "CMS") in title, description,
  OG/Twitter tags and hero h1
- Swap OG/Twitter image from icon-512 to dashboard-preview.png with
  width/height/alt metadata
- Add GitHub link in nav (icon), hero (secondary btn), footer, and a
  new Open Source callout section
- Wrap content in <main> landmark; add width/height on screenshot for
  CLS; add third-party license page to sitemap; Organization schema
  sameAs now points to the GitHub repo

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 19:56:22 -05:00
ScreenTinker ea80d3aca5 Landing: replace iframe mock with dashboard screenshot
Swaps the live-app iframe for a static PNG of the Displays view.
Faster load, no auth flash, looks sharp.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 19:47:13 -05:00
ScreenTinker 3476f2b7e7 Landing: group Sign In next to Start Free Trial on the right
Removes the far-right floating position; Sign In sits in the nav
cluster alongside the CTA instead of pinned to the viewport edge.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 19:37:41 -05:00
ScreenTinker e0bfa76545 Landing: float Sign In to far top-right, separate from Start Free Trial
Sign In now lives outside the nav-links cluster with margin-left:auto,
pinning it to the top-right corner with visible separation from the
primary CTA.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 19:30:33 -05:00
ScreenTinker 87a935cb74 Landing: fix mobile nav overflow so Sign In stays visible
Pixel 8 Pro portrait (~412px) was clipping Sign In because logo + both
buttons overflowed. Hide logo text below 420px, shorten 'Start Free Trial'
to 'Try Free' on mobile, nowrap nav-links with tighter padding.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 19:27:19 -05:00
ScreenTinker 25f3870472 Landing: keep Sign In button visible on mobile nav
Previously hidden behind the primary CTA; now shows alongside it with
tighter padding on small screens.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 19:24:57 -05:00
ScreenTinker 8da0e60c20 Mobile: public-facing pages (landing + login)
Login view:
- Remove `margin-left: calc(-1 * var(--sidebar-width))` from the
  centering wrapper. It was a hack to compensate for the sidebar
  offset, but app.js already zeros the app margin on the login
  route. On mobile this was pushing the login card ~240px off
  the left edge of the viewport.
- Use min-height + padding so the card breathes on short screens.
- Drop inline font-size:11px on the support-token input so the
  global .input 16px mobile rule applies (iOS focus-zoom prevention).

app.js:
- Hide the mobile hamburger button on the login route; it has no
  function there since the sidebar is already hidden.

Landing page:
- Scope the old blanket `.nav-links { display: none }` to hide only
  the section anchors + secondary Sign In button, so the primary
  "Start Free Trial" CTA stays visible on mobile.
- Wrap the 5-column Compare table in a horizontal-scroll container
  and set min-width:560px so it scrolls instead of overflowing
  the page.
- Add min-height:44px to .btn on mobile, tighten section padding
  to 16px (from 24px) so content doesn't feel cramped against
  the viewport edge.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 18:52:53 -05:00
ScreenTinker 1594a9d4a4 Initial open source release
ScreenTinker - open source digital signage management software.
MIT License, all features included, no license gates.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 12:14:53 -05:00