34 Commits

Author SHA1 Message Date
Kunthawat Greethong
99f7d8f0bb Remove Rano Tech brand from pipe-coupling page 2026-06-22 17:33:08 +07:00
Kunthawat Greethong
85a0a54eb4 Use @astrojs/sitemap instead of manual sitemap.xml.ts
- astro.config.mjs: site=https://dealplustech.co.th, add sitemap integration
- Remove hardcoded sitemap.xml.ts (now auto-generated)
2026-06-16 09:48:38 +07:00
Kunthawat Greethong
5c1d3c7ef9 Fix domain: dealplustech.com -> dealplustech.co.th
- BaseLayout.astro: siteUrl
- sitemap.xml.ts: SITE
- [slug].astro: articleUrl, ogImage, JSON-LD URLs, share links
- terms-and-conditions.astro: hardcoded URLs
2026-06-16 09:46:31 +07:00
Kunthawat Greethong
da14d0d05d Add Umami analytics tracking scripts 2026-06-16 09:44:50 +07:00
Kunthawat Greethong
5b521661ec feat: เพิ่ม consent script (consent.moreminimore.com) 2026-06-15 17:50:59 +07:00
Kunthawat Greethong
3469a09a9f feat: เพิ่ม tracking codes — GA4, Google Ads, Microsoft Clarity, Meta Pixel 2026-06-15 13:07:27 +07:00
Kunthawat Greethong
5d5e12d7e7 fix: รูปหลักบทความ plastic-grilles-guide path ถูกต้อง (blog/grilles -> grilles)
- featured_image: /images/grilles/air-grille-content.jpg
- สินค้าใหม่ SCG page 2: UBB-G, FRK-G, FRKF-G, Cylence Zandera Scenera, SUPERCOOL-G (6 รายการ)
- สินค้าใหม่ Microfiber page 2: FSF, Pipe Cover, GWPF (3 รายการ)
- รูปสินค้าใหม่ทั้งหมดจาก 3t-insulation.com
- Solar -> NO-CLAD (thermobreak-series-guide)
- Facebook icon + ชื่อ Dealplustech ที่ footer
2026-06-15 12:36:26 +07:00
Kunthawat Greethong
f5af9e46e1 fix(mobile-menu): make menu scrollable when content overflows
After adding the products dropdown (7 categories + 27 subcategory
links), the mobile menu was taller than typical phone screens
(~640px on iPhone SE). Without overflow handling, users on
short screens could not reach the bottom items (ผลงาน, ติดต่อเรา).

Added to mobile menu container:
- max-h-[calc(100vh-4rem)]: cap height at viewport minus header
- overflow-y-auto: enable internal scrollbar when content overflows
- overscroll-contain: prevent scroll chain to background page
  (iOS Safari specific)

Menu is now fully scrollable within its own container — page
content underneath stays still while user scrolls through menu
items.
2026-06-11 11:17:43 +07:00
Kunthawat Greethong
bb7007aa88 feat(mobile-menu): add collapsible products dropdown matching desktop
The mobile menu previously had only flat links (หน้าแรก, เกี่ยวกับเรา,
สินค้าทั้งหมด, ผลงาน, ติดต่อเรา) while the desktop menu had a mega
dropdown with 7 product categories (ระบบท่อ, ฉนวนหุ้มท่อ, เครื่องเชื่อมท่อ,
ระบบน้ำ, อุปกรณ์ปรับอากาศ, อุปกรณ์ดับเพลิง, ระบบรั้ว) and 27
subcategory links.

Updated mobile menu in BaseLayout.astro to mirror the desktop
structure: "สินค้าทั้งหมด" is now a collapsible button that
expands to show all 7 categories and their 27 subcategory links,
plus a "ดูสินค้าทั้งหมด →" CTA at the bottom.

Toggle behavior:
- Click button: opens submenu, rotates chevron 180°, sets
  aria-expanded="true"
- Click again: closes submenu, returns chevron
- aria-controls="mobile-products-submenu" for accessibility

Reverted Header.astro changes — that file is not actually used
by the site (BaseLayout.astro has its own inline header with the
real desktop mega menu and mobile menu).

Mobile menu now has parity with desktop navigation structure.
2026-06-11 11:09:15 +07:00
Kunthawat Greethong
900ffe0f06 feat(seo): add FAQPage JSON-LD schema to pipe-coupling
pipe-coupling.astro has a FAQ UI section (4 Q&A) but was missing
the FAQPage structured data. Added faq prop to BaseLayout so the
schema renders automatically via the existing JsonLd.astro
component.

Now all 3 pages with FAQ UI (realflex, เทอร์โมเบรค-thermobreak,
pipe-coupling) have matching FAQPage JSON-LD schema, which helps
Google rich results + GEO (Google AI Overview, Perplexity, etc.)
surface the FAQ content.

FAQPage schema renders as:
{
  "@type": "FAQPage",
  "mainEntity": [
    {"@type": "Question", "name": "...", "acceptedAnswer": {"@type": "Answer", "text": "..."}},
    ...
  ]
}
2026-06-10 19:31:57 +07:00
Kunthawat Greethong
bef271c1a2 fix(insulation): remove 7 gallery images + restore Product Details
Two fixes:

1 Deleted 7 product photos user requested:
- rockwool/gallery-8.jpg
- microfiber/gallery-9.jpg
- maxflex/gallery-2.jpg
- aeroflex/gallery-7.jpg
- aeroflex/gallery-15.jpg
- armflex/gallery-6.jpg
- armflex/gallery-7.jpg

Also updated gallery HTML in 5 pages to skip the deleted image
numbers. After deletion: rockwool=10, microfiber=9, maxflex=8,
aeroflex=13, armflex=5.

2) Restored Product Details section for armflex, aeroflex, and
maxflex. These 3 pages had their content stripped earlier in
the session (only kept Hero + Gallery + CTA). Pulled the
original Product Details from commit 17f4eb7 (which had the
full data: features, specs, certifications, applications,
water-vapor resistance values, etc.).

armflex: 10.3KB restored
aeroflex: 9.6KB restored
maxflex: 10.1KB restored
EOF
)
2026-06-10 17:19:25 +07:00
Kunthawat Greethong
2996812209 feat(insulation): add product photo gallery to 6 insulation pages
User provided 61 additional product photos across 6 insulation
brands. Added a responsive image gallery section (before CTA)
to each of these product pages:

- armflex.astro: 7 photos
- aeroflex.astro: 15 photos
- maxflex.astro: 9 photos
- เทอร์โมเบรค-thermobreak.astro: 9 photos
- rockwool.astro: 11 photos
- microfiber.astro: 10 photos

Files renamed to gallery-1.jpg ... gallery-N.jpg format in
public/images/{brand}/. Each image is clickable (opens full
size in new tab), uses lazy loading, and has hover zoom.

Gallery section layout:
- 2 columns on mobile, 3 on tablet, 4 on desktop
- Title: "แกลอรี่ภาพสินค้า"
- Subtitle: "ภาพสินค้าจริงจากงานติดตั้ง — คลิกเพื่อดูภาพขนาดเต็ม"
2026-06-10 14:45:50 +07:00
Kunthawat Greethong
d5d4574683 fix(hero): replace product pill with category from all-products
Each product page's hero pill now shows the category from
all-products.astro instead of the product-specific sub-pill.

Updated 25 pages:
- ระบบท่อ (6): ppr-thai-ppr, ppr-scg, hdpe, upvc, syler, xy-lent
- เครื่องเชื่อมท่อ (2): pipe-coupling, เม็กกรู๊ฟ-คับปลิ้ง
- ระบบน้ำ (3): วาล์ว-valve, water-pump, water-treatment
- อุปกรณ์ปรับอากาศ (3): grilles, durgo-avvs, หัวจ่าย-ball-jet
- อุปกรณ์ดับเพลิง (2): ตู้ดับเพลิง, realflex
- ฉนวนหุ้มท่อ (7): armflex, aeroflex, maxflex, thermobreak, rockwool, microfiber, scg
- ระบบรั้ว (1): รั้วเทวดา
- เดิม: ระบบรั้วไวน์แมน, เครื่องเชื่อม-hdpe, เครื่องเชื่อม-ppr
2026-06-10 14:34:42 +07:00
Kunthawat Greethong
d93191f675 fix(all-products): sync card images with each product's hero image
Found 3 mismatches between all-products.astro card images and
the actual hero image of each product page:
- ROCKWOOL: prod_0.png → cool-n-comfort-rl.jpg (matches hero)
- MICROFIBER: prod_0.jpg → microfiber-enf.jpg (matches hero)
- SCG: prod_0.jpg → scg-crb-g.jpg (matches hero)

Other 22 products were already in sync. All 27 products now
show their canonical hero image on the all-products page.
2026-06-10 14:30:46 +07:00
Kunthawat Greethong
1e4fa53fb2 fix(pipe-coupling): use MJG80A screenshot as hero image
User provided screenshot of RNT Smart Clamp MJG80A (with SUS304
material, NBR seal, 89.1mm pipe OD, 67.8-91.9mm range) as the
canonical hero image for this product.

Changes:
- public/images/pipe-coupling/mjg80a-hero.png: new hero image
  (copied from user's Desktop screenshot)
- pipe-coupling.astro: hero <img src> updated to mjg80a-hero.png
- all-products.astro: card image updated to mjg80a-hero.png

Other 5 model images (mjg/mjh/mjd/mjer/mjcx/rnt-e009) unchanged
as those are individual model photos from ranotech.com.
2026-06-10 14:21:47 +07:00
Kunthawat Greethong
8346a731a6 feat(pipe-coupling): rewrite as RNT Smart Clamp from Rano Tech
Replaced SMC content with RNT Smart Clamp (Rano Tech) per
supplier data from https://www.ranotech.com/rnt-smart-clamp/

Changes:
1 Content rewrite — pipe-coupling.astro now shows:
   - Hero: RNT Smart Clamp (แคลมป์รัดท่อ Rano Tech)
   - 5 product models with descriptions:
     * MJG — same-size coupling (ต่อท่อขนาดเท่ากัน)
     * MJH — repair clamp with hinge (ซ่อมรั่วแบบบานพับ)
     * MJD — hybrid repair/coupling (ไฮบริด)
     * MJER — elbow repair (ซ่อมส่วนโค้ง)
     * MJCX — custom series
   - FAQ section (4 questions)
   - Installation details

2) Images — Downloaded 12 real RNT product photos from
   ranotech.com/wp-content/uploads/ to
   public/images/pipe-coupling/. Old SMC images removed.

3) all-products.astro — Pipe Coupling card now uses smc-02.jpg
   (new RNT main image) instead of old BG-SMC02.png

4) Sitemap — pipe-coupling URL unchanged (no change needed)
EOF
)
2026-06-10 14:18:58 +07:00
Kunthawat Greethong
72ace6ff50 fix(insulation): remove spec table from product cards
Removed the spec table that was at the bottom of each product
card. Now each card just shows: image, name, tagline, and
description paragraphs.

Size: 35 products still showing, 0 spec tables.
- rockwool: 62KB → 53KB
- microfiber: 60KB → 55KB
- scg: 57KB → 53KB
2026-06-10 14:10:04 +07:00
Kunthawat Greethong
535aefc0f3 feat(insulation): add detailed product descriptions + spec tables
Crawled full content from each of 35 product pages on supplier
site (3t-insulation.com). For each product card on the 3 brand
pages, now show:
- Full Thai product name (e.g. "ROCKDUCT (ProRox BL)" instead of
  just "ROCKDUCT")
- Short tag-line describing use case
- Real product description (2 paragraphs from supplier, in Thai)
- Spec table (density, thickness, dimensions, fire rating, etc.)
  filtered to remove product option noise

Each product card is now a real catalog entry instead of a name
+ image. Source data is /tmp/product_details.json (35 entries).

Stats per page:
- rockwool: 11 products, 41 spec entries, 62KB
- microfiber: 12 products, 24 spec entries, 60KB
- scg: 12 products, 17 spec entries, 57KB
2026-06-10 13:34:55 +07:00
Kunthawat Greethong
15b4b40681 fix(insulation,menu): real product images + accurate count + menu reorder
1 REAL product images (downloaded from supplier product pages)
   Replaced generic logos/QR codes with actual product photos
   from WooCommerce product galleries on 3t-insulation.com:
   - rockwool/: cool-n-comfort-rl/sl, prorox-bl/ps960/sl/wm,
     rocksafe-plus, safe-n-silent-pro, thermalrock-s, rockduct, conlit-h110
   - microfiber/: microfiber-enf/fl/fld/fls/fr/frd/frk/glc/gts/hi-temp/pfl/pln
   - scg/: scg-crb-g, scg-cylence-zoftone/zoundblock, scg-dln-g, scg-fpo-g,
     scg-fso-g, scg-gtb-g, scg-hti-g, scg-htif-g, scg-htifd-g, scg-stay-cool, scg-ub-g

2) Home page category count for ฉนวนหุ้มท่อ: 4 → 7
   (Armaflex, Aeroflex, Maxflex, Thermobreak, ROCKWOOL, MICROFIBER, SCG)

3) Menu order swap: ฉนวนหุ้มท่อ ↔ เครื่องเชื่อมท่อ
   Moved ฉนวนหุ้มท่อ BEFORE เครื่องเชื่อมท่อ in BaseLayout categories
EOF
)
2026-06-10 13:26:43 +07:00
Kunthawat Greethong
4692e89736 fix(menu,images): correct header menu + use real product images
Two fixes:
1) Header.astro menu items for ROCKWOOL/MICROFIBER/SCG were
   placed AFTER the closing array bracket - not visible in dropdown.
   Moved INSIDE the 'ฉนวนและรั้ว' items array.
2) Product images were generic logos/QR codes. Replaced hero
   images with actual product photos (1200x800) from supplier
   WooCommerce galleries.

Also added footer links.
2026-06-10 11:55:55 +07:00
Kunthawat Greethong
739e081296 feat(products): add 3 new insulation brand pages — ROCKWOOL, MICROFIBER, SCG
New product pages for insulation product lines, using supplier (3t-insulation.com)
product data and images (downloaded, no hotlinks):

1 ROCKWOOL (/rockwool) — Stone wool insulation, pipe/duct/roof/fire
2) MICROFIBER (/microfiber) — Glass wool insulation, roof/HVAC/industrial
3) SCG (/scg) — SCG insulation, roof/HVAC/acoustic/high-temp

Each page:
- Hero section (canonical template: pill + title + description + buttons)
- Brand description with key product info
- Product grid (6+ models per brand with images)
- Features section (quality, pricing, delivery)
- CTA section (canonical template)

Images: downloaded from supplier site to public/images/{brand}/
Menu: added to Header, BaseLayout nav (under ฉนวนหุ้มท่อ), Footer, all-products

Crawled from: 3t-insulation.com/brand/{rockwool,microfiber,scg}/
(13 products per brand, ~39 total product models referenced)

Pages created: 3 | Files changed: 37 (3 new .astro + 3 image dirs + 3 menu edits)
EOF
)
2026-06-10 11:10:24 +07:00
Kunthawat Greethong
5abe1edb71 fix(content): 4 content updates per user request
1 เทอร์โมเบรค → Thermobreak (display names only)
   - Header menu, BaseLayout nav/categories, all-products, index
   - URLs (/เทอร์โมเบรค-thermobreak) left unchanged
   - Product page (เทอร์โมเบรค-thermobreak.astro) unchanged

2) รูป category ระบบน้ำ → valve kitz image
   - ระบบน้ำ.astro + index.astro: water-pump_000C.jpg → valve_000C.jpg

3) ท่อพีพีอาร์ → ระบบท่อ (category + menu only)
   - Header, Footer, BaseLayout, all-products (6 category refs), index
   - Product names/sub-labels left unchanged

4) Add Supply Air image to grilles page
   - New image: /images/grilles/supply-air.jpg
   - Added to product details gallery alongside existing images
EOF
)
2026-06-10 10:49:59 +07:00
Kunthawat Greethong
31b0619c90 feat(images): replace thermobreak main product image
Replace thermobreak-solarblock.png with new thermobreak-main.jpg
across product page, all-products, and homepage (6 refs total).

New image copied to public/images/thermobreak/.
Blog images (thermobreak-hero.jpg, etc.) left unchanged.
2026-06-10 10:34:31 +07:00
Kunthawat Greethong
ea36f28c17 fix(hero): remove duplicate old CTAs from 19 product pages
Old CTAs (e.g. 'สนใจสินค้าของเรา?', 'สอบถามราคาและสั่งซื้อ',
'sนใจรั้วเทวดา?') were still present above the canonical CTA
section added in 17f4eb7. Each page now has exactly one CTA.

Deleted section types:
  - 12x section gradient (สนใจสินค้าของเรา?)
  -  5x section gradient (สนใจสินค้านี้? — different buttons)
  -  3x box bg-primary-50 (สอบถามราคาและสั่งซื้อ)
  -  2x accent gradient sections
  -  1x div-based (ตู้ดับเพลิง — special format)

Verified: new CTA intact, no duplicate headings, build 40/40 OK,
HTML render: 20/20 have correct single CTA with 2 buttons.
2026-06-10 10:30:00 +07:00
Kunthawat Greethong
17f4eb752b fix(content): drop @ prefix from JPPSELECTION in text + add CTA to 20 products
Two changes driven by content review:

1) @JPPSELECTION → JPPSELECTION in user-visible text only
   - 19 page files: drop @ from inline JPPSELECTION in <p> tags
   - BaseLayout.astro + Footer.astro: drop @ from companyInfo.line
     (rendered as "LINE: @JPPSELECTION" in mobile CTA + footer)
   - URL line.me/ti/p/~JPPSELECTION left unchanged (the @ belongs
     in the URL per line.me convention)

2) Add missing CTA section to all 20 product pages
   - 16 pages had no CTA at all
   - 4 pages (armflex, aeroflex, maxflex, xy-lent) had bespoke
     CTA variants; replaced with canonical template
   - Template copied from ppr-thai-ppr
   - CTA inserted before </main> in all 20 product pages

Verified rendered HTML:
- @JPPSELECTION in <p> text = 0 across all 20 pages
- "LINE: JPPSELECTION" rendered in footer / mobile CTA
- heading + contact + line buttons present in all 20

Files touched: 30
2026-06-10 09:44:28 +07:00
Kunthawat Greethong
eb6557eeb3 fix(hero): correct Line icon SVG path in all 20 product heroes
The SVG path used for the Line chat button in the canonical template
contained a malformed curve command — a stray '0' inserted into a
relative bezier segment that broke the path into an unintended
sub-path. Result: the icon rendered as a misaligned blob instead of
the LINE wordmark glyph.

Restored the canonical path from ท่อ-ppr-thai-ppr (the untouched
stash reference) for all 20 product hero pages:

  วาล์ว-valve, ท่อ-syler, ตู้ดับเพลิง, armflex, grilles, aeroflex,
  durgo-avvs, maxflex, pipe-coupling, realflex, water-pump,
  water-treatment, ท่อ-xy-lent, ระบบรั้วไวน์แมน, รั้วเทวดา,
  หัวจ่าย-ball-jet, เครื่องเชื่อม-hdpe, เครื่องเชื่อม-ppr,
  เทอร์โมเบรค, เม็กกรู๊ฟ-คับปลิ้ง

Path length 1079 chars, byte-for-byte identical to the working
template (stash) version. Verified against rendered HTML: all 20
pages now have the correct path; no other markup changed.
2026-06-10 09:30:29 +07:00
Kunthawat Greethong
74db1dad77 fix(hero): correct hero layout to TEXT-LEFT for 12 product pages
Two prior commits (d8ff358, 9653fcb) claimed to rewrite heroes with a
TEXT-LEFT template, but the source-order check in the prior apply step
was misread. The committed heroes were still IMAGE-LEFT, with the
sticky wrapper around the image and the data-price-button still
rendering where the page has no #pricelist section.

This commit rewrites the hero section of 12 pages against the actual
HEAD~1 (9653fcb) source and the HEAD (d8ff358) source for วาล์ว-valve
to apply the canonical TEXT-LEFT template correctly:
  - text container (pill, title, features, buttons) before image
  - sticky wrapper moved to the image column
  - data-price-button removed where id="pricelist" is missing
  - max-w-md mx-auto lg:max-w-none classes restored on image + card

Pages: วาล์ว-valve, realflex, water-pump, water-treatment, ท่อ-xy-lent,
ระบบรั้วไวน์แมน, รั้วเทวดา, หัวจ่าย-ball-jet, เครื่องเชื่อม-hdpe,
เครื่องเชื่อม-ppr, เทอร์โมเบรค, เม็กกรู๊ฟ-คับปลิ้ง

Verified rendered HTML: all 12 pages have TEXT-LEFT order in DOM,
data-price-button present only where id="pricelist" exists.
2026-06-10 09:10:14 +07:00
Kunthawat Greethong
9653fcbaf9 refactor(hero): rewrite 11 product hero sections to canonical template
Continue template migration for the remaining image-LEFT product pages.

- Extract data per page (pill, title, image, features, hasPricelist)
- Generate hero from canonical template (text-LEFT, image-RIGHT, animated bg)
- Remove dead <Header slot=Footer slot= StickyBottomCTA slots
- Remove dead data-price-button when id="pricelist" missing
- Drop legacy image-LEFT layout, mobile-only sections, justify-center workarounds
- Apply consistent TEXT-LEFT order across all product heroes
- Drop unused special components (auto-rendered by BaseLayout already)

Pages: realflex, water-pump, water-treatment, ท่อ-xy-lent (0 features),
ระบบรั้วไวน์แมน, รั้วเทวดา (0 features), หัวจ่าย-ball-jet (6 features),
เครื่องเชื่อม-hdpe, เครื่องเชื่อม-ppr (5 features),
เทอร์โมเบรค, เม็กกรู๊ฟ-คับปลิ้ง (mobile+desktop special).

Verified: 11 pages render with valid HTML, correct features per page.
2026-06-10 08:35:22 +07:00
Kunthawat Greethong
d8ff358dd7 refactor(hero): rewrite 9 product hero sections to canonical template
- Extract data from each page (pill, title, image, features, hasPricelist)
- Generate hero from canonical template (text-LEFT, image-RIGHT, animated bg)
- Remove dead <Header slot=Footer slot= StickyBottomCTA slots (BaseLayout renders defaults)
- Remove dead data-price-button when id="pricelist" missing (auto-detect hides it)
- Drop legacy justify-center/max-w-md workarounds; use canonical classes
- Apply consistent TEXT-LEFT order across all product heroes
- Drop unused mobile-only sections in ตู้ดับเพลิง (page now follows template)
- Add overflow-x-clip on header/footer (submenu overflow fix)
- Add .submenu-anchor class for submenu positioning

Verified: 9 pages render with valid HTML, TEXT-LEFT, correct features, no dead links.
2026-06-10 08:16:06 +07:00
hermes
d7aa7c2013 docs(ci): rewrite setup guide for Gitea Webhook deploy trigger
The previous doc assumed a Gitea Actions workflow (which requires a
self-hosted act_runner that we don't have, hence the 'No matching
online runner with label: ubuntu-latest' error).

The actual setup is much simpler: a Gitea Webhook pointing at the
deploy endpoint. No runner, no workflow file. Setup is one-time in
Settings -> Webhooks -> Add Webhook.

Documents:
- The push -> webhook -> deploy -> EasyPanel flow
- Exact payload URL, method, content type, events
- Test Delivery verification step
- Why Gitea Actions doesn't work without act_runner
- Troubleshooting: push not triggering, build failure modes
  (nixpacks 'No start command', node 20 vs 22), and a curl recipe
  for redeploying without a code change.
2026-06-09 14:54:14 +07:00
hermes
4820289252 ci: remove Gitea Actions workflows (rely on EasyPanel auto-deploy)
The workflows were never able to run — Gitea Actions has no managed
runners the way GitHub does, and 'ubuntu-latest' isn't a label this
self-hosted Gitea instance can match. Every push to main showed the
job as 'No matching online runner with label: ubuntu-latest' in
the Actions tab.

EasyPanel already watches the 'main' branch and rebuilds on push
(Dockerfile-based, git source). The CI step was duplicative and
produced noise in the Actions tab without running anything.

If a real CI step is needed later (lint, build artifact, test), a
Gitea act_runner has to be installed on a server, registered with
labels like 'self-hosted:host:linux', and the workflow has to use
those labels. Skipping that for now.

Files removed:
  .gitea/workflows/build-and-deploy.yml
  .gitea/workflows/lint.yml
  .gitea/  (empty after removal)
2026-06-09 14:36:47 +07:00
hermes
b7931731f9 fix(responsive): mobile footer restructure + categories 1-col + overflow fix
Some checks failed
Build & Deploy to EasyPanel / build-and-deploy (push) Has been cancelled
Lint & Test / build-check (push) Has been cancelled
Three mobile issues fixed:

1. Categories section: <640px (mobile) now 1 column. Previously it was
   already 2 columns at the smallest breakpoint which made the tiles feel
   cramped. Uses Tailwind's default sm: (640px) for the 1->2 transition,
   not a custom min-[500px].

2. Footer mobile layout (<1024px): three centered rows inside the same
   max-w-7xl container.
     - Row 1: Company Info (1 col, max-w-md, centered)
     - Row 2: Quick Links + สินค้ายอดนิยม (2 cols, max-w-2xl, centered)
     - Row 3: ติดต่อเรา (1 col, max-w-sm outer, max-w-xs button column)
   The narrow inner max-widths are deliberate: a full-width row of
   contact buttons looks stretched, especially the bottom one with the
   โทรเลย / แอดไลน์ buttons.

3. Horizontal overflow bug: data-animate='fade-left' and 'fade-right'
   children sit at translateX(30px) / translateX(-30px) in their
   initial state, before the IntersectionObserver fires. On small
   viewports this pushes the page 30px wider than the viewport and
   reveals a horizontal scrollbar as soon as the user loads the page.
   Adding overflow-x-clip on <footer> keeps the off-screen translation
   contained inside the footer.
2026-06-09 14:18:54 +07:00
Kunthawat Greethong
0bd480d103 merge: bring Dockerfile Node 22 fix from main 2026-06-09 13:33:30 +07:00
hermes
21a538cbb8 fix(deploy): bump Dockerfile Node from 20-alpine to 22-alpine
Some checks failed
Build & Deploy to EasyPanel / build-and-deploy (push) Has been cancelled
Lint & Test / build-check (push) Has been cancelled
Astro 6.x requires Node >=22.12.0. The previous Dockerfile used
node:20-alpine, which the Astro CLI rejected with:

  Node.js v20.20.2 is not supported by Astro!
  Please upgrade Node.js to a supported version: ">=22.12.0"

EasyPanel pulled the change, ran the build, and failed at
`RUN npm run build`. Bumping to node:22-alpine fixes it.

Also added two 'common failure' sections to docs/ci-setup.md
covering the nixpacks 'No start command' and Node version
mismatch errors we just hit.
2026-06-09 13:33:14 +07:00
277 changed files with 2950 additions and 3758 deletions

View File

@@ -1,97 +0,0 @@
name: Build & Deploy to EasyPanel
on:
push:
branches:
- main
workflow_dispatch:
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci --no-audit --no-fund
- name: Build Astro site
run: npm run build
- name: Trigger EasyPanel redeploy
if: success()
env:
EASYPANEL_TOKEN: ${{ secrets.EASYPANEL_TOKEN }}
EASYPANEL_PROJECT_NAME: ${{ secrets.EASYPANEL_PROJECT_NAME }}
EASYPANEL_SERVICE_NAME: ${{ secrets.EASYPANEL_SERVICE_NAME }}
run: |
# EasyPanel tRPC endpoint for app service redeploy
EASYPANEL_API="https://panelwebsite.moreminimore.com/api"
DEPLOY_URL="${EASYPANEL_API}/trpc/services.app.deployService"
# Guard: required secrets
if [ -z "$EASYPANEL_TOKEN" ] || [ -z "$EASYPANEL_PROJECT_NAME" ] || [ -z "$EASYPANEL_SERVICE_NAME" ]; then
echo "::warning::One or more required secrets are empty:"
[ -z "$EASYPANEL_TOKEN" ] && echo " - EASYPANEL_TOKEN"
[ -z "$EASYPANEL_PROJECT_NAME" ] && echo " - EASYPANEL_PROJECT_NAME"
[ -z "$EASYPANEL_SERVICE_NAME" ] && echo " - EASYPANEL_SERVICE_NAME"
echo "Skipping deploy trigger. Set these in repo settings to enable auto-deploy."
exit 0
fi
# tRPC mutation payload: {"json":{"projectName":"...","serviceName":"..."}}
PAYLOAD=$(jq -nc \
--arg pj "$EASYPANEL_PROJECT_NAME" \
--arg sv "$EASYPANEL_SERVICE_NAME" \
'{json:{projectName:$pj, serviceName:$sv}}')
echo "Triggering EasyPanel redeploy"
echo " Endpoint: $DEPLOY_URL"
echo " Project: $EASYPANEL_PROJECT_NAME"
echo " Service: $EASYPANEL_SERVICE_NAME"
echo " Payload: $PAYLOAD"
echo ""
HTTP_CODE=$(curl -sS -o /tmp/easypanel-response.json -w "%{http_code}" \
-X POST "$DEPLOY_URL" \
-H "Authorization: Bearer *** \
-H "Content-Type: application/json" \
-d "$PAYLOAD")
RESPONSE=$(cat /tmp/easypanel-response.json)
echo " HTTP $HTTP_CODE"
echo " Response: $RESPONSE"
# 2xx = success
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
echo ""
echo "EasyPanel redeploy triggered successfully"
exit 0
fi
# 4xx with explicit field errors = workflow is right, payload is wrong
if [ "$HTTP_CODE" -ge 400 ] && [ "$HTTP_CODE" -lt 500 ]; then
echo "::error::EasyPanel rejected the deploy request (HTTP $HTTP_CODE). See response above."
echo "::error::Likely the payload shape does not match what the panel expects."
echo "::error::Check https://panelwebsite.moreminimore.com/api for the procedure spec."
exit 1
fi
# 5xx = server error
echo "::error::EasyPanel API error (HTTP $HTTP_CODE). Response: $RESPONSE"
exit 1
- name: Upload build artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
retention-days: 7

View File

@@ -1,22 +0,0 @@
name: Lint & Test
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
build-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci --no-audit --no-fund
- name: Build (catches syntax + missing-image errors before deploy)
run: npm run build

View File

@@ -1,7 +1,7 @@
# ===================================================================== # =====================================================================
# Stage 1: Build the Astro static site # Stage 1: Build the Astro static site
# ===================================================================== # =====================================================================
FROM node:20-alpine AS builder FROM node:22-alpine AS builder
WORKDIR /app WORKDIR /app

View File

@@ -1,13 +1,14 @@
import { defineConfig } from 'astro/config' import { defineConfig } from 'astro/config'
import tailwindcss from '@tailwindcss/vite' import tailwindcss from '@tailwindcss/vite'
import react from '@astrojs/react' import react from '@astrojs/react'
import sitemap from '@astrojs/sitemap'
import { fileURLToPath } from 'url' import { fileURLToPath } from 'url'
import path from 'path' import path from 'path'
const __dirname = path.dirname(fileURLToPath(import.meta.url)) const __dirname = path.dirname(fileURLToPath(import.meta.url))
export default defineConfig({ export default defineConfig({
site: 'https://dealplustech.com', site: 'https://dealplustech.co.th',
output: 'static', output: 'static',
vite: { vite: {
plugins: [tailwindcss()], plugins: [tailwindcss()],
@@ -22,6 +23,7 @@ export default defineConfig({
}, },
integrations: [ integrations: [
react(), react(),
sitemap(),
], ],
build: { build: {
assets: '_assets', assets: '_assets',

View File

@@ -1,85 +1,109 @@
# CI/CD Setup — EasyPanel Deploy # CI/CD Setup — Deploy via Gitea Webhook
Push to `main` triggers `build-and-deploy.yml`: The site auto-rebuilds on every push to `main` via a Gitea **webhook**
1. Builds the Astro static site into `dist/` (no Actions runner, no `.gitea/workflows/`, no act_runner required).
2. Uploads `dist/` as a 7-day artifact
3. Calls EasyPanel tRPC endpoint to trigger a redeploy
## Required Gitea repo secrets ## How it works
Go to **Settings → Actions → Secrets** and add three secrets: ```
git push origin main
Gitea Webhook (Gitea built-in, not Gitea Actions)
│ POST Content-Type: application/json
http://110.164.146.47:3000/api/deploy/<token>
│ HTTP 200 "Deploying..."
EasyPanel pulls repo, builds with Dockerfile, redeploys
```
| Name | Value (for this site) | Where to get it | ## One-time webhook setup
|---|---|---|
| `EASYPANEL_TOKEN` | `cmq61xwrv000407qn9e2hhfuw` | EasyPanel → profile/settings → API tokens |
| `EASYPANEL_PROJECT_NAME` | `customerwebsite` | EasyPanel → project name in the dashboard |
| `EASYPANEL_SERVICE_NAME` | `dealplustech-astro` | EasyPanel → service name inside the project |
> ⚠️ Project name ≠ repo name. The site lives in the `customerwebsite` In `https://git.moreminimore.com/kunthawat/dealplustech-astroreal/settings/hooks`:
> project on the panel; the repo is `dealplustech-astroreal`.
If any of these are empty, the workflow logs a warning and skips the deploy 1. Click **Add Webhook → Gitea**
trigger. The build still runs and the artifact is still uploaded. 2. Fill in:
- **Payload URL**: `http://110.164.146.47:3000/api/deploy/772d2c3a4a7d8671657c947c059bc1cdc64bd816efb7fbe2`
- **HTTP Method**: `POST`
- **Content Type**: `application/json`
- **Events**: **Push events**
- **Active**: ✓
- **Branch filter**: leave empty (or `main` to restrict)
3. Click **Add Webhook**
4. Test: click the webhook row → **Test Delivery****Push events** → confirm
**Last Response** is HTTP 200.
Done. From now on every push to main redeploys automatically.
## EasyPanel service requirements ## EasyPanel service requirements
The service on the panel side must be: The service on the panel side (`project=customerwebsite`,
`service=dealplustech-astro`) must be:
- **Type: `app`** - **Type: `app`**
- **Source: Git**, pointing at this repo (`kunthawat/dealplustech-astroreal`) - **Source: Git**, branch `main`
on branch **`main`** (not `source-code`). - **Build type: `dockerfile`**, file `Dockerfile` at repo root
- **Build type: `dockerfile`**, **file: `Dockerfile`** at the repo root.
> The workflow used to trigger nixpacks builds, but nixpacks expects a
> `start` script in `package.json` and an Astro static site doesn't
> have one. Switching to a Dockerfile + nginx fixes that.
- **Port: `80`** - **Port: `80`**
If you need a different service type later, swap the endpoint in The Dockerfile is two-stage:
`.gitea/workflows/build-and-deploy.yml` to the matching procedure:
- `services.app.deployService` (Dockerfile / app)
- `services.box.rebuildDockerImage` (low-level)
- `services.compose.deployService` (docker-compose)
## Why Dockerfile and not nixpacks 1. `node:22-alpine` — runs `npm ci` and `npm run build` to produce
`dist/`
2. `nginx:1.27-alpine` — copies `dist/` to nginx's web root and serves
it (with gzip, security headers, 1-year cache for hashed assets,
and a `try_files` fallback for Astro's UTF-8 slugs)
Astro builds to static files in `dist/` — there is no Node server to run. ## Why not Gitea Actions?
Nixpacks tries to find a `start` command and fails. The Dockerfile in
this repo:
1. **Stage 1** (`node:20-alpine`): runs `npm ci` then `npm run build` to Gitea Actions does **not** ship with managed runners (unlike GitHub
produce `dist/`. Actions' `ubuntu-latest`). Without a self-hosted `act_runner` registered
2. **Stage 2** (`nginx:1.27-alpine`): copies `dist/` to nginx's web root with matching labels, any workflow run will fail with
and serves it. **"No matching online runner with label: ubuntu-latest"**.
`nginx.conf` ships gzip, security headers, 1-year cache for hashed The Gitea Webhook mechanism is built into Gitea itself — no runner
assets, and a `try_files` fallback for client-side routes (Astro's required. It fires the same events but POSTs to a URL you control,
file-based routing produces UTF-8 slugs). which is the simplest possible deploy trigger.
## Verifying the trigger payload If a real CI step is ever needed (lint, test, build artifact), install
`act_runner` on a box, register it, and write a workflow using
`runs-on: self-hosted`. See `act_runner` docs for setup.
If the deploy runs but the panel rejects it, the response in the workflow ## Troubleshooting
log will show the exact `zodErrors` field telling you which field name
or shape is wrong. Update the `PAYLOAD` JSON in the workflow accordingly.
To test the payload shape from your local machine: ### Push happened, but site didn't update
1. Open the webhook row in Settings → Webhooks
2. Open **Recent Deliveries**
3. Check the most recent one:
- Status 200 → deploy endpoint received it, check EasyPanel logs
- Status != 200 → check the response body
- **No recent delivery** → webhook is not bound to this branch/event
or the push target branch doesn't match the filter
### Build fails inside EasyPanel
1. EasyPanel UI → service `dealplustech-astro` → Logs tab
2. Look for the `nixpacks` or `docker build` step
3. Common failures:
- **"No start command could be found"**: you're on `nixpacks`. Switch
build type to `dockerfile` and point at `Dockerfile` at repo root.
- **"Node.js v20.x is not supported by Astro"**: the Dockerfile is
using `node:20-alpine`. It should be `node:22-alpine` (Astro 6
requires `>=22.12.0`).
- **"Failed to sync changes"** (HTTP 500 from the deploy endpoint):
usually a panel-side state issue. Retry, or open the service in
the panel UI and check container state.
### Want to redeploy without a code change
The webhook URL itself is idempotent. You can hit it directly with:
```bash ```bash
curl -sS -X POST \ curl -X POST \
"https://panelwebsite.moreminimore.com/api/trpc/services.app.deployService" \ "http://110.164.146.47:3000/api/deploy/772d2c3a4a7d8671657c947c059bc1cdc64bd816efb7fbe2"
-H "Authorization: Bearer *** -H "Content-Type: application/json" \
-d '{"json":{"projectName":"customerwebsite","serviceName":"dealplustech-astro"}}'
``` ```
A 2xx response = the panel accepted the trigger. The service will start A `200` response with body `Deploying...` means the panel accepted the
rebuilding/redploying in the EasyPanel dashboard. trigger. The actual rebuild happens in the background — check EasyPanel
UI for the progress.
## Common failure: "Failed to sync changes" (HTTP 500)
This is a **server-side** error, not a payload problem. It usually means:
- The EasyPanel service is currently in a state that can't be redeployed
(e.g. the previous container is stuck, or a build is in progress).
- The Docker daemon on the panel host is busy.
Try again in a few seconds. If it persists, open the EasyPanel dashboard
and check the service's container state.

77
package-lock.json generated
View File

@@ -1,15 +1,16 @@
{ {
"name": "dealplustech-emdash", "name": "dealplustech-astroreal",
"version": "1.0.0", "version": "1.0.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "dealplustech-emdash", "name": "dealplustech-astroreal",
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"@astrojs/check": "^0.9.4", "@astrojs/check": "^0.9.4",
"@astrojs/react": "^5.0.5", "@astrojs/react": "^5.0.5",
"@astrojs/sitemap": "^3.7.3",
"@tailwindcss/typography": "^0.5.15", "@tailwindcss/typography": "^0.5.15",
"@tailwindcss/vite": "^4.0.0", "@tailwindcss/vite": "^4.0.0",
"astro": "^6.1.7", "astro": "^6.1.7",
@@ -160,6 +161,17 @@
"react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0" "react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0"
} }
}, },
"node_modules/@astrojs/sitemap": {
"version": "3.7.3",
"resolved": "https://registry.npmjs.org/@astrojs/sitemap/-/sitemap-3.7.3.tgz",
"integrity": "sha512-f8euLVsyeAmAkSm/1M2Kb8sL8byQmfgbvBNaHFItCheTj/IpiJYSEWVcqDHZ/yEHxiS7+w87mQkzwZaPHmk5GA==",
"license": "MIT",
"dependencies": {
"sitemap": "^9.0.0",
"stream-replace-string": "^2.0.0",
"zod": "^4.3.6"
}
},
"node_modules/@astrojs/telemetry": { "node_modules/@astrojs/telemetry": {
"version": "3.3.2", "version": "3.3.2",
"resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.3.2.tgz", "resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.3.2.tgz",
@@ -2329,8 +2341,6 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz",
"integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==", "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==",
"license": "MIT", "license": "MIT",
"optional": true,
"peer": true,
"dependencies": { "dependencies": {
"undici-types": ">=7.24.0 <7.24.7" "undici-types": ">=7.24.0 <7.24.7"
} }
@@ -2355,6 +2365,15 @@
"@types/react": "^19.2.0" "@types/react": "^19.2.0"
} }
}, },
"node_modules/@types/sax": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz",
"integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/unist": { "node_modules/@types/unist": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
@@ -2562,6 +2581,12 @@
"url": "https://github.com/sponsors/jonschlinkert" "url": "https://github.com/sponsors/jonschlinkert"
} }
}, },
"node_modules/arg": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
"license": "MIT"
},
"node_modules/argparse": { "node_modules/argparse": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@@ -5748,6 +5773,40 @@
"integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/sitemap": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/sitemap/-/sitemap-9.0.1.tgz",
"integrity": "sha512-S6hzjGJSG3d6if0YoF5kTyeRJvia6FSTBroE5fQ0bu1QNxyJqhhinfUsXi9fH3MgtXODWvwo2BDyQSnhPQ88uQ==",
"license": "MIT",
"dependencies": {
"@types/node": "^24.9.2",
"@types/sax": "^1.2.1",
"arg": "^5.0.0",
"sax": "^1.4.1"
},
"bin": {
"sitemap": "dist/esm/cli.js"
},
"engines": {
"node": ">=20.19.5",
"npm": ">=10.8.2"
}
},
"node_modules/sitemap/node_modules/@types/node": {
"version": "24.13.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.2.tgz",
"integrity": "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==",
"license": "MIT",
"dependencies": {
"undici-types": "~7.18.0"
}
},
"node_modules/sitemap/node_modules/undici-types": {
"version": "7.18.2",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
"license": "MIT"
},
"node_modules/smol-toml": { "node_modules/smol-toml": {
"version": "1.6.1", "version": "1.6.1",
"resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.1.tgz", "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.1.tgz",
@@ -5779,6 +5838,12 @@
"url": "https://github.com/sponsors/wooorm" "url": "https://github.com/sponsors/wooorm"
} }
}, },
"node_modules/stream-replace-string": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/stream-replace-string/-/stream-replace-string-2.0.0.tgz",
"integrity": "sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==",
"license": "MIT"
},
"node_modules/string-width": { "node_modules/string-width": {
"version": "4.2.3", "version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
@@ -5980,9 +6045,7 @@
"version": "7.24.6", "version": "7.24.6",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz",
"integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==",
"license": "MIT", "license": "MIT"
"optional": true,
"peer": true
}, },
"node_modules/unified": { "node_modules/unified": {
"version": "11.0.5", "version": "11.0.5",

View File

@@ -12,6 +12,7 @@
"dependencies": { "dependencies": {
"@astrojs/check": "^0.9.4", "@astrojs/check": "^0.9.4",
"@astrojs/react": "^5.0.5", "@astrojs/react": "^5.0.5",
"@astrojs/sitemap": "^3.7.3",
"@tailwindcss/typography": "^0.5.15", "@tailwindcss/typography": "^0.5.15",
"@tailwindcss/vite": "^4.0.0", "@tailwindcss/vite": "^4.0.0",
"astro": "^6.1.7", "astro": "^6.1.7",

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 472 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="400" height="300" viewBox="0 0 400 300"><rect width="400" height="300" fill="#65a30d"/><text x="200" y="150" text-anchor="middle" fill="white" font-family="sans-serif" font-size="18">Coupling 05</text></svg>

Before

Width:  |  Height:  |  Size: 255 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="400" height="300" viewBox="0 0 400 300"><rect width="400" height="300" fill="#65a30d"/><text x="200" y="150" text-anchor="middle" fill="white" font-family="sans-serif" font-size="18">Coupling 07</text></svg>

Before

Width:  |  Height:  |  Size: 255 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 581 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 566 KiB

Some files were not shown because too many files have changed in this diff Show More