create-easel-theme 0.1.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +6 -6
  2. package/dist/cli.js +8 -5
  3. package/dist/templates/js/base/config/settings_schema.json +1 -1
  4. package/dist/templates/js/base/layout/password.liquid +19 -0
  5. package/dist/templates/js/base/locales/en.default.json +53 -0
  6. package/dist/templates/js/base/locales/en.default.schema.json +30 -0
  7. package/dist/templates/js/base/sections/contact-form.liquid +23 -0
  8. package/dist/templates/js/base/sections/main-article.liquid +15 -0
  9. package/dist/templates/js/base/sections/main-blog.liquid +28 -0
  10. package/dist/templates/js/base/sections/main-cart.liquid +31 -0
  11. package/dist/templates/js/base/sections/main-collection.liquid +25 -0
  12. package/dist/templates/js/base/sections/main-list-collections.liquid +27 -0
  13. package/dist/templates/js/base/sections/main-page.liquid +11 -0
  14. package/dist/templates/js/base/sections/main-password.liquid +17 -0
  15. package/dist/templates/js/base/sections/main-product.liquid +25 -0
  16. package/dist/templates/js/base/sections/main-search.liquid +28 -0
  17. package/dist/templates/js/base/snippets/product-card.liquid +15 -0
  18. package/dist/templates/js/base/src/style.css +74 -1
  19. package/dist/templates/js/base/templates/article.json +8 -0
  20. package/dist/templates/js/base/templates/blog.json +8 -0
  21. package/dist/templates/js/base/templates/cart.json +8 -0
  22. package/dist/templates/js/base/templates/collection.json +8 -0
  23. package/dist/templates/js/base/templates/gift_card.liquid +35 -0
  24. package/dist/templates/js/base/templates/list-collections.json +8 -0
  25. package/dist/templates/js/base/templates/page.contact.json +11 -0
  26. package/dist/templates/js/base/templates/page.json +8 -0
  27. package/dist/templates/js/base/templates/password.json +9 -0
  28. package/dist/templates/js/base/templates/product.json +8 -0
  29. package/dist/templates/js/base/templates/search.json +8 -0
  30. package/dist/templates/ts/base/config/settings_schema.json +1 -1
  31. package/dist/templates/ts/base/layout/password.liquid +19 -0
  32. package/dist/templates/ts/base/locales/en.default.json +53 -0
  33. package/dist/templates/ts/base/locales/en.default.schema.json +30 -0
  34. package/dist/templates/ts/base/sections/contact-form.liquid +23 -0
  35. package/dist/templates/ts/base/sections/main-article.liquid +15 -0
  36. package/dist/templates/ts/base/sections/main-blog.liquid +28 -0
  37. package/dist/templates/ts/base/sections/main-cart.liquid +31 -0
  38. package/dist/templates/ts/base/sections/main-collection.liquid +25 -0
  39. package/dist/templates/ts/base/sections/main-list-collections.liquid +27 -0
  40. package/dist/templates/ts/base/sections/main-page.liquid +11 -0
  41. package/dist/templates/ts/base/sections/main-password.liquid +17 -0
  42. package/dist/templates/ts/base/sections/main-product.liquid +25 -0
  43. package/dist/templates/ts/base/sections/main-search.liquid +28 -0
  44. package/dist/templates/ts/base/snippets/product-card.liquid +15 -0
  45. package/dist/templates/ts/base/src/style.css +74 -1
  46. package/dist/templates/ts/base/templates/article.json +8 -0
  47. package/dist/templates/ts/base/templates/blog.json +8 -0
  48. package/dist/templates/ts/base/templates/cart.json +8 -0
  49. package/dist/templates/ts/base/templates/collection.json +8 -0
  50. package/dist/templates/ts/base/templates/gift_card.liquid +35 -0
  51. package/dist/templates/ts/base/templates/list-collections.json +8 -0
  52. package/dist/templates/ts/base/templates/page.contact.json +11 -0
  53. package/dist/templates/ts/base/templates/page.json +8 -0
  54. package/dist/templates/ts/base/templates/password.json +9 -0
  55. package/dist/templates/ts/base/templates/product.json +8 -0
  56. package/dist/templates/ts/base/templates/search.json +8 -0
  57. package/package.json +22 -3
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # create-easel-theme
2
2
 
3
- Create a focused Shopify Liquid theme with Easel, Vite, and your choice of tools. The interactive UI uses [Clack](https://github.com/bombshell-dev/clack).
3
+ Scaffold a focused Shopify Liquid theme with Vite and Easel. Choose TypeScript or JavaScript, optional Alpine, React or Vue, and plain CSS or Tailwind CSS through a [Clack](https://github.com/bombshell-dev/clack) prompt.
4
4
 
5
5
  ```sh
6
6
  npm create easel-theme@latest my-theme
@@ -43,14 +43,14 @@ Existing folders must be empty apart from `.git` and `.DS_Store`, which are pres
43
43
  - Plain CSS (default) or Tailwind CSS.
44
44
  - Optional dependency installation using npm, pnpm, Yarn, or Bun. The manager is detected from the invoking package manager; use `--package-manager` to override it.
45
45
 
46
- The output is a standalone project using the published `vite-plugin-shopify-easel` package. It does not depend on this repository or on the scaffolder at runtime. The current templates target Easel `0.1.0` and require Node.js 22.12.0 or newer.
46
+ The output is a standalone project using the published `vite-plugin-shopify-easel` package. It does not depend on this repository or on the scaffolder at runtime. The current templates target Easel `0.2.2` and require Node.js 22.12.0 or newer.
47
47
 
48
- The starter includes a homepage counter, a 404 page, theme settings, and translations. It is deliberately not a complete commerce theme. Framework integrations use their official Vite plugins where applicable, and include Theme Editor initialization and cleanup. React includes its development preamble.
48
+ The starter includes a homepage counter, theme settings, translations, generic JSON templates and sections for the standard storefront page types, `theme.liquid` and `password.liquid` layouts, and a Liquid gift card template. Customer account templates are intentionally omitted because current customer accounts operate independently of themes. Shopify's default robots and agent-discovery templates are left unchanged. Metaobject templates are definition-specific and should be added when those definitions enable web pages. The generic sections are starting points rather than complete commerce features or a production design. Framework integrations use their official Vite plugins where applicable, and include Theme Editor initialization and cleanup. React includes its development preamble.
49
49
 
50
50
  ## Non-interactive use
51
51
 
52
52
  ```sh
53
- pnpm create:theme ../my-theme --yes --language ts --framework vue --tailwind --no-install
53
+ npm create easel-theme@latest my-theme -- --yes --language ts --framework vue --tailwind --no-install
54
54
  ```
55
55
 
56
56
  Flags:
@@ -79,7 +79,7 @@ Without an interactive terminal, or in CI, a directory is required, prompts are
79
79
  - The CLI does not authenticate with Shopify, create stores, push themes, initialize Git, or change global package-manager configuration.
80
80
  - The CLI invokes the selected package manager directly, without interpolating the destination into a shell command. pnpm installation uses `--ignore-workspace` to avoid installing into an unrelated ancestor workspace.
81
81
 
82
- After generation, normal Vite commands and Shopify CLI take over. Easel does not wrap either tool. Stop development before building and deploy the generated Liquid loader alongside its matching assets.
82
+ After generation, normal Vite commands and Shopify CLI take over. Easel does not wrap either tool. Run `npm run check` for source and build validation, `npm run check:theme` for Shopify Theme Check, or `npm run check:all` for both. The Theme Check scripts require Shopify CLI to be installed separately. Stop development before building and deploy the generated Liquid loader alongside its matching assets.
83
83
 
84
84
  ## Development
85
85
 
@@ -97,4 +97,4 @@ The CLI is TypeScript bundled with tsup. One base theme is composed with small f
97
97
 
98
98
  The repository's `test/create.test.js` covers generation, validation, filesystem safety, automation, and installation outcomes. The package's `test/install.test.js` also checks signal-handler ordering and cleanup. The package smoke test installs the actual tarball, exercises the npm executable, and installs and checks all 16 language/framework/styling combinations outside the repository. `pnpm test:theme-check` also validates the generated Liquid variants.
99
99
 
100
- Publishing this package is separate from publishing the Vite plugin. No scaffolder release workflow or npm trusted publisher is configured yet; see the repository's `RELEASING.md` for the current release boundary.
100
+ The generator and Vite plugin share a version and trusted-publishing workflow; see the repository's `RELEASING.md` for the release contract.
package/dist/cli.js CHANGED
@@ -22,7 +22,7 @@ function assertTargetDirectory(target) {
22
22
  import { resolve as resolve3 } from "path";
23
23
 
24
24
  // package.json
25
- var version = "0.1.0";
25
+ var version = "0.2.2";
26
26
 
27
27
  // src/generate.ts
28
28
  import {
@@ -191,7 +191,7 @@ function projectPackage(options) {
191
191
  const dependencies = {};
192
192
  const devDependencies = {
193
193
  vite: "8.2.2",
194
- "vite-plugin-shopify-easel": "0.1.0"
194
+ "vite-plugin-shopify-easel": "0.2.2"
195
195
  };
196
196
  if (typescript) devDependencies.typescript = "5.9.3";
197
197
  if (options.framework === "alpine") {
@@ -217,6 +217,7 @@ function projectPackage(options) {
217
217
  devDependencies["@tailwindcss/vite"] = "4.3.3";
218
218
  }
219
219
  const typecheck = options.framework === "vue" ? "vue-tsc" : "tsc";
220
+ const codeCheck = typescript ? `${typecheck} --noEmit && vite build` : "vite build";
220
221
  return {
221
222
  name: packageNameFor(options.target),
222
223
  private: true,
@@ -225,7 +226,9 @@ function projectPackage(options) {
225
226
  scripts: {
226
227
  dev: "vite",
227
228
  build: "vite build",
228
- check: typescript ? `${typecheck} --noEmit && vite build` : "vite build"
229
+ check: codeCheck,
230
+ "check:theme": "shopify theme check",
231
+ "check:all": `${codeCheck} && shopify theme check`
229
232
  },
230
233
  ...Object.keys(dependencies).length ? { dependencies } : {},
231
234
  devDependencies
@@ -290,7 +293,7 @@ Shopify CLI is installed and authenticated separately. The notification flag is
290
293
 
291
294
  ## Build and deploy
292
295
 
293
- Stop the Vite development server, run \`npm run check\`, then use Shopify CLI to preview or deploy the theme. \`npm run build\` builds without the additional TypeScript check.
296
+ Stop the Vite development server, then run \`npm run check:all\` before using Shopify CLI to preview or deploy the theme. This runs the TypeScript and Vite checks followed by Shopify Theme Check. Use \`npm run check\` when Shopify CLI is unavailable, or \`npm run build\` to build without the additional checks.
294
297
 
295
298
  Deploy \`snippets/easel-assets.liquid\` together with its matching built assets. These generated files are ignored by Git. Never delete unrelated assets or Easel's ownership state to force a build.
296
299
 
@@ -298,7 +301,7 @@ Deploy \`snippets/easel-assets.liquid\` together with its matching built assets.
298
301
 
299
302
  Shopify directories live at the project root. Source lives in \`src/\`; Easel is configured in \`vite.config.${options.language}\`. The homepage contains a small counter to demonstrate the selected tools, with initialization and cleanup for Theme Editor section reloads.
300
303
 
301
- This is a minimal starter, not a complete commerce theme: product, collection, cart, and other storefront features are yours to build. Update theme metadata in \`config/settings_schema.json\` for your project.
304
+ The starter includes generic JSON templates and sections for products, collections, the collection list, cart, pages, contact, blogs, articles, search, password access, and 404 responses. It also includes theme and password layouts and a Liquid gift card template. These are accessible starting points, not complete commerce features or a production design. Customer account templates are intentionally omitted because current customer accounts operate independently of themes. Shopify's default robots and agent-discovery templates are left unchanged, and metaobject templates should be added for the definitions that enable web pages. Update theme metadata in \`config/settings_schema.json\` for your project.
302
305
 
303
306
  Start with one shared entry. Add named page bundles only when selective loading is useful; see [the named bundles example](https://github.com/BillyNoyes/Easel/tree/main/examples/named-bundles).
304
307
 
@@ -2,7 +2,7 @@
2
2
  {
3
3
  "name": "theme_info",
4
4
  "theme_name": "__THEME_NAME__",
5
- "theme_version": "0.1.0",
5
+ "theme_version": "0.2.2",
6
6
  "theme_author": "Billy Noyes",
7
7
  "theme_documentation_url": "https://github.com/BillyNoyes/Easel",
8
8
  "theme_support_url": "https://github.com/BillyNoyes/Easel/issues"
@@ -0,0 +1,19 @@
1
+ <!doctype html>
2
+ <html lang="{{ request.locale.iso_code }}">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <link rel="canonical" href="{{ canonical_url | escape }}">
7
+ <link rel="icon" type="image/svg+xml" href="{{ 'easel.svg' | asset_url }}">
8
+ <title>{{ shop.name | escape }}</title>
9
+ <style>
10
+ :root { --theme-background: {{ settings.background }}; --theme-foreground: {{ settings.foreground }}; }
11
+ </style>
12
+ {% render 'easel-assets' %}
13
+ {{ content_for_header }}
14
+ </head>
15
+ <body>
16
+ <a class="skip-link" href="#MainContent">{{ 'accessibility.skip_to_content' | t }}</a>
17
+ <main id="MainContent" tabindex="-1">{{ content_for_layout }}</main>
18
+ </body>
19
+ </html>
@@ -2,6 +2,37 @@
2
2
  "accessibility": {
3
3
  "skip_to_content": "Skip to content"
4
4
  },
5
+ "article": {
6
+ "back_to_blog": "Back to {{ blog }}"
7
+ },
8
+ "blog": {
9
+ "empty": "No articles found."
10
+ },
11
+ "cart": {
12
+ "title": "Cart",
13
+ "empty": "Your cart is empty.",
14
+ "continue_shopping": "Continue shopping",
15
+ "quantity": "Quantity",
16
+ "subtotal": "Subtotal",
17
+ "update": "Update cart",
18
+ "checkout": "Checkout"
19
+ },
20
+ "collection": {
21
+ "empty": "No products found."
22
+ },
23
+ "collections": {
24
+ "title": "Collections",
25
+ "empty": "No collections found."
26
+ },
27
+ "contact": {
28
+ "title": "Contact us",
29
+ "success": "Thanks for contacting us. We'll get back to you soon.",
30
+ "name": "Name",
31
+ "email": "Email",
32
+ "phone": "Phone number",
33
+ "message": "Message",
34
+ "send": "Send"
35
+ },
5
36
  "general": {
6
37
  "not_found": "Page not found",
7
38
  "return_home": "Return home",
@@ -9,5 +40,27 @@
9
40
  "decrease": "Decrease count",
10
41
  "increase": "Increase count"
11
42
  }
43
+ },
44
+ "gift_card": {
45
+ "disabled": "This gift card is disabled.",
46
+ "expired": "This gift card has expired.",
47
+ "expires_on": "Expires {{ date }}.",
48
+ "code": "Gift card code",
49
+ "continue_shopping": "Continue shopping"
50
+ },
51
+ "password": {
52
+ "password": "Store password",
53
+ "enter": "Enter store"
54
+ },
55
+ "product": {
56
+ "add_to_cart": "Add to cart",
57
+ "sold_out": "Sold out"
58
+ },
59
+ "search": {
60
+ "title": "Search",
61
+ "label": "Search the store",
62
+ "submit": "Search",
63
+ "results": "{{ count }} results for “{{ terms }}”",
64
+ "empty": "No results found."
12
65
  }
13
66
  }
@@ -3,12 +3,42 @@
3
3
  "not_found_name": "404 page"
4
4
  },
5
5
  "sections": {
6
+ "contact_form": {
7
+ "name": "Contact form"
8
+ },
6
9
  "hero": {
7
10
  "name": "Hero",
8
11
  "heading": "Heading",
9
12
  "default_heading": "__THEME_NAME__",
10
13
  "text": "Text",
11
14
  "default_text": "<p>Build the storefront experience here.</p>"
15
+ },
16
+ "main_article": {
17
+ "name": "Article"
18
+ },
19
+ "main_blog": {
20
+ "name": "Blog"
21
+ },
22
+ "main_cart": {
23
+ "name": "Cart"
24
+ },
25
+ "main_collection": {
26
+ "name": "Collection"
27
+ },
28
+ "main_list_collections": {
29
+ "name": "Collection list"
30
+ },
31
+ "main_page": {
32
+ "name": "Page"
33
+ },
34
+ "main_password": {
35
+ "name": "Password page"
36
+ },
37
+ "main_product": {
38
+ "name": "Product"
39
+ },
40
+ "main_search": {
41
+ "name": "Search"
12
42
  }
13
43
  },
14
44
  "settings": {
@@ -0,0 +1,23 @@
1
+ <section class="page contact">
2
+ <h2>{{ 'contact.title' | t }}</h2>
3
+ {% form 'contact' %}
4
+ {% if form.posted_successfully? %}<p>{{ 'contact.success' | t }}</p>{% endif %}
5
+ {{ form.errors | default_errors }}
6
+ <label for="ContactName-{{ section.id }}">{{ 'contact.name' | t }}</label>
7
+ <input id="ContactName-{{ section.id }}" type="text" name="contact[name]" value="{{ form.name | escape }}" autocomplete="name">
8
+ <label for="ContactEmail-{{ section.id }}">{{ 'contact.email' | t }}</label>
9
+ <input id="ContactEmail-{{ section.id }}" type="email" name="contact[email]" value="{{ form.email | escape }}" autocomplete="email" required>
10
+ <label for="ContactPhone-{{ section.id }}">{{ 'contact.phone' | t }}</label>
11
+ <input id="ContactPhone-{{ section.id }}" type="tel" name="contact[phone]" value="{{ form.phone | escape }}" autocomplete="tel">
12
+ <label for="ContactMessage-{{ section.id }}">{{ 'contact.message' | t }}</label>
13
+ <textarea id="ContactMessage-{{ section.id }}" name="contact[body]" rows="8" required>{{ form.body | escape }}</textarea>
14
+ <button type="submit">{{ 'contact.send' | t }}</button>
15
+ {% endform %}
16
+ </section>
17
+
18
+ {% schema %}
19
+ {
20
+ "name": "t:sections.contact_form.name",
21
+ "settings": []
22
+ }
23
+ {% endschema %}
@@ -0,0 +1,15 @@
1
+ <article class="page article">
2
+ <a href="{{ blog.url }}">{{ 'article.back_to_blog' | t: blog: blog.title }}</a>
3
+ <header>
4
+ <h1>{{ article.title | escape }}</h1>
5
+ <p>{{ article.published_at | time_tag: format: 'date' }}</p>
6
+ </header>
7
+ <div class="rte">{{ article.content }}</div>
8
+ </article>
9
+
10
+ {% schema %}
11
+ {
12
+ "name": "t:sections.main_article.name",
13
+ "settings": []
14
+ }
15
+ {% endschema %}
@@ -0,0 +1,28 @@
1
+ <section class="page">
2
+ <h1>{{ blog.title | escape }}</h1>
3
+ {% paginate blog.articles by 12 %}
4
+ {% if blog.articles_count > 0 %}
5
+ <ul class="resource-list" role="list">
6
+ {% for article in blog.articles %}
7
+ <li>
8
+ <article class="card">
9
+ <h2><a href="{{ article.url }}">{{ article.title | escape }}</a></h2>
10
+ <p>{{ article.published_at | time_tag: format: 'date' }}</p>
11
+ {% if article.excerpt != blank %}<div class="rte">{{ article.excerpt }}</div>{% endif %}
12
+ </article>
13
+ </li>
14
+ {% endfor %}
15
+ </ul>
16
+ {{ paginate | default_pagination }}
17
+ {% else %}
18
+ <p>{{ 'blog.empty' | t }}</p>
19
+ {% endif %}
20
+ {% endpaginate %}
21
+ </section>
22
+
23
+ {% schema %}
24
+ {
25
+ "name": "t:sections.main_blog.name",
26
+ "settings": []
27
+ }
28
+ {% endschema %}
@@ -0,0 +1,31 @@
1
+ <section class="page">
2
+ <h1>{{ 'cart.title' | t }}</h1>
3
+ {% if cart.item_count == 0 %}
4
+ <p>{{ 'cart.empty' | t }}</p>
5
+ <a href="{{ routes.all_products_collection_url }}">{{ 'cart.continue_shopping' | t }}</a>
6
+ {% else %}
7
+ {% form 'cart', cart %}
8
+ <ul class="cart-items" role="list">
9
+ {% for item in cart.items %}
10
+ <li class="cart-item">
11
+ <a href="{{ item.url }}">{{ item.product.title | escape }}</a>
12
+ {% unless item.product.has_only_default_variant %}<p>{{ item.variant.title | escape }}</p>{% endunless %}
13
+ <label for="CartQuantity-{{ forloop.index }}">{{ 'cart.quantity' | t }}</label>
14
+ <input id="CartQuantity-{{ forloop.index }}" type="number" name="updates[]" value="{{ item.quantity }}" min="0">
15
+ <p>{{ item.final_line_price | money }}</p>
16
+ </li>
17
+ {% endfor %}
18
+ </ul>
19
+ <p>{{ 'cart.subtotal' | t }}: {{ cart.total_price | money }}</p>
20
+ <button type="submit" name="update">{{ 'cart.update' | t }}</button>
21
+ <button type="submit" name="checkout">{{ 'cart.checkout' | t }}</button>
22
+ {% endform %}
23
+ {% endif %}
24
+ </section>
25
+
26
+ {% schema %}
27
+ {
28
+ "name": "t:sections.main_cart.name",
29
+ "settings": []
30
+ }
31
+ {% endschema %}
@@ -0,0 +1,25 @@
1
+ <section class="page">
2
+ <header>
3
+ <h1>{{ collection.title | escape }}</h1>
4
+ {% if collection.description != blank %}<div class="rte">{{ collection.description }}</div>{% endif %}
5
+ </header>
6
+ {% paginate collection.products by 24 %}
7
+ {% if collection.products_count > 0 %}
8
+ <div class="resource-grid">
9
+ {% for product in collection.products %}
10
+ {% render 'product-card', product: product %}
11
+ {% endfor %}
12
+ </div>
13
+ {{ paginate | default_pagination }}
14
+ {% else %}
15
+ <p>{{ 'collection.empty' | t }}</p>
16
+ {% endif %}
17
+ {% endpaginate %}
18
+ </section>
19
+
20
+ {% schema %}
21
+ {
22
+ "name": "t:sections.main_collection.name",
23
+ "settings": []
24
+ }
25
+ {% endschema %}
@@ -0,0 +1,27 @@
1
+ <section class="page">
2
+ <h1>{{ 'collections.title' | t }}</h1>
3
+ {% paginate collections by 24 %}
4
+ <div class="resource-grid">
5
+ {% for collection in collections %}
6
+ <article class="card">
7
+ <a href="{{ collection.url }}">
8
+ {% if collection.featured_image %}
9
+ {{ collection.featured_image | image_url: width: 720 | image_tag: loading: 'lazy', widths: '240, 360, 540, 720', sizes: '(min-width: 64rem) 20rem, 50vw', alt: collection.featured_image.alt | default: collection.title }}
10
+ {% endif %}
11
+ <h2>{{ collection.title | escape }}</h2>
12
+ </a>
13
+ </article>
14
+ {% else %}
15
+ <p>{{ 'collections.empty' | t }}</p>
16
+ {% endfor %}
17
+ </div>
18
+ {{ paginate | default_pagination }}
19
+ {% endpaginate %}
20
+ </section>
21
+
22
+ {% schema %}
23
+ {
24
+ "name": "t:sections.main_list_collections.name",
25
+ "settings": []
26
+ }
27
+ {% endschema %}
@@ -0,0 +1,11 @@
1
+ <section class="page">
2
+ <h1>{{ page.title | escape }}</h1>
3
+ <div class="rte">{{ page.content }}</div>
4
+ </section>
5
+
6
+ {% schema %}
7
+ {
8
+ "name": "t:sections.main_page.name",
9
+ "settings": []
10
+ }
11
+ {% endschema %}
@@ -0,0 +1,17 @@
1
+ <section class="page password">
2
+ <h1>{{ shop.name | escape }}</h1>
3
+ {% if shop.password_message != blank %}<div class="rte">{{ shop.password_message }}</div>{% endif %}
4
+ {% form 'storefront_password' %}
5
+ {{ form.errors | default_errors }}
6
+ <label for="StorefrontPassword-{{ section.id }}">{{ 'password.password' | t }}</label>
7
+ <input id="StorefrontPassword-{{ section.id }}" type="password" name="password" autocomplete="current-password" required>
8
+ <button type="submit">{{ 'password.enter' | t }}</button>
9
+ {% endform %}
10
+ </section>
11
+
12
+ {% schema %}
13
+ {
14
+ "name": "t:sections.main_password.name",
15
+ "settings": []
16
+ }
17
+ {% endschema %}
@@ -0,0 +1,25 @@
1
+ <section class="page product">
2
+ <div class="product__media">
3
+ {% if product.featured_image %}
4
+ {{ product.featured_image | image_url: width: 1200 | image_tag: widths: '360, 540, 720, 960, 1200', sizes: '(min-width: 48rem) 50vw, 100vw', alt: product.featured_image.alt | default: product.title }}
5
+ {% endif %}
6
+ </div>
7
+ <div class="product__details">
8
+ <h1>{{ product.title | escape }}</h1>
9
+ <p>{{ product.selected_or_first_available_variant.price | money }}</p>
10
+ {% if product.description != blank %}<div class="rte">{{ product.description }}</div>{% endif %}
11
+ {% form 'product', product %}
12
+ <input type="hidden" name="id" value="{{ product.selected_or_first_available_variant.id }}">
13
+ <button type="submit" name="add"{% unless product.selected_or_first_available_variant.available %} disabled{% endunless %}>
14
+ {% if product.selected_or_first_available_variant.available %}{{ 'product.add_to_cart' | t }}{% else %}{{ 'product.sold_out' | t }}{% endif %}
15
+ </button>
16
+ {% endform %}
17
+ </div>
18
+ </section>
19
+
20
+ {% schema %}
21
+ {
22
+ "name": "t:sections.main_product.name",
23
+ "settings": []
24
+ }
25
+ {% endschema %}
@@ -0,0 +1,28 @@
1
+ <section class="page">
2
+ <h1>{{ 'search.title' | t }}</h1>
3
+ <form action="{{ routes.search_url }}" method="get" role="search">
4
+ <label for="SearchInput-{{ section.id }}">{{ 'search.label' | t }}</label>
5
+ <input id="SearchInput-{{ section.id }}" type="search" name="q" value="{{ search.terms | escape }}">
6
+ <button type="submit">{{ 'search.submit' | t }}</button>
7
+ </form>
8
+ {% if search.performed %}
9
+ {% paginate search.results by 24 %}
10
+ <p>{{ 'search.results' | t: count: search.results_count, terms: search.terms }}</p>
11
+ <ul class="resource-list" role="list">
12
+ {% for item in search.results %}
13
+ <li><a href="{{ item.url }}">{{ item.title | escape }}</a></li>
14
+ {% else %}
15
+ <li>{{ 'search.empty' | t }}</li>
16
+ {% endfor %}
17
+ </ul>
18
+ {{ paginate | default_pagination }}
19
+ {% endpaginate %}
20
+ {% endif %}
21
+ </section>
22
+
23
+ {% schema %}
24
+ {
25
+ "name": "t:sections.main_search.name",
26
+ "settings": []
27
+ }
28
+ {% endschema %}
@@ -0,0 +1,15 @@
1
+ {% doc %}
2
+ Renders a product link with its featured image and price.
3
+
4
+ @param {product} product - The product to render.
5
+ {% enddoc %}
6
+
7
+ <article class="card">
8
+ <a href="{{ product.url }}">
9
+ {% if product.featured_image %}
10
+ {{ product.featured_image | image_url: width: 720 | image_tag: loading: 'lazy', widths: '240, 360, 540, 720', sizes: '(min-width: 64rem) 20rem, 50vw', alt: product.featured_image.alt | default: product.title }}
11
+ {% endif %}
12
+ <h2>{{ product.title | escape }}</h2>
13
+ </a>
14
+ <p>{{ product.price | money }}</p>
15
+ </article>
@@ -54,8 +54,81 @@ output {
54
54
  }
55
55
 
56
56
  .hero,
57
- .not-found {
57
+ .not-found,
58
+ .page {
58
59
  margin-inline: auto;
59
60
  max-width: 64rem;
60
61
  padding: 4rem 1.5rem;
61
62
  }
63
+
64
+ .page > :first-child,
65
+ .card > :first-child {
66
+ margin-block-start: 0;
67
+ }
68
+
69
+ .page > :last-child,
70
+ .card > :last-child {
71
+ margin-block-end: 0;
72
+ }
73
+
74
+ .resource-grid,
75
+ .resource-list,
76
+ .cart-items {
77
+ display: grid;
78
+ grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
79
+ gap: 1.5rem;
80
+ padding: 0;
81
+ list-style: none;
82
+ }
83
+
84
+ .card img,
85
+ .product__media img {
86
+ display: block;
87
+ width: 100%;
88
+ height: auto;
89
+ }
90
+
91
+ .product {
92
+ display: grid;
93
+ gap: 2rem;
94
+ }
95
+
96
+ form {
97
+ display: grid;
98
+ gap: 0.75rem;
99
+ max-width: 40rem;
100
+ }
101
+
102
+ input:not([type='hidden']),
103
+ textarea {
104
+ width: 100%;
105
+ border: 1px solid currentColor;
106
+ background: transparent;
107
+ color: inherit;
108
+ font: inherit;
109
+ padding: 0.75rem;
110
+ }
111
+
112
+ .rte,
113
+ .rte img,
114
+ .rte iframe {
115
+ max-width: 100%;
116
+ }
117
+
118
+ .gift-card__balance {
119
+ font-size: 2rem;
120
+ }
121
+
122
+ .gift-card__code {
123
+ display: block;
124
+ width: fit-content;
125
+ margin-block: 1rem;
126
+ border: 1px solid currentColor;
127
+ padding: 0.75rem;
128
+ }
129
+
130
+ @media (min-width: 48rem) {
131
+ .product {
132
+ grid-template-columns: 1fr 1fr;
133
+ }
134
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "sections": {
3
+ "main": {
4
+ "type": "main-article"
5
+ }
6
+ },
7
+ "order": ["main"]
8
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "sections": {
3
+ "main": {
4
+ "type": "main-blog"
5
+ }
6
+ },
7
+ "order": ["main"]
8
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "sections": {
3
+ "main": {
4
+ "type": "main-cart"
5
+ }
6
+ },
7
+ "order": ["main"]
8
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "sections": {
3
+ "main": {
4
+ "type": "main-collection"
5
+ }
6
+ },
7
+ "order": ["main"]
8
+ }
@@ -0,0 +1,35 @@
1
+ {% layout none %}
2
+
3
+ <!doctype html>
4
+ <html lang="{{ request.locale.iso_code }}">
5
+ <head>
6
+ <meta charset="utf-8">
7
+ <meta name="viewport" content="width=device-width,initial-scale=1">
8
+ <link rel="canonical" href="{{ canonical_url | escape }}">
9
+ <link rel="icon" type="image/svg+xml" href="{{ 'easel.svg' | asset_url }}">
10
+ <title>{{ gift_card.product.title | default: shop.name | escape }}</title>
11
+ <style>
12
+ :root { --theme-background: {{ settings.background }}; --theme-foreground: {{ settings.foreground }}; }
13
+ </style>
14
+ {% render 'easel-assets' %}
15
+ {{ content_for_header }}
16
+ </head>
17
+ <body>
18
+ <main class="page gift-card">
19
+ <h1>{{ gift_card.product.title | default: shop.name | escape }}</h1>
20
+ <p class="gift-card__balance">{{ gift_card.balance | money_with_currency }}</p>
21
+ {% unless gift_card.enabled %}
22
+ <p>{{ 'gift_card.disabled' | t }}</p>
23
+ {% endunless %}
24
+ {% if gift_card.expired %}
25
+ <p>{{ 'gift_card.expired' | t }}</p>
26
+ {% elsif gift_card.expires_on %}
27
+ {% assign expiration_date = gift_card.expires_on | date: format: 'date' %}
28
+ <p>{{ 'gift_card.expires_on' | t: date: expiration_date }}</p>
29
+ {% endif %}
30
+ <p>{{ 'gift_card.code' | t }}</p>
31
+ <code class="gift-card__code">{{ gift_card.code | format_code }}</code>
32
+ <a href="{{ shop.url }}">{{ 'gift_card.continue_shopping' | t }}</a>
33
+ </main>
34
+ </body>
35
+ </html>