domma-cms 0.84.0 → 0.85.0
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.
- package/README.md +4 -4
- package/admin/dist/domma/domma-tools.css +3 -3
- package/admin/dist/domma/domma-tools.min.js +4 -4
- package/admin/js/lib/themes.js +1 -1
- package/package.json +2 -2
- package/plugins/free-tier.lock.json +53 -5
- package/plugins/security/plugin.js +10 -0
- package/plugins/security/plugin.json +1 -1
- package/plugins/security/server/facts.js +3 -1
- package/plugins/shopping-cart/CLAUDE.md +93 -0
- package/plugins/shopping-cart/admin/css/index.css +224 -0
- package/plugins/shopping-cart/admin/lib/kit.js +190 -0
- package/plugins/shopping-cart/admin/templates/shop.html +1 -0
- package/plugins/shopping-cart/admin/views/orders.js +365 -0
- package/plugins/shopping-cart/admin/views/overview.js +214 -0
- package/plugins/shopping-cart/admin/views/products.js +547 -0
- package/plugins/shopping-cart/admin/views/settings.js +247 -0
- package/plugins/shopping-cart/admin/views/shop.js +130 -0
- package/plugins/shopping-cart/collections/shop-orders.json +20 -0
- package/plugins/shopping-cart/collections/shop-products.json +24 -0
- package/plugins/shopping-cart/config.js +52 -0
- package/plugins/shopping-cart/inject/body.html +2 -0
- package/plugins/shopping-cart/lib/extensions.js +96 -0
- package/plugins/shopping-cart/lib/payments.js +281 -0
- package/plugins/shopping-cart/lib/pricing.js +232 -0
- package/plugins/shopping-cart/lib/render.js +282 -0
- package/plugins/shopping-cart/lib/samples.js +34 -0
- package/plugins/shopping-cart/lib/shop.js +436 -0
- package/plugins/shopping-cart/lib/summary.js +127 -0
- package/plugins/shopping-cart/plugin.js +559 -0
- package/plugins/shopping-cart/plugin.json +52 -0
- package/plugins/shopping-cart/plugin.public.js +144 -0
- package/plugins/shopping-cart/public/cart.js +715 -0
- package/plugins/shopping-cart/public/lib/money.js +105 -0
- package/plugins/shopping-cart/public/lib/orders.js +122 -0
- package/plugins/shopping-cart/public/lib/products.js +235 -0
- package/plugins/shopping-cart/public/samples/candle-2.svg +16 -0
- package/plugins/shopping-cart/public/samples/candle.svg +16 -0
- package/plugins/shopping-cart/public/samples/mug-2.svg +14 -0
- package/plugins/shopping-cart/public/samples/mug.svg +14 -0
- package/plugins/shopping-cart/public/samples/notebook-2.svg +15 -0
- package/plugins/shopping-cart/public/samples/notebook.svg +15 -0
- package/plugins/shopping-cart/public/samples/print-2.svg +14 -0
- package/plugins/shopping-cart/public/samples/print.svg +14 -0
- package/plugins/shopping-cart/public/samples/tee-2.svg +12 -0
- package/plugins/shopping-cart/public/samples/tee.svg +12 -0
- package/plugins/shopping-cart/public/samples/tote-2.svg +13 -0
- package/plugins/shopping-cart/public/samples/tote.svg +13 -0
- package/plugins/shopping-cart/public/shop.css +415 -0
- package/plugins/shopping-cart/templates/shop.html +4 -0
- package/plugins/shopping-cart/tests/api.test.js +144 -0
- package/plugins/shopping-cart/tests/pure.test.js +210 -0
- package/scripts/setup.js +1 -0
package/admin/js/lib/themes.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const THEMES=[{id:"charcoal-dark",label:"Charcoal",dark:!0,primary:"#5b8cff",bg:"#1a1d23"},{id:"charcoal-light",label:"Charcoal",dark:!1,primary:"#4a7aff",bg:"#f4f5f7"},{id:"ocean-dark",label:"Ocean",dark:!0,primary:"#00b4d8",bg:"#0d1b2a"},{id:"ocean-light",label:"Ocean",dark:!1,primary:"#0096c7",bg:"#e8f4f8"},{id:"forest-dark",label:"Forest",dark:!0,primary:"#52b788",bg:"#1a231e"},{id:"forest-light",label:"Forest",dark:!1,primary:"#40916c",bg:"#f0f7f4"},{id:"sunset-dark",label:"Sunset",dark:!0,primary:"#ff6b6b",bg:"#1f1a1a"},{id:"sunset-light",label:"Sunset",dark:!1,primary:"#e05555",bg:"#fff0f0"},{id:"royal-dark",label:"Royal",dark:!0,primary:"#9b59b6",bg:"#1a1525"},{id:"royal-light",label:"Royal",dark:!1,primary:"#8e44ad",bg:"#f5f0fa"},{id:"lemon-dark",label:"Lemon",dark:!0,primary:"#f1c40f",bg:"#1a1a10"},{id:"lemon-light",label:"Lemon",dark:!1,primary:"#d4ac0d",bg:"#fffef0"},{id:"silver-dark",label:"Silver",dark:!0,primary:"#95a5a6",bg:"#1c1e20"},{id:"silver-light",label:"Silver",dark:!1,primary:"#7f8c8d",bg:"#f2f3f4"},{id:"grayve-dark",label:"Grayve",dark:!0,primary:"#00bcd4",bg:"#1a1e21"},{id:"grayve-light",label:"Grayve",dark:!1,primary:"#00838f",bg:"#ffffff"},{id:"mint-dark",label:"Mint",dark:!0,primary:"#00c896",bg:"#0f1f1a"},{id:"mint-light",label:"Mint",dark:!1,primary:"#00a878",bg:"#f0faf6"},{id:"unicorn-dark",label:"Unicorn",dark:!0,primary:"#e040fb",bg:"#1a0a1e"},{id:"unicorn-light",label:"Unicorn",dark:!1,primary:"#ba68c8",bg:"#fdf5ff"},{id:"dreamy-dark",label:"Dreamy",dark:!0,primary:"#7986cb",bg:"#15152a"},{id:"dreamy-light",label:"Dreamy",dark:!1,primary:"#5c6bc0",bg:"#f3f4fd"},{id:"christmas-dark",label:"Christmas",dark:!0,primary:"#e53935",bg:"#0a1a0a"},{id:"christmas-light",label:"Christmas",dark:!1,primary:"#c62828",bg:"#f5fff5"},{id:"wedding-dark",label:"Wedding",dark:!0,primary:"#ec407a",bg:"#1a0e14"},{id:"wedding-light",label:"Wedding",dark:!1,primary:"#e91e63",bg:"#fff5f8"},{id:"admin-sharp-indigo",label:"Admin \xB7 Sharp",name:"Indigo",admin:!0,dark:!1,primary:"#5b63a8",bg:"#eef1f6"},{id:"admin-sharp-steel",label:"Admin \xB7 Sharp",name:"Steel",admin:!0,dark:!1,primary:"#3b76bc",bg:"#eef1f6"},{id:"admin-sharp-teal",label:"Admin \xB7 Sharp",name:"Teal",admin:!0,dark:!1,primary:"#2a8178",bg:"#eef1f6"},{id:"admin-smooth-indigo",label:"Admin \xB7 Smooth",name:"Indigo",admin:!0,dark:!0,primary:"#5b63a8",bg:"#586170"},{id:"admin-smooth-steel",label:"Admin \xB7 Smooth",name:"Steel",admin:!0,dark:!0,primary:"#3b76bc",bg:"#586170"},{id:"admin-smooth-teal",label:"Admin \xB7 Smooth",name:"Teal",admin:!0,dark:!0,primary:"#2a8178",bg:"#586170"}];export function paletteOrder(
|
|
1
|
+
export const THEMES=[{id:"charcoal-dark",label:"Charcoal",dark:!0,primary:"#5b8cff",bg:"#1a1d23"},{id:"charcoal-light",label:"Charcoal",dark:!1,primary:"#4a7aff",bg:"#f4f5f7"},{id:"ocean-dark",label:"Ocean",dark:!0,primary:"#00b4d8",bg:"#0d1b2a"},{id:"ocean-light",label:"Ocean",dark:!1,primary:"#0096c7",bg:"#e8f4f8"},{id:"forest-dark",label:"Forest",dark:!0,primary:"#52b788",bg:"#1a231e"},{id:"forest-light",label:"Forest",dark:!1,primary:"#40916c",bg:"#f0f7f4"},{id:"sunset-dark",label:"Sunset",dark:!0,primary:"#ff6b6b",bg:"#1f1a1a"},{id:"sunset-light",label:"Sunset",dark:!1,primary:"#e05555",bg:"#fff0f0"},{id:"royal-dark",label:"Royal",dark:!0,primary:"#9b59b6",bg:"#1a1525"},{id:"royal-light",label:"Royal",dark:!1,primary:"#8e44ad",bg:"#f5f0fa"},{id:"lemon-dark",label:"Lemon",dark:!0,primary:"#f1c40f",bg:"#1a1a10"},{id:"lemon-light",label:"Lemon",dark:!1,primary:"#d4ac0d",bg:"#fffef0"},{id:"silver-dark",label:"Silver",dark:!0,primary:"#95a5a6",bg:"#1c1e20"},{id:"silver-light",label:"Silver",dark:!1,primary:"#7f8c8d",bg:"#f2f3f4"},{id:"grayve-dark",label:"Grayve",dark:!0,primary:"#00bcd4",bg:"#1a1e21"},{id:"grayve-light",label:"Grayve",dark:!1,primary:"#00838f",bg:"#ffffff"},{id:"mint-dark",label:"Mint",dark:!0,primary:"#00c896",bg:"#0f1f1a"},{id:"mint-light",label:"Mint",dark:!1,primary:"#00a878",bg:"#f0faf6"},{id:"unicorn-dark",label:"Unicorn",dark:!0,primary:"#e040fb",bg:"#1a0a1e"},{id:"unicorn-light",label:"Unicorn",dark:!1,primary:"#ba68c8",bg:"#fdf5ff"},{id:"dreamy-dark",label:"Dreamy",dark:!0,primary:"#7986cb",bg:"#15152a"},{id:"dreamy-light",label:"Dreamy",dark:!1,primary:"#5c6bc0",bg:"#f3f4fd"},{id:"christmas-dark",label:"Christmas",dark:!0,primary:"#e53935",bg:"#0a1a0a"},{id:"christmas-light",label:"Christmas",dark:!1,primary:"#c62828",bg:"#f5fff5"},{id:"wedding-dark",label:"Wedding",dark:!0,primary:"#ec407a",bg:"#1a0e14"},{id:"wedding-light",label:"Wedding",dark:!1,primary:"#e91e63",bg:"#fff5f8"},{id:"corporate-dark",label:"Corporate",dark:!0,primary:"#2563eb",bg:"#0b0f19"},{id:"corporate-light",label:"Corporate",dark:!1,primary:"#1e40af",bg:"#f8fafc"},{id:"admin-sharp-indigo",label:"Admin \xB7 Sharp",name:"Indigo",admin:!0,dark:!1,primary:"#5b63a8",bg:"#eef1f6"},{id:"admin-sharp-steel",label:"Admin \xB7 Sharp",name:"Steel",admin:!0,dark:!1,primary:"#3b76bc",bg:"#eef1f6"},{id:"admin-sharp-teal",label:"Admin \xB7 Sharp",name:"Teal",admin:!0,dark:!1,primary:"#2a8178",bg:"#eef1f6"},{id:"admin-slate-indigo",label:"Admin \xB7 Slate",name:"Indigo",admin:!0,dark:!0,primary:"#5b63a8",bg:"#252c37"},{id:"admin-slate-steel",label:"Admin \xB7 Slate",name:"Steel",admin:!0,dark:!0,primary:"#3b76bc",bg:"#252c37"},{id:"admin-slate-teal",label:"Admin \xB7 Slate",name:"Teal",admin:!0,dark:!0,primary:"#2a8178",bg:"#252c37"},{id:"admin-smooth-indigo",label:"Admin \xB7 Smooth",name:"Indigo",admin:!0,dark:!0,primary:"#5b63a8",bg:"#586170"},{id:"admin-smooth-steel",label:"Admin \xB7 Smooth",name:"Steel",admin:!0,dark:!0,primary:"#3b76bc",bg:"#586170"},{id:"admin-smooth-teal",label:"Admin \xB7 Smooth",name:"Teal",admin:!0,dark:!0,primary:"#2a8178",bg:"#586170"}];export function paletteOrder(e){const d=[...new Set(e.map(a=>a.label))],r=a=>a.toLowerCase().startsWith("admin")?1:0;return d.sort((a,l)=>r(a)-r(l)||a.localeCompare(l))}export function themesInDisplayOrder(e=THEMES){const d=paletteOrder(e);return[...e].sort((r,a)=>d.indexOf(r.label)-d.indexOf(a.label)||(r.dark===a.dark?0:r.dark?-1:1))}export function populateThemeSelect(e,{includeDefault:d=!1,defaultLabel:r="Use site default",includeAdmin:a=!1}={}){for(;e.firstChild;)e.removeChild(e.firstChild);if(d){const i=document.createElement("option");i.value="",i.textContent=r,e.appendChild(i)}const l=THEMES.filter(i=>a||!i.admin);for(const i of paletteOrder(l)){const f=document.createElement("optgroup");f.label=i;for(const n of l.filter(t=>t.label===i)){const t=document.createElement("option");t.value=n.id,t.textContent=n.name||`${n.label} ${n.dark?"Dark":"Light"}`,f.appendChild(t)}e.appendChild(f)}}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "domma-cms",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.85.0",
|
|
4
4
|
"description": "File-based CMS powered by Domma and Fastify. Run npx domma-cms my-site to create a new project.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "server/server.js",
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
"@fastify/rate-limit": "^10.3.0",
|
|
84
84
|
"@fastify/static": "9.1.1",
|
|
85
85
|
"bcryptjs": "^3.0.3",
|
|
86
|
-
"domma-js": "0.44.
|
|
86
|
+
"domma-js": "0.44.6",
|
|
87
87
|
"dotenv": "^17.2.3",
|
|
88
88
|
"fastify": "5.8.5",
|
|
89
89
|
"gray-matter": "^4.0.3",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"note": "Written by `make sync-free-tier` in dcms-marketplace. Do not edit these plugins here - edit them there and sync.",
|
|
3
|
-
"source": "dcms-marketplace@
|
|
3
|
+
"source": "dcms-marketplace@9bdfedf",
|
|
4
4
|
"plugins": {
|
|
5
5
|
"blog": {
|
|
6
6
|
"version": "1.6.3",
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
}
|
|
78
78
|
},
|
|
79
79
|
"security": {
|
|
80
|
-
"version": "1.0.
|
|
80
|
+
"version": "1.0.3",
|
|
81
81
|
"files": {
|
|
82
82
|
"CLAUDE.md": "a9b7574236740de80513a535fb761127fa306f2177a5dda6e761213b1ad5b287",
|
|
83
83
|
"admin/lib/blocks.js": "5ef346b7de751518f8beb9212e3b89b270ae236560ae3d8a1f40d129c50918ff",
|
|
@@ -91,14 +91,62 @@
|
|
|
91
91
|
"admin/views/security.js": "dcd38e07c5e83bbe013f5d764c52edef76d28ed4c3fe1cba8c5a59ce1b93e962",
|
|
92
92
|
"bin/unlock.js": "5fcc3c0ef3ba3c424a366aa62896f51abad578183dfde283aa838a86549ca785",
|
|
93
93
|
"config.js": "e6a00f7816637687d6a68ebbac65a71489246a926c28523c2a78d859e75b7ae0",
|
|
94
|
-
"plugin.js": "
|
|
95
|
-
"plugin.json": "
|
|
94
|
+
"plugin.js": "3051c886f77b1b1e7f45873fbdcd78a5ba2d72caea97bde1235e84a298ab6114",
|
|
95
|
+
"plugin.json": "bdcc88f0f5dec2d707806687ca80e1697255af49b4f30f43650e3824775deef8",
|
|
96
96
|
"server/common-passwords.NOTICE": "3b148a49a569bfa24feaa7879db602da8f0607fa61ab6c52b7a62391fc7c009a",
|
|
97
97
|
"server/common-passwords.txt": "68782d6a4a19a4768d5f15dd66bd534e7a33055cc755411e33f16d18c50fdcce",
|
|
98
|
-
"server/facts.js": "
|
|
98
|
+
"server/facts.js": "25538a8d1cfe012688fceb8ec0d0dd7b43ac9e168430723d6fb731183701fb46",
|
|
99
99
|
"server/state.js": "1319b322e94a4aec176948990b2d674b28dfece761eef4d5685df474037bfc4a",
|
|
100
100
|
"tests/security.test.js": "951636a0e42ac8dbf78b69ca7e7e2adcd4e29d94a51998e738c3afa742c00866"
|
|
101
101
|
}
|
|
102
|
+
},
|
|
103
|
+
"shopping-cart": {
|
|
104
|
+
"version": "1.0.0",
|
|
105
|
+
"files": {
|
|
106
|
+
"CLAUDE.md": "0f89bab23796eba425e26792c952eb00eddb09fb2c74a4a7a1fb3722437dae0b",
|
|
107
|
+
"admin/css/index.css": "a6bab1d0072e364ee1d94c2d7d7b99c3660ba91c5eba537e269a579f5b17f924",
|
|
108
|
+
"admin/lib/kit.js": "3e721a53a97f136833975f99d9f985fcec4018b328b4d355267fe11702234fa0",
|
|
109
|
+
"admin/templates/shop.html": "c5bb03f1f7cbabfbd8202f3533783eb1848103755b1ba25fdcf1fd8e9478cb8e",
|
|
110
|
+
"admin/views/orders.js": "8255b75a4a0eeccec7cf76982a5f64469ab7a2ad5f742a54afe55bca61ace713",
|
|
111
|
+
"admin/views/overview.js": "3901dfb9527fed0689bb31098488136471ff081b9fe28979339f39b2d53e3bd3",
|
|
112
|
+
"admin/views/products.js": "4947de61aa57da59fd0e819ac0f5380bbdee6b73855bd91d3872445fdd6c3e2d",
|
|
113
|
+
"admin/views/settings.js": "28cb0b5e8d45e3723ffe77e46299bfc13fbad02a2ae8103843c6894dc232387a",
|
|
114
|
+
"admin/views/shop.js": "a53a1793251b962a87c5d01685f934b6476bcfcdd51e3dea3a319eb8c03a4137",
|
|
115
|
+
"collections/shop-orders.json": "0bca8eef2d5f9108651f73d273480265c167dc983aa115ce59613d13015ca255",
|
|
116
|
+
"collections/shop-products.json": "4d4613ecec7b8a5a0744c4f0a57b17a4429b461e060206a4bbdea5ad7c8c75dc",
|
|
117
|
+
"config.js": "dcb91db2839c1ab9e40bb1811397024fad9e37e3753d17e2baa2b9b31d4a7d7c",
|
|
118
|
+
"inject/body.html": "de20f92c83229545d20e5186fd37ef0046a8820881bff34e7df5391cd7a2c75d",
|
|
119
|
+
"lib/extensions.js": "2cba90efef5b437ace557c7265fe7363ac7923d155681087a63e31e1168f6601",
|
|
120
|
+
"lib/payments.js": "84ad14813688de1f38448b4935180a786cda8af2f7f4b8890a8f1d426982d1aa",
|
|
121
|
+
"lib/pricing.js": "94953075062e9b36cdbd6ea149662d013d97ee12359a9ff7459669beff99ab99",
|
|
122
|
+
"lib/render.js": "3eacb4f7bf62376873e6fe59e2fe55e72a97fda8ed6bff73b93791f568cceffd",
|
|
123
|
+
"lib/samples.js": "912b3d5bd543dffa5f870caf3c2b873f642fd55e3ebf3f97e1b31eeaaac77d3b",
|
|
124
|
+
"lib/shop.js": "36e1cea342e23f1a53b9fac63b993f860afc1bcb74b25bef9f7bf8eaf07d2d85",
|
|
125
|
+
"lib/summary.js": "a510386e28eb19d9617f488551d1057819ae9f76fc85c744f2e5dbce492607fe",
|
|
126
|
+
"plugin.js": "594c2174e36566f90ac1e9ff11cb76656ad111dc392dc8e9556d731b7dd0a41a",
|
|
127
|
+
"plugin.json": "d5a3aeebb422cde97ac30605e8706acfb39e69601e7e6ae5d626c3744d49093c",
|
|
128
|
+
"plugin.public.js": "176383dfa7af913c433e0397584c46a67a21ab3307b80a08f0aaf50da2634f9e",
|
|
129
|
+
"public/cart.js": "fcc706773a36e4bfd02ea235bcdad629137e996218e0a0e23892e33c689f2a6e",
|
|
130
|
+
"public/lib/money.js": "d22b2b6f80dde6f7887b69e24188c05ea93c02217aba639b5331aa3bd616f4fc",
|
|
131
|
+
"public/lib/orders.js": "09478e20d238adc8e387b3d9efcb13c441898fab18387ce6a1a8a92304ac4c5b",
|
|
132
|
+
"public/lib/products.js": "c698dcb509dfc09a135e4039ec085dda8a3606e7f9f9586ea0fcf068f9147438",
|
|
133
|
+
"public/samples/candle-2.svg": "aca5691b9168467caf6feb7622ab154ee92504be8b887ac6c818db22476e7bed",
|
|
134
|
+
"public/samples/candle.svg": "ef75e95a53ce13e88928004c6f1290c45a1094765e2ba741ae1fc55d404b881f",
|
|
135
|
+
"public/samples/mug-2.svg": "5f37d23e90b7e79e42d38009a56b3ea26adae03586830a0cd418565491bd4532",
|
|
136
|
+
"public/samples/mug.svg": "e29e268fae300b95e38021443aa6df57e6a4dcb14c4058e5ef7106eb9bc1be6c",
|
|
137
|
+
"public/samples/notebook-2.svg": "21fbeaaab182485b3b9dde4e3b9d83cf3f9cadd5825225632cda14f0453c9538",
|
|
138
|
+
"public/samples/notebook.svg": "ff659345f465324199858f992e779aa7e0c4308e3cc39f15f7d5a0d4618b21d0",
|
|
139
|
+
"public/samples/print-2.svg": "09385dc66f5902caa399c6a4cc11ae320a779cbd355b7965d573483af9ccb818",
|
|
140
|
+
"public/samples/print.svg": "47e0f0960231b81b2d03f205c9b86e03f7c22011a2f243cfa915d08a73de377d",
|
|
141
|
+
"public/samples/tee-2.svg": "aaec073d128b3ea90e58225dd34daca8cbc39aa0f3112d2bc65119284718b5e4",
|
|
142
|
+
"public/samples/tee.svg": "287a82d7fc00acb1543b3209927ae20dcbe3d4b2a2b3de03574ab0c463385f3c",
|
|
143
|
+
"public/samples/tote-2.svg": "9c7f41cc57866e71973e413a6c87f46be48f7488d3490ceea58e24559c334813",
|
|
144
|
+
"public/samples/tote.svg": "01de817a32c8d3d8ea47ff491049f439ca3e4d4fce943dbdc62a4187139e016c",
|
|
145
|
+
"public/shop.css": "277904382e491f0ecc76aad916126970b87e161ef06ac6deeff193358b4e4c31",
|
|
146
|
+
"templates/shop.html": "4649e57d23458833a157799dc80141824eb0f658d96734bd1859c68e0ba9a8b9",
|
|
147
|
+
"tests/api.test.js": "a806df62981574a98d435eefc98143b2b03071b5f58a4e7db3cab93850c0e8ae",
|
|
148
|
+
"tests/pure.test.js": "dbe35ed7e94cea2f96d42d5abe7d41173393180ff5764c0539799329aa61bfa0"
|
|
149
|
+
}
|
|
102
150
|
}
|
|
103
151
|
}
|
|
104
152
|
}
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
* GET|PUT /settings
|
|
20
20
|
*/
|
|
21
21
|
import fs from 'node:fs';
|
|
22
|
+
import os from 'node:os';
|
|
22
23
|
import path from 'node:path';
|
|
23
24
|
import {fileURLToPath} from 'node:url';
|
|
24
25
|
import fp from 'fastify-plugin';
|
|
@@ -40,6 +41,14 @@ const NAME = 'security';
|
|
|
40
41
|
/** A call from this machine itself, not a relayed visitor. */
|
|
41
42
|
const genuinelyLocal = (request) => isLoopbackIp(request.raw?.socket?.remoteAddress) && !request.headers['x-forwarded-for'];
|
|
42
43
|
|
|
44
|
+
/**
|
|
45
|
+
* This machine's own addresses (public ones included). A request from one is the
|
|
46
|
+
* server calling itself through the public name - a site's health check, Site
|
|
47
|
+
* Manager's certificate checks - and is never blocked or counted as probing.
|
|
48
|
+
*/
|
|
49
|
+
const OWN = new Set(Object.values(os.networkInterfaces()).flat().filter(Boolean).map(i => plainIp(i.address)));
|
|
50
|
+
const isOwnAddress = (ip) => OWN.has(plainIp(ip));
|
|
51
|
+
|
|
43
52
|
let commonPasswords = null;
|
|
44
53
|
function common() {
|
|
45
54
|
if (commonPasswords) return commonPasswords;
|
|
@@ -72,6 +81,7 @@ async function security(fastify, options = {}) {
|
|
|
72
81
|
try {
|
|
73
82
|
if (genuinelyLocal(request)) return;
|
|
74
83
|
const ip = plainIp(request.ip);
|
|
84
|
+
if (isOwnAddress(ip)) return;
|
|
75
85
|
const now = Date.now();
|
|
76
86
|
const s = state.get();
|
|
77
87
|
const blocked = blockedBy(s, ip, now);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "security",
|
|
3
3
|
"displayName": "Security",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.3",
|
|
5
5
|
"tier": "free",
|
|
6
6
|
"description": "Keeps a Domma CMS site safe: a health check of what is weak and how to fix it, sign-in lockout after repeated wrong passwords, password rules, a log of every sign-in, and blocking of the scanners that probe every site for WordPress, .env files and the like.",
|
|
7
7
|
"author": "Domma CMS",
|
|
@@ -112,7 +112,9 @@ export async function gatherFacts(request, {settings, checks, twoFactor = false}
|
|
|
112
112
|
const [eng, cert, head, probes] = await Promise.all([
|
|
113
113
|
engine(),
|
|
114
114
|
isLocalHost(host) ? Promise.resolve(null) : certificate(host),
|
|
115
|
-
|
|
115
|
+
// Headers and HTTPS are about the PUBLIC site: without a public name to ask (a check run by the site
|
|
116
|
+
// itself, or on localhost) they are left unanswered rather than judged on the local port.
|
|
117
|
+
isLocalHost(host) ? Promise.resolve(null) : headers(origin),
|
|
116
118
|
probeFiles(port)
|
|
117
119
|
]);
|
|
118
120
|
const smtp = getConfig('site')?.smtp || {};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# shopping-cart - AI Assistant Guide
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Sell from a Domma CMS site. 1.0.0 (2026-09-26): products with options (size, colour) each with its own price,
|
|
6
|
+
SKU and stock; a storefront at a configurable address (`/shop`) with category chips, search and sort; product
|
|
7
|
+
pages with a gallery; a basket kept in the shopper's browser that follows them round the site (floating
|
|
8
|
+
button + drawer on every page); one-page checkout; Stripe (hosted Checkout), PayPal (Orders v2) or payment
|
|
9
|
+
offline; a receipt page that waits for the payment to be confirmed; orders in the admin with fulfil (tracking),
|
|
10
|
+
refund (through the provider), cancel, packing slips; an Overview with takings and what needs doing; a sidebar
|
|
11
|
+
badge for paid orders to fulfil; sample products with bundled pictures.
|
|
12
|
+
|
|
13
|
+
`tier: free`. Shopping Cart Pro (plugins/shopping-cart-pro) is an ADD-ON that plugs into this one - it does not
|
|
14
|
+
supersede it (see Extensions).
|
|
15
|
+
|
|
16
|
+
## The one rule about money
|
|
17
|
+
|
|
18
|
+
Every amount is an integer in MINOR units (pence) - products, orders, settings (`shippingFlat: 395` = £3.95).
|
|
19
|
+
Only the edges convert: the admin's inputs (`kit.moneyIn/moneyOut`), display (`formatMoney`), PayPal's decimal
|
|
20
|
+
string (`fromMinor`). The browser never sends a price: `POST /quote` and `POST /checkout` take product ids,
|
|
21
|
+
option ids and quantities, and `lib/pricing.js` `priceBasket` is the ONE place a total is worked out. Checkout
|
|
22
|
+
refuses (409, with the new quote) when the basket changed (sold out, fewer left, a code refused) until the
|
|
23
|
+
shopper resubmits with `accept`, and when `expectTotal` differs from what it would charge.
|
|
24
|
+
|
|
25
|
+
## Files
|
|
26
|
+
|
|
27
|
+
- `plugin.json`, `config.js` (every setting, documented), `plugin.js` (API, shortcodes, payments' return routes)
|
|
28
|
+
- `plugin.public.js` - storefront pages from ONE onRequest hook on the LIVE shop path (like the Blog), so
|
|
29
|
+
moving the shop needs no restart. `matchShopPath` is exported and tested.
|
|
30
|
+
- `public/lib/money.js`, `products.js`, `orders.js` - PURE, shared by server, admin and storefront (public/ is
|
|
31
|
+
served; lib/ is not). Product cleaning, stock (`available`, `totalStock`), order statuses and moves (`NEXT`).
|
|
32
|
+
- `lib/pricing.js` - the basket's price: lines (capped at stock), then stages discount -> shipping -> tax.
|
|
33
|
+
- `lib/extensions.js` - the registry extensions register into (globalThis, Symbol.for('domma.shopping-cart')).
|
|
34
|
+
- `lib/shop.js` - collections I/O, live settings (masking, cleaning), order numbers, `markPaid` / `moveOrder` /
|
|
35
|
+
refunds with stock, all order writes serialised by `withLock`. `shelfNow`/`settingsNow` for the shortcodes.
|
|
36
|
+
- `lib/payments.js` - Stripe and PayPal over fetch (no SDK). `stripeVerify` checks webhook signatures.
|
|
37
|
+
- `lib/render.js` - the storefront's HTML (server-rendered; everything escaped; descriptions via parseMarkdown).
|
|
38
|
+
- `lib/summary.js` - Overview figures and the badge. `lib/samples.js` + `public/samples/*.svg` - sample products.
|
|
39
|
+
- `public/cart.js`, `public/shop.css` - the browser half, injected on every page (`inject/body.html`).
|
|
40
|
+
- `templates/shop.html` - the fragment every storefront page renders in (`renderBlogPage`).
|
|
41
|
+
- `admin/views/shop.js` - the Tool: tab host (Overview, Orders, Products + extension tabs), banner cog/visit/+.
|
|
42
|
+
`overview.js`, `orders.js`, `products.js`, `settings.js` (the cog's slideover). `admin/lib/kit.js` - shared.
|
|
43
|
+
- `tests/pure.test.js` (money, products, pricing, registry, payments, paths, settings, summary),
|
|
44
|
+
`tests/api.test.js` (a whole sale against the real collection service; restores the checkout).
|
|
45
|
+
|
|
46
|
+
## Data
|
|
47
|
+
|
|
48
|
+
Collections `shop-products` and `shop-orders` (made at every boot if missing; API access all off - orders hold
|
|
49
|
+
shoppers' details). Settings: `config/plugins.json` `shopping-cart.settings`; the order counter is plugin state
|
|
50
|
+
`shopping-cart.orderSeq` (never below the highest order number, starting 1001).
|
|
51
|
+
|
|
52
|
+
Order statuses: pending (checkout started) -> awaiting (offline) -> paid -> fulfilled; cancelled; refunded.
|
|
53
|
+
Stock is taken when an order becomes PAID (`stockTaken`), never at checkout, and can go back on a refund.
|
|
54
|
+
A paid or refunded order cannot be deleted. Each order has a random `token`: the receipt URL is
|
|
55
|
+
`<shop>/order/<id>?t=<token>`; without the token it is a 404.
|
|
56
|
+
|
|
57
|
+
## Payments
|
|
58
|
+
|
|
59
|
+
- Stripe: `POST /checkout` makes a Checkout Session (itemised; ONE "Order #n" line when there is a discount,
|
|
60
|
+
since Stripe discounts only through its coupons), success_url = the receipt + `session_id`. The receipt page
|
|
61
|
+
(and `GET /order/:id`) asks Stripe and marks the order paid when `stripeSessionPaid` agrees on order id,
|
|
62
|
+
amount and currency. The webhook (`/stripe/webhook`, raw body, `stripeWebhookSecret`) catches a closed tab.
|
|
63
|
+
- PayPal: create order -> approve -> `GET /paypal/return` CAPTURES (ORDER_ALREADY_CAPTURED reads it instead)
|
|
64
|
+
and checks `paypalPaid`. Sandbox/live by setting.
|
|
65
|
+
- Offline: the order is `awaiting`; the receipt shows `offlineInstructions`. Mark paid from the admin.
|
|
66
|
+
- Refunds: `POST /orders/:id/refund` refunds through Stripe (payment_intent) or PayPal (capture id) FIRST,
|
|
67
|
+
and only then marks the order refunded. Full refunds only.
|
|
68
|
+
- Secrets never leave the server: GET /settings masks them (`••••••••`); saving the mask keeps the value.
|
|
69
|
+
|
|
70
|
+
## Extensions (Shopping Cart Pro)
|
|
71
|
+
|
|
72
|
+
`cartRegistry().register(owner, {stages, on, tabs, checkout})` - see lib/extensions.js. A shipping handler may also
|
|
73
|
+
return `choices: [{id, label, amount}]` - the quote carries them as `shippingChoices` (+ `shippingChoice`, the
|
|
74
|
+
picked one's `ref`) and checkout offers them when there are two or more, sending `service` back. A stage handler that
|
|
75
|
+
throws falls back to the built-in rule (and an internal note), so an extension cannot take checkout down.
|
|
76
|
+
`emit()` never throws or waits. `registry.services` gives extensions the Cart's data functions. Extension tabs
|
|
77
|
+
are dynamic-imported by shop.js from `/plugins/<owner>/...` only, and get the same ctx as the built-in tabs.
|
|
78
|
+
An extension registers from `onReady`; a lapsed licence means it never registers and nothing else changes.
|
|
79
|
+
|
|
80
|
+
## Shortcodes
|
|
81
|
+
|
|
82
|
+
`[shop-products category= tag= featured=true limit=8 columns=3 /]`, `[shop-product slug= /]`,
|
|
83
|
+
`[buy-button slug= label= /]`. Rendered from `shelfNow()` (products as last read - shortcodes are synchronous).
|
|
84
|
+
A cached page can show a stale stock flag until it is re-rendered; checkout re-prices regardless.
|
|
85
|
+
|
|
86
|
+
## Gotchas
|
|
87
|
+
|
|
88
|
+
- Storefront CSS tokens are defined on `body`, not `:root`: themes set --dm-* on body's dm-theme-* class.
|
|
89
|
+
- The site's own input/link styles reach the storefront: see the "reach in here" block at the end of shop.css.
|
|
90
|
+
- `siteBase()` uses `request.host` (with the port); `request.hostname` drops it under Fastify 5.
|
|
91
|
+
- A test site on a non-standard port needs `site.baseUrl` for Stripe/PayPal return addresses behind a proxy.
|
|
92
|
+
- Admin views: item templates live in JS (a templateUrl goes through _.render); checkboxes are set after render
|
|
93
|
+
and listened to; slideovers via kit.openPanel (disposed and removed on close).
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/* Shop admin - only --dm-* tokens, so it follows the admin theme (light, dark, custom). */
|
|
2
|
+
.shp, .shp-form, .shp-order { --shp-soft: color-mix(in srgb, var(--dm-text) 5%, transparent); --shp-line: var(--dm-border); }
|
|
3
|
+
.shp [hidden], .shp-form [hidden], .shp-order [hidden] { display: none !important; }
|
|
4
|
+
.shp { display: flex; flex-direction: column; gap: 1rem; }
|
|
5
|
+
.shp-body { display: flex; flex-direction: column; gap: 1rem; min-width: 0; }
|
|
6
|
+
.shp-error { color: var(--dm-danger); }
|
|
7
|
+
.shp-muted, .shp-note { color: var(--dm-text-muted); }
|
|
8
|
+
.shp-note { margin: 0; font-size: var(--dm-text-sm); }
|
|
9
|
+
.shp-note.is-bad { color: var(--dm-danger); }
|
|
10
|
+
.shp-spacer { flex: 1; }
|
|
11
|
+
.shp-loading { color: var(--dm-text-muted); padding: 2rem 0; }
|
|
12
|
+
|
|
13
|
+
/* Tabs under the banner */
|
|
14
|
+
.shp-nav { display: flex; flex-wrap: wrap; gap: .15rem; border-bottom: 1px solid var(--dm-border); }
|
|
15
|
+
.shp-nav a { display: inline-flex; align-items: center; gap: .4rem; padding: .5rem .85rem; color: var(--dm-text-muted); text-decoration: none;
|
|
16
|
+
font-size: var(--dm-text-sm); font-weight: 500; border-bottom: 2px solid transparent; margin-bottom: -1px; border-radius: var(--dm-radius-sm) var(--dm-radius-sm) 0 0; }
|
|
17
|
+
.shp-nav a:hover { color: var(--dm-text); background: var(--shp-soft); }
|
|
18
|
+
.shp-nav a.is-on { color: var(--dm-primary); border-bottom-color: var(--dm-primary); font-weight: 600; }
|
|
19
|
+
.shp-nav a.is-ext::after { content: 'Pro'; font-size: .62rem; font-weight: 700; letter-spacing: .04em; padding: .05rem .35rem; border-radius: 999px;
|
|
20
|
+
background: color-mix(in srgb, var(--dm-primary) 15%, transparent); color: var(--dm-primary); }
|
|
21
|
+
|
|
22
|
+
/* Cards, bars, chips */
|
|
23
|
+
.shp-card { border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); padding: .9rem 1rem; display: flex; flex-direction: column; gap: .7rem;
|
|
24
|
+
background: var(--dm-surface, transparent); min-width: 0; }
|
|
25
|
+
.shp-card > header { display: flex; align-items: center; justify-content: space-between; gap: .5rem; }
|
|
26
|
+
.shp-card h3 { margin: 0; font-size: var(--dm-text-base, 1rem); font-weight: 600; display: flex; align-items: center; gap: .3rem; }
|
|
27
|
+
.shp-more { font-size: var(--dm-text-sm); color: var(--dm-primary); text-decoration: none; }
|
|
28
|
+
.shp-bar { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; }
|
|
29
|
+
.shp-search { flex: 1; min-width: 10rem; max-width: 22rem; }
|
|
30
|
+
.shp-seg { display: inline-flex; flex-wrap: wrap; border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); overflow: hidden; }
|
|
31
|
+
.shp-seg button { border: 0; background: none; color: var(--dm-text); padding: .32rem .7rem; cursor: pointer; font: inherit; font-size: var(--dm-text-sm);
|
|
32
|
+
display: inline-flex; align-items: center; gap: .3rem; }
|
|
33
|
+
.shp-seg button + button { border-left: 1px solid var(--dm-border); }
|
|
34
|
+
.shp-seg button b { font-weight: 500; opacity: .6; }
|
|
35
|
+
.shp-seg button:hover { background: var(--shp-soft); }
|
|
36
|
+
.shp-seg button.is-on { background: var(--dm-primary); color: var(--dm-primary-text, #fff); }
|
|
37
|
+
.shp-chip { display: inline-flex; align-items: center; gap: .25rem; font-size: var(--dm-text-xs); font-weight: 600; padding: .12rem .55rem; border-radius: 999px;
|
|
38
|
+
white-space: nowrap; background: color-mix(in srgb, var(--dm-text) 9%, transparent); color: var(--dm-text-muted); }
|
|
39
|
+
.shp-chip.is-primary { background: color-mix(in srgb, var(--dm-primary) 14%, transparent); color: var(--dm-primary); }
|
|
40
|
+
.shp-chip.is-success { background: color-mix(in srgb, var(--dm-success) 15%, transparent); color: var(--dm-success); }
|
|
41
|
+
.shp-chip.is-warning { background: color-mix(in srgb, var(--dm-warning) 16%, transparent); color: color-mix(in srgb, var(--dm-warning) 55%, var(--dm-text)); }
|
|
42
|
+
.shp-chip.is-danger { background: color-mix(in srgb, var(--dm-danger) 14%, transparent); color: var(--dm-danger); }
|
|
43
|
+
|
|
44
|
+
/* Rows */
|
|
45
|
+
.shp-list { display: flex; flex-direction: column; }
|
|
46
|
+
.shp-row { display: flex; align-items: center; gap: .85rem; padding: .6rem .4rem; border-bottom: 1px solid var(--dm-border); cursor: pointer; border-radius: var(--dm-radius-sm); }
|
|
47
|
+
.shp-row:last-child { border-bottom: 0; }
|
|
48
|
+
.shp-row:hover, .shp-row:focus-visible { background: var(--shp-soft); outline: none; }
|
|
49
|
+
.shp-num { font-variant-numeric: tabular-nums; font-weight: 700; color: var(--dm-text-muted); min-width: 3.4rem; }
|
|
50
|
+
.shp-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
|
|
51
|
+
.shp-main strong, .shp-main small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
52
|
+
.shp-main small { color: var(--dm-text-muted); font-size: var(--dm-text-xs); }
|
|
53
|
+
.shp-amt { font-variant-numeric: tabular-nums; font-weight: 600; min-width: 5rem; text-align: right; }
|
|
54
|
+
.shp-kebab { border: 0; background: none; cursor: pointer; font-size: 1.15rem; color: var(--dm-text-muted); border-radius: var(--dm-radius-sm); padding: 0 .35rem; line-height: 1.6; }
|
|
55
|
+
.shp-kebab:hover { color: var(--dm-primary); background: var(--shp-soft); }
|
|
56
|
+
.shp-thumb { width: 2.6rem; height: 2.6rem; border-radius: var(--dm-radius-sm); overflow: hidden; flex: none; background: var(--shp-soft); }
|
|
57
|
+
.shp-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
|
58
|
+
.shp-star { color: var(--dm-warning); font-size: .85em; }
|
|
59
|
+
.shp-stock { font-size: var(--dm-text-xs); color: var(--dm-text-muted); min-width: 5.5rem; text-align: right; font-variant-numeric: tabular-nums; }
|
|
60
|
+
.shp-stock.is-warning { color: color-mix(in srgb, var(--dm-warning) 55%, var(--dm-text)); font-weight: 600; }
|
|
61
|
+
.shp-stock.is-danger { color: var(--dm-danger); font-weight: 600; }
|
|
62
|
+
@media (max-width: 720px) { .shp-stock, .shp-num { display: none; } .shp-amt { min-width: 0; } }
|
|
63
|
+
|
|
64
|
+
/* Overview */
|
|
65
|
+
.shp-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); gap: .75rem; }
|
|
66
|
+
.shp-tile { position: relative; border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); padding: .9rem 1rem; display: flex; flex-direction: column;
|
|
67
|
+
gap: .1rem; color: inherit; text-decoration: none; background: var(--dm-surface, transparent); overflow: hidden; }
|
|
68
|
+
a.shp-tile:hover { border-color: var(--dm-primary); }
|
|
69
|
+
.shp-tile-icon { position: absolute; top: .85rem; right: .9rem; color: var(--dm-text-muted); opacity: .7; }
|
|
70
|
+
.shp-tile-label { font-size: var(--dm-text-sm); color: var(--dm-text-muted); }
|
|
71
|
+
.shp-tile-value { font-size: 1.65rem; font-weight: 700; font-variant-numeric: tabular-nums; letter-spacing: -.01em; }
|
|
72
|
+
.shp-tile-sub { font-size: var(--dm-text-xs); color: var(--dm-text-muted); }
|
|
73
|
+
.shp-tile.is-up .shp-tile-sub { color: var(--dm-success); font-weight: 600; }
|
|
74
|
+
.shp-tile.is-down .shp-tile-sub { color: var(--dm-danger); font-weight: 600; }
|
|
75
|
+
.shp-tile.is-attention { border-color: color-mix(in srgb, var(--dm-primary) 50%, var(--dm-border)); box-shadow: inset 3px 0 0 var(--dm-primary); }
|
|
76
|
+
.shp-grid2 { display: grid; grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); gap: 1rem; }
|
|
77
|
+
@media (max-width: 960px) { .shp-grid2 { grid-template-columns: 1fr; } }
|
|
78
|
+
|
|
79
|
+
.shp-chart { position: relative; }
|
|
80
|
+
.shp-chart svg { width: 100%; height: auto; display: block; overflow: visible; }
|
|
81
|
+
.shp-chart .shp-gridline { stroke: var(--dm-border); stroke-width: 1; }
|
|
82
|
+
.shp-chart text { fill: var(--dm-text-muted); font-size: 11px; font-family: inherit; }
|
|
83
|
+
.shp-bar path { fill: var(--dm-primary); opacity: .82; transition: opacity .15s; }
|
|
84
|
+
.shp-bar .shp-bar-zero { fill: var(--dm-border); }
|
|
85
|
+
.shp-bar.is-on path, .shp-bar:focus path { opacity: 1; }
|
|
86
|
+
.shp-bar:focus { outline: none; }
|
|
87
|
+
.shp-bar { cursor: default; }
|
|
88
|
+
.shp-tip { position: absolute; top: -.25rem; width: 120px; pointer-events: none; background: var(--dm-surface, #fff); color: var(--dm-text); border: 1px solid var(--dm-border);
|
|
89
|
+
border-radius: var(--dm-radius-sm); padding: .35rem .55rem; box-shadow: var(--dm-shadow-md, 0 6px 20px rgba(0,0,0,.15)); display: flex; flex-direction: column; font-size: var(--dm-text-xs); }
|
|
90
|
+
.shp-tip strong { font-size: var(--dm-text-sm); }
|
|
91
|
+
.shp-tip span { color: var(--dm-text-muted); }
|
|
92
|
+
|
|
93
|
+
.shp-todo { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .3rem; }
|
|
94
|
+
.shp-todo-item { width: 100%; display: flex; align-items: center; gap: .6rem; border: 0; background: var(--shp-soft); color: var(--dm-text); border-radius: var(--dm-radius-sm);
|
|
95
|
+
padding: .55rem .7rem; cursor: pointer; text-align: left; font: inherit; font-size: var(--dm-text-sm); }
|
|
96
|
+
.shp-todo-item:hover { background: color-mix(in srgb, var(--dm-primary) 10%, transparent); }
|
|
97
|
+
.shp-todo-item > span:nth-child(2) { flex: 1; }
|
|
98
|
+
.shp-todo-item.is-danger > span:first-child { color: var(--dm-danger); }
|
|
99
|
+
.shp-todo-item.is-warning > span:first-child { color: var(--dm-warning); }
|
|
100
|
+
.shp-todo-go { color: var(--dm-text-muted); }
|
|
101
|
+
.shp-all-clear { display: flex; align-items: center; gap: .5rem; color: var(--dm-success); padding: .5rem 0; }
|
|
102
|
+
.shp-mini-list { display: flex; flex-direction: column; }
|
|
103
|
+
.shp-mini { display: flex; align-items: center; gap: .75rem; padding: .5rem .3rem; border-bottom: 1px solid var(--dm-border); color: inherit; text-decoration: none; border-radius: var(--dm-radius-sm); }
|
|
104
|
+
.shp-mini:last-child { border-bottom: 0; }
|
|
105
|
+
.shp-mini:hover { background: var(--shp-soft); }
|
|
106
|
+
.shp-mini-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
|
|
107
|
+
.shp-mini-main small { color: var(--dm-text-muted); font-size: var(--dm-text-xs); }
|
|
108
|
+
.shp-mini-amt { font-weight: 600; font-variant-numeric: tabular-nums; }
|
|
109
|
+
.shp-top { display: grid; grid-template-columns: minmax(0, 1fr) 30% auto; gap: .75rem; align-items: center; padding: .4rem 0; }
|
|
110
|
+
.shp-top-name { display: flex; flex-direction: column; min-width: 0; }
|
|
111
|
+
.shp-top-name small { color: var(--dm-text-muted); font-size: var(--dm-text-xs); }
|
|
112
|
+
.shp-top-bar { height: 8px; border-radius: 999px; background: var(--shp-soft); overflow: hidden; }
|
|
113
|
+
.shp-top-bar span { display: block; height: 100%; background: var(--dm-primary); border-radius: 0 4px 4px 0; }
|
|
114
|
+
|
|
115
|
+
.shp-welcome { border: 1px dashed var(--dm-border); border-radius: var(--dm-radius-lg, 14px); padding: 2rem; text-align: center; max-width: 46rem; margin: 1rem auto; }
|
|
116
|
+
.shp-welcome h2 { margin: .5rem 0 .25rem; }
|
|
117
|
+
.shp-welcome-icon { display: inline-grid; place-items: center; width: 3.6rem; height: 3.6rem; border-radius: 50%; color: var(--dm-primary);
|
|
118
|
+
background: color-mix(in srgb, var(--dm-primary) 12%, transparent); }
|
|
119
|
+
.shp-steps { list-style: none; padding: 0; margin: 1.5rem 0 0; display: grid; gap: .6rem; text-align: left; counter-reset: st; }
|
|
120
|
+
.shp-steps li { counter-increment: st; display: grid; grid-template-columns: 2rem 1fr auto; grid-template-rows: auto auto; column-gap: .75rem; align-items: center;
|
|
121
|
+
border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); padding: .75rem 1rem; }
|
|
122
|
+
.shp-steps li::before { content: counter(st); grid-row: 1 / 3; width: 2rem; height: 2rem; border-radius: 50%; display: grid; place-items: center;
|
|
123
|
+
background: var(--shp-soft); font-weight: 700; }
|
|
124
|
+
.shp-steps li.is-done::before { content: '✓'; background: var(--dm-success); color: #fff; }
|
|
125
|
+
.shp-steps li span { grid-column: 2; color: var(--dm-text-muted); font-size: var(--dm-text-sm); }
|
|
126
|
+
.shp-steps li .btn { grid-column: 3; grid-row: 1 / 3; }
|
|
127
|
+
|
|
128
|
+
/* Forms (slideovers) */
|
|
129
|
+
.shp-form { display: flex; flex-direction: column; gap: .9rem; }
|
|
130
|
+
.shp-grid { display: grid; grid-template-columns: 1fr 1fr; gap: .8rem 1rem; }
|
|
131
|
+
.shp-wide { grid-column: 1 / -1; }
|
|
132
|
+
@media (max-width: 640px) { .shp-grid { grid-template-columns: 1fr; } }
|
|
133
|
+
.shp-form .form-label { display: flex; align-items: center; gap: .3rem; margin-bottom: .25rem; }
|
|
134
|
+
.shp-input-lg { font-size: 1.1rem; font-weight: 600; }
|
|
135
|
+
.shp-mono { font-family: var(--dm-font-mono, ui-monospace, monospace); font-size: .88rem; }
|
|
136
|
+
.shp-err { display: block; color: var(--dm-danger); font-size: var(--dm-text-xs); margin-top: .25rem; }
|
|
137
|
+
.shp-hint { display: block; color: var(--dm-text-muted); font-size: var(--dm-text-xs); margin-top: .3rem; }
|
|
138
|
+
.shp-link { border: 0; background: none; padding: 0; color: var(--dm-primary); cursor: pointer; font: inherit; text-decoration: underline; }
|
|
139
|
+
.shp-check { display: inline-flex; align-items: center; gap: .45rem; cursor: pointer; font-size: var(--dm-text-sm); }
|
|
140
|
+
.shp-checks { display: flex; flex-direction: column; gap: .5rem; }
|
|
141
|
+
.shp-toggles { display: flex; align-items: flex-end; padding-bottom: .5rem; }
|
|
142
|
+
.shp-money { display: flex; align-items: stretch; }
|
|
143
|
+
.shp-money > span { display: grid; place-items: center; padding: 0 .6rem; border: 1px solid var(--dm-border); border-right: 0; border-radius: var(--dm-radius-sm) 0 0 var(--dm-radius-sm);
|
|
144
|
+
background: var(--shp-soft); color: var(--dm-text-muted); font-size: var(--dm-text-xs); font-weight: 600; }
|
|
145
|
+
.shp-money input { border-top-left-radius: 0; border-bottom-left-radius: 0; }
|
|
146
|
+
.shp-actions { display: flex; align-items: center; gap: .4rem; padding-top: .75rem; border-top: 1px solid var(--dm-border); position: sticky; bottom: 0;
|
|
147
|
+
background: var(--dm-surface, var(--dm-body-bg, #fff)); padding-bottom: .25rem; }
|
|
148
|
+
.shp-actions .btn { display: inline-flex; align-items: center; gap: .3rem; }
|
|
149
|
+
.shp-sect { border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); padding: .6rem .9rem; }
|
|
150
|
+
.shp-sect[open] { padding-bottom: .9rem; }
|
|
151
|
+
.shp-sect > summary { cursor: pointer; font-weight: 600; padding: .2rem 0; }
|
|
152
|
+
.shp-sect[open] > summary { margin-bottom: .6rem; }
|
|
153
|
+
.shp-sect > * + * { margin-top: .6rem; }
|
|
154
|
+
|
|
155
|
+
.shp-pics { display: flex; flex-wrap: wrap; gap: .5rem; }
|
|
156
|
+
.shp-pic, .shp-pic-add { position: relative; width: 5.5rem; height: 5.5rem; border-radius: var(--dm-radius-md); overflow: hidden; }
|
|
157
|
+
.shp-pic { cursor: grab; background: var(--shp-soft); }
|
|
158
|
+
.shp-pic img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
|
159
|
+
.shp-pic-x { position: absolute; top: .25rem; right: .25rem; width: 1.4rem; height: 1.4rem; border-radius: 50%; border: 0; background: rgba(0,0,0,.6); color: #fff;
|
|
160
|
+
cursor: pointer; line-height: 1; opacity: 0; transition: opacity .15s; }
|
|
161
|
+
.shp-pic:hover .shp-pic-x, .shp-pic-x:focus { opacity: 1; }
|
|
162
|
+
.shp-pic-main { position: absolute; left: .25rem; bottom: .25rem; font-size: .62rem; font-weight: 700; background: var(--dm-primary); color: var(--dm-primary-text, #fff);
|
|
163
|
+
padding: .05rem .35rem; border-radius: 999px; }
|
|
164
|
+
.shp-pic-add { border: 1.5px dashed var(--dm-border); background: none; color: var(--dm-text-muted); cursor: pointer; display: flex; flex-direction: column;
|
|
165
|
+
align-items: center; justify-content: center; gap: .2rem; font: inherit; font-size: var(--dm-text-xs); }
|
|
166
|
+
.shp-pic-add:hover { border-color: var(--dm-primary); color: var(--dm-primary); }
|
|
167
|
+
.shp-media { display: grid; grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr)); gap: .5rem; }
|
|
168
|
+
.shp-media-tile { border: 2px solid transparent; border-radius: var(--dm-radius-md); padding: 0; background: var(--shp-soft); cursor: pointer; overflow: hidden;
|
|
169
|
+
display: flex; flex-direction: column; color: var(--dm-text); font: inherit; font-size: var(--dm-text-xs); }
|
|
170
|
+
.shp-media-tile img { width: 100%; aspect-ratio: 1; object-fit: cover; display: block; }
|
|
171
|
+
.shp-media-tile span { padding: .2rem .35rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
172
|
+
.shp-media-tile.is-on { border-color: var(--dm-primary); box-shadow: 0 0 0 2px color-mix(in srgb, var(--dm-primary) 30%, transparent); }
|
|
173
|
+
|
|
174
|
+
.shp-variants { display: flex; flex-direction: column; gap: .5rem; align-items: flex-start; }
|
|
175
|
+
.shp-variants-head { display: flex; align-items: center; gap: .5rem; }
|
|
176
|
+
.shp-variants-head .form-label { margin: 0; white-space: nowrap; }
|
|
177
|
+
.shp-optname { max-width: 12rem; }
|
|
178
|
+
.shp-vtable { width: 100%; display: flex; flex-direction: column; gap: .35rem; }
|
|
179
|
+
.shp-vrow { display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 1fr) minmax(0, 1.2fr) minmax(0, .9fr) 1.8rem; gap: .35rem; align-items: center; }
|
|
180
|
+
.shp-vrow.is-head { font-size: var(--dm-text-xs); color: var(--dm-text-muted); font-weight: 600; }
|
|
181
|
+
.shp-vrow .form-input { padding: .35rem .5rem; font-size: var(--dm-text-sm); }
|
|
182
|
+
|
|
183
|
+
/* An order */
|
|
184
|
+
.shp-order { display: flex; flex-direction: column; gap: 1rem; }
|
|
185
|
+
.shp-order-head { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem; }
|
|
186
|
+
.shp-order-acts { display: flex; gap: .35rem; }
|
|
187
|
+
.shp-order-acts .btn { display: inline-flex; align-items: center; gap: .3rem; }
|
|
188
|
+
.shp-lines { width: 100%; border-collapse: collapse; }
|
|
189
|
+
.shp-lines td { padding: .5rem .35rem; border-bottom: 1px solid var(--dm-border); vertical-align: middle; }
|
|
190
|
+
.shp-lines td small { display: block; color: var(--dm-text-muted); font-size: var(--dm-text-xs); }
|
|
191
|
+
.shp-lines-img { width: 3rem; }
|
|
192
|
+
.shp-lines-img img { width: 2.6rem; height: 2.6rem; border-radius: var(--dm-radius-sm); object-fit: cover; display: block; }
|
|
193
|
+
.shp-lines .shp-num { text-align: right; white-space: nowrap; color: var(--dm-text-muted); font-weight: 500; }
|
|
194
|
+
.shp-totals { margin: 0 0 0 auto; width: min(22rem, 100%); display: flex; flex-direction: column; gap: .3rem; font-variant-numeric: tabular-nums; }
|
|
195
|
+
.shp-totals > div { display: flex; justify-content: space-between; gap: 1rem; font-size: var(--dm-text-sm); }
|
|
196
|
+
.shp-totals dt { color: var(--dm-text-muted); } .shp-totals dd { margin: 0; font-weight: 600; }
|
|
197
|
+
.shp-totals .is-included { font-size: var(--dm-text-xs); } .shp-totals .is-included dd { font-weight: 400; color: var(--dm-text-muted); }
|
|
198
|
+
.shp-totals .is-total { border-top: 1px solid var(--dm-border); padding-top: .4rem; font-size: 1.05rem; }
|
|
199
|
+
.shp-totals .is-total dt { color: var(--dm-text); font-weight: 700; } .shp-totals .is-total dd { font-weight: 800; }
|
|
200
|
+
.shp-order-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
|
|
201
|
+
@media (max-width: 640px) { .shp-order-cols { grid-template-columns: 1fr; } }
|
|
202
|
+
.shp-order h4 { margin: 0 0 .35rem; font-size: var(--dm-text-xs); text-transform: uppercase; letter-spacing: .06em; color: var(--dm-text-muted); }
|
|
203
|
+
.shp-order p { margin: 0; }
|
|
204
|
+
.shp-order a { color: var(--dm-primary); }
|
|
205
|
+
.shp-callout { border-left: 3px solid var(--dm-warning); background: color-mix(in srgb, var(--dm-warning) 9%, transparent); padding: .6rem .8rem; border-radius: var(--dm-radius-sm); }
|
|
206
|
+
.shp-callout p { white-space: pre-wrap; margin-top: .25rem; }
|
|
207
|
+
.shp-ref { font-size: var(--dm-text-xs); }
|
|
208
|
+
.shp-ext { display: flex; flex-wrap: wrap; gap: .3rem; }
|
|
209
|
+
.shp-history { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: .4rem; border-left: 2px solid var(--dm-border); padding-left: .8rem; }
|
|
210
|
+
.shp-history li { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; font-size: var(--dm-text-sm); }
|
|
211
|
+
.shp-history li > span:last-child { color: var(--dm-text-muted); }
|
|
212
|
+
|
|
213
|
+
/* Settings */
|
|
214
|
+
.shp-settings-nav { align-self: flex-start; }
|
|
215
|
+
.shp-countries { display: flex; flex-wrap: wrap; gap: .3rem; margin-bottom: .45rem; }
|
|
216
|
+
.shp-country button { border: 0; background: none; color: inherit; cursor: pointer; padding: 0 0 0 .2rem; font-size: 1rem; line-height: 1; }
|
|
217
|
+
.shp-add-country { max-width: 16rem; }
|
|
218
|
+
.shp-pay { display: flex; flex-direction: column; gap: .75rem; }
|
|
219
|
+
.shp-provider { border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); padding: .75rem .9rem; display: flex; flex-direction: column; gap: .75rem; }
|
|
220
|
+
.shp-provider-head { display: flex; align-items: center; justify-content: space-between; gap: .5rem; }
|
|
221
|
+
.shp-copy { display: flex; align-items: center; gap: .3rem; background: var(--shp-soft); border-radius: var(--dm-radius-sm); padding: .3rem .5rem; }
|
|
222
|
+
.shp-copy code { flex: 1; overflow-wrap: anywhere; font-size: var(--dm-text-xs); }
|
|
223
|
+
.shp-steps li .shp-step-btns { grid-column: 3; grid-row: 1 / 3; display: flex; gap: .3rem; color: inherit; font-size: inherit; }
|
|
224
|
+
.shp-steps li .shp-step-btns .btn { grid-column: auto; grid-row: auto; }
|