尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證Custom Artist Portfolio made for my favorite living painter, Simone Chagoya. Includes a 3D gallery designed using Blender and coded using Three.js
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 README ↗The online studio and store for Simone Chagoya: natural gemstone necklaces, signed prints, watercolor cards and original paintings, made in Chicago.
Built with Astro as a static site, plus two serverless functions: one opens Stripe Checkout, one receives Stripe's webhook. It is hosted on Netlify and has no monthly platform fee. Payments cost Stripe's standard processing rate.
npm install
npm run dev # http://localhost:4321
npm run build # production build into dist/
npm run check # type check
Node 22.12 or newer.
Netlify. Create a site from this repo. netlify.toml already holds the build settings. Point simonechagoya.com at it.
Stripe (Payments only; Connect is for platforms that pay other sellers, and this shop has one seller). Do this once in a Stripe sandbox, then again in live mode, because live keys and live webhooks have their own secrets.
npm run setup:stripe on your machine. It signs in to Netlify (browser, first time only), asks for the Stripe secret key and an optional restricted key, then creates the Stripe webhook and the "Stripe sale" build hook, saves STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET and NETLIFY_BUILD_HOOK_URL on the site, and starts a deploy. The secret key is only used to create the webhook and is never saved. For the site key, a restricted key (rk_...) with Checkout Sessions: Write, Products: Read and Prices: Read limits the damage if it ever leaks.npm run setup:stripe -- --live with live keys. It replaces the keys for production only, so deploy previews keep test keys and can never charge a card. Test orders stop counting against stock once live keys are in.https://<site>/api/stripe-webhook with the events checkout.session.completed, checkout.session.async_payment_succeeded, checkout.session.async_payment_failed and checkout.session.expired; the build hook is Netlify > Site configuration > Build & deploy > Build hooks. Checkout stays off until both Stripe variables are set, because the webhook is what stops a one-of-one piece from selling twice.4242 4242 4242 4242. Then check: Stripe shows the webhook delivered with a 200, a second checkout for the piece is refused, and after the rebuild (about a minute) the piece shows Sold. Test orders stop counting once live keys are in, so there is nothing to clean up.STRIPE_AUTOMATIC_TAX=true. Without an active registration, Stripe collects no tax and shows no error. Stripe Tax charges a per-transaction fee; confirm the setup with her accountant.Order emails and the orders page. The site sends its own emails: a confirmation to the buyer, a sale alert to Simone, and a shipping email with tracking when Simone marks an order shipped at /studio. They go through Resend (free up to 3,000 emails a month).
simonechagoya.com under Domains, and add the DNS records it shows wherever the domain's DNS lives. Wait for it to say Verified.RESEND_API_KEY (the key), ORDER_NOTIFY_EMAIL (where sale alerts go; comma separated for more than one) and STUDIO_PASSWORD (the password for /studio). Optional: ORDER_EMAIL_FROM to change the sender from Simone Chagoya Studio <[email protected]>. Then redeploy.[email protected] reaches a real inbox: buyers reply to it.Product photos. The 13 live Etsy listings are imported with their real photos (71 of them, graded to the shared finish). Etsy's designed mock-up cards (the photo on a scene with the name and logo) and collages are left out on purpose: the site shows real product photos only. The two print scans are converted to sRGB without grading, so the art's colors stay true.
Sold pieces and listing videos come through the Etsy API, which the sandbox also cannot reach. On a normal machine, follow "Sold listings and videos" under Importing from Etsy, then commit src/content/products, src/assets/products and public/media/products.
Forms. In Netlify > Forms, turn on form detection. Add an email notification for the inquiry form (commissions and contact) and the newsletter form.
Confirm the policies listed under "Needs Simone's sign-off" below.
Free listings. Submit https://simonechagoya.com/feeds/products.xml to Google Merchant Center (free Shopping listings) and as a Pinterest catalog. Only real, available products with photos are included.
Etsy. Point the Etsy shop announcement and About section at the new site.
Each product is one Markdown file in src/content/products/. The file name is the URL: smoke-necklace.md becomes /products/smoke-necklace.
---
title: "Smoke Necklace"
category: necklaces # necklaces, earrings, bracelets, rings, prints, cards, originals
price: 250
summary: "The Smoke Necklace is for the woman who is grounded, steady, and clear in her purpose."
intention: "Grounding · Focus · Protection" # brass caps line above the title
images:
- ../../assets/products/smoke-necklace/1.jpg
materials: ["Smoky and Clear Quartz", "Solid 925 Sterling Silver Clasp, rings, and beads"]
dimensions: "17 in"
specs: # the Details tab; derived from materials and dimensions if left out
- { label: "Bead size", value: "23 x 8 mm" }
care: # the Care tab; a general note is shown if left out
- "Store the necklace separately in the included cotton muslin bag."
stock: 1
oneOfAKind: true
status: available # available, sold, or inquire (originals)
featured: true # shown first and on the home page
---
The description goes here, in plain paragraphs.
stock. When a piece runs out it shows Sold. Sold pieces stay on the site, which helps show demand. The buyer gets a confirmation and Simone gets a sale alert by email.simonechagoya.com/studio (bookmark it on your phone), find the order under To ship, pick the carrier, paste the tracking number and press Mark shipped. The buyer gets an email with a tracking link. Gift notes and express orders are flagged there. If a receipt did not go out, the order says so and has a Send receipt button.stock is how many were ever offered on this site, not how many are left, so never lower it after a sale. To restock, raise it by the number you made: a necklace at stock: 2 with two sales goes to stock: 3 for one more. For the same reason, do not re-run the Etsy importer with --force once the site is selling.inventory (orders/<checkout session id>), then trigger a deploy. If a sold piece ever still shows as available after a rebuild, set status: sold in its file; checkout refuses it either way.src/assets/products/<slug>/. Shoot them at a 4:5 ratio, at least 2000 px tall, in soft daylight on linen or stone. Run them through scripts/media first (below) so every photo shares one white balance and finish. The site resizes and compresses them automatically.public/media/products/<slug>/ as short muted MP4 loops with a poster frame, listed under videos: in the product file. They play second in the gallery, only while on screen, and never on their own for visitors who turn off motion.status: inquire. They show an Inquire button that opens the commissions form with the painting's name filled in.scripts/media grades every photo and video to one white balance, exposure and finish, so pieces shot on different days look like one collection. It measures each photo on its own background and corrects the light, not the stones. It also converts iPhone HEIC and Display P3 to web sRGB and strips all metadata, including GPS location.
python3 -m venv .venv-media && .venv-media/bin/pip install -r scripts/media/requirements.txt
.venv-media/bin/python -I scripts/media/grade.py photo IMG_5010.heic src/assets/products/smoke-necklace/1.jpg
.venv-media/bin/python -I scripts/media/video.py IMG_5052.mov public/media/products/smoke-necklace/1.mp4 public/media/products/smoke-necklace/1.jpg --start 2 --dur 8
videos:
- { src: "/media/products/smoke-necklace/1.mp4", poster: "/media/products/smoke-necklace/1.jpg" }
Cards (home page, shop grids, the bag) show a framed cover when the product has one: src/assets/products/<slug>/cover.jpg, made from the first photo by frame.py. It finds the piece on its background, scales every piece to the same height (so tops and bottoms line up in a row), centers it in a 4:5 frame and evens the background out to one shared white. The product page keeps the original photos. Run it again whenever a product's first photo changes:
.venv-media/bin/python -I scripts/media/frame.py photo src/assets/products/smoke-necklace/1.jpg src/assets/products/smoke-necklace/cover.jpg
It is made for flat lays on a plain light background. Check the result by eye; a busy background or a piece worn on the body will not frame well.
video.py tone-maps iPhone HDR, crops to the gallery's 4:5 frame (--x/--y move the crop), trims (--start/--dur, 10 seconds by default), drops the sound and writes a small H.264 file. It needs ffmpeg. Keep each loop under about 3 MB: video lives in the git repo, and every visitor who opens the page downloads it.
npm run import:etsy -- ~/Downloads/EtsyListingsDownload.csv --dry-run # preview, writes nothing
npm run import:etsy -- ~/Downloads/EtsyListingsDownload.csv --replace-samples # import and remove sample listings
The importer creates one file per listing, downloads every listing photo, guesses the category from the title and tags, and prints a list of anything to check by hand. It reads Simone's description format: the intention line, the Details block (into specs), the Care block (into care), and drops the repeated title and the sign-off. It never overwrites an existing product unless you pass --force, and it never touches original paintings.
--no-images imports the text only and records the photo URLs in etsyImages. --fetch-images (no CSV needed) downloads them later. Either way, an image at Etsy's mock-up card size (1080 x 1350) or a repeat of an earlier image in the same listing is left out with a warning. Check the warnings for collages, which it cannot spot.--force rewrites every field from the CSV except featured. Make other hand edits after the last import, or redo them.The API import adds what the CSV leaves out: every sold-out listing becomes a product with status: sold (the Sold badge, a disabled Sold button, left out of the feed and refused at checkout), and every listing video becomes a gallery loop.
Create an app at etsy.com/developers/your-apps. Add http://localhost:3003/etsy-callback as a callback URL.
Put its keystring and shared secret in .env as ETSY_API_KEY and ETSY_SHARED_SECRET.
Sign in once as the shop owner. This saves a token to etsy-export/etsy-token.json (gitignored) that refreshes itself for 90 days:
npm run import:etsy -- --etsy-login
Preview, then import:
npm run import:etsy -- --api --dry-run
npm run import:etsy -- --api
How it matches listings to product files: by etsyListingId when the file has one (the import records it), otherwise by title. A sold listing whose title matches a product still for sale on Etsy is an earlier piece of the same design, so it gets its own page (smoke-necklace-2) instead of marking the one for sale as sold. A product imported from the CSV that has since sold on Etsy is marked sold in place (status: sold, stock: 0); nothing else in the file changes. Original paintings are never touched.
Videos download to etsy-export/videos/<slug>/ and are graded with scripts/media/video.py into public/media/products/<slug>/etsy-<n>.mp4 with a poster frame, then added to the product's videos list. A product that already has its own videos keeps them and skips the Etsy ones, since they are usually the same footage. That needs the media tools (see Photos and video). Without them the raw files wait in etsy-export/ and the next --api run grades them. --no-videos and --no-images skip either download.
npm test runs the API import against fixtures in scripts/fixtures/etsy-api/, with no network.
These are sensible defaults. Change them in src/data/site.ts or the page named.
/shipping-returns)./faq, /shipping-returns, home page). Materials, packaging and care in /faq come from her Etsy listings.site.ts to show it in the footer.src/data/reviews.ts, word for word. The eighth, from a family member, is left out on purpose: the FTC review rule requires disclosing that relationship, and an undisclosed family review is the first thing a skeptical buyer spots. The rating line ("5.0 from 7 reviews on Etsy") is in site.ts.src/
content/products/ one Markdown file per product
content.config.ts the product schema (what fields are allowed)
data/site.ts store settings: shipping, returns, rating, social links
data/reviews.ts customer reviews (section hidden while empty)
assets/ paintings and brand imagery, optimized at build time
components/ header, footer, bag drawer, product card
layouts/ page shells
lib/bag.ts the shopping bag (browser side)
lib/catalog.ts catalog helpers, category names and copy
lib/inventory.ts holds and recorded orders for single pieces (Netlify Blobs)
lib/orders.ts reads orders from Stripe, sends order emails (Resend), records shipments
lib/order-mail.ts the order email templates
pages/ every route, including:
api/checkout.ts re-prices the bag server side, holds single pieces and opens Stripe Checkout
api/stripe-webhook.ts records orders, rebuilds the site and sends the order emails after a sale
studio.astro the orders page: mark shipped with tracking, resend receipts (password protected)
bag.json.ts catalog data the bag reads
feeds/products.xml.ts Google Merchant and Pinterest feed
scripts/import-etsy.mjs Etsy importer: listings CSV, plus sold listings and videos from the API
scripts/etsy-api.mjs Etsy API client and OAuth login
Design system. Colors, type scale and spacing are CSS variables at the top of src/styles/global.css: linen and stone grounds, espresso ink, fired clay and brass accents. The type is Cormorant (display) with Instrument Sans (text). Both fonts are self-hosted.
Logo. Simone's mark lives in public/brand/ as SVG (wordmark, and the full lockup with STUDIO), vectorized from her logo file, plus a PNG for search engines. The site draws it through src/components/Logo.astro, which takes its color from the surrounding text, so the same file works on light and dark backgrounds. To update the logo, replace the SVG files and keep their names.
Checkout safety. The browser only sends product slugs and quantities. Prices, stock, sample status and one-of-one limits are all checked again on the server before Stripe sees the order. A single piece (or the last one of a small run) is held for one buyer while their Checkout is open (31 minutes, then the session expires), and recorded orders count against stock at checkout even before the site rebuilds. Checkout stays off until both the Stripe key and the webhook secret are set, because without the webhook no sale is recorded. Holds and orders live in Netlify Blobs (src/lib/inventory.ts). Webhook events are verified against the signing secret before anything else runs.