From b4e9e85f89c447943b63438c4e0314767aaa066f Mon Sep 17 00:00:00 2001 From: Hermes Bot Date: Fri, 7 Aug 2026 05:48:07 +0800 Subject: [PATCH] Add Halal food finder, ingredient scanner, and UI updates --- package-lock.json | 122 +++++++ package.json | 1 + src/App.svelte | 11 +- src/lib/HalalFoodFinder.svelte | 208 ++++++++++++ src/lib/IngredientScanner.svelte | 291 +++++++++++++++++ src/lib/halalDB.js | 542 +++++++++++++++++++++++++++++++ vite.config.js | 15 + 7 files changed, 1188 insertions(+), 2 deletions(-) create mode 100644 src/lib/HalalFoodFinder.svelte create mode 100644 src/lib/IngredientScanner.svelte create mode 100644 src/lib/halalDB.js diff --git a/package-lock.json b/package-lock.json index a68dede..048909d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "muslimpro-clone-phase1", "version": "0.1.0", "dependencies": { + "tesseract.js": "^5.1.1", "vite-plugin-pwa": "^0.21.0", "workbox-precaching": "^7.3.0" }, @@ -2729,6 +2730,12 @@ "node": ">=6.0.0" } }, + "node_modules/bmp-js": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz", + "integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==", + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "5.0.9", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", @@ -3725,6 +3732,12 @@ "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", "license": "ISC" }, + "node_modules/idb-keyval": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.3.0.tgz", + "integrity": "sha512-um+2dgAWmYsu615EXpWVwSmapJhON0G43t3Ka/EVaohzPQXSMqKEqeDK/oIW3Ow+BXaF2PvSc+oBTFp793A5Ow==", + "license": "Apache-2.0" + }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -3881,6 +3894,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-electron": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz", + "integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==", + "license": "MIT" + }, "node_modules/is-finalizationregistry": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", @@ -4094,6 +4113,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", + "license": "MIT" + }, "node_modules/is-weakmap": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", @@ -4345,6 +4370,26 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-releases": { "version": "2.0.52", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.52.tgz", @@ -4395,6 +4440,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/opencollective-postinstall": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz", + "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==", + "license": "MIT", + "bin": { + "opencollective-postinstall": "index.js" + } + }, "node_modules/own-keys": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.2.tgz", @@ -4566,6 +4620,12 @@ "node": ">=4" } }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT" + }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", @@ -5168,6 +5228,31 @@ "node": ">=10" } }, + "node_modules/tesseract.js": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tesseract.js/-/tesseract.js-5.1.1.tgz", + "integrity": "sha512-lzVl/Ar3P3zhpUT31NjqeCo1f+D5+YfpZ5J62eo2S14QNVOmHBTtbchHm/YAbOOOzCegFnKf4B3Qih9LuldcYQ==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "bmp-js": "^0.1.0", + "idb-keyval": "^6.2.0", + "is-electron": "^2.2.2", + "is-url": "^1.2.4", + "node-fetch": "^2.6.9", + "opencollective-postinstall": "^2.0.3", + "regenerator-runtime": "^0.13.3", + "tesseract.js-core": "^5.1.1", + "wasm-feature-detect": "^1.2.11", + "zlibjs": "^0.3.1" + } + }, + "node_modules/tesseract.js-core": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tesseract.js-core/-/tesseract.js-core-5.1.1.tgz", + "integrity": "sha512-KX3bYSU5iGcO1XJa+QGPbi+Zjo2qq6eBhNjSGR5E5q0JtzkoipJKOUQD7ph8kFyteCEfEQ0maWLu8MCXtvX5uQ==", + "license": "Apache-2.0" + }, "node_modules/tinyglobby": { "version": "0.2.17", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", @@ -5184,6 +5269,12 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/type-fest": { "version": "0.16.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", @@ -5513,6 +5604,28 @@ } } }, + "node_modules/wasm-feature-detect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.8.0.tgz", + "integrity": "sha512-zksaLKM2fVlnB5jQQDqKXXwYHLQUVH9es+5TOOHwGOVJOCeRBCiPjwSg+3tN2AdTCzjgli4jijCH290kXb/zWQ==", + "license": "Apache-2.0" + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -5828,6 +5941,15 @@ "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", "dev": true, "license": "MIT" + }, + "node_modules/zlibjs": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/zlibjs/-/zlibjs-0.3.1.tgz", + "integrity": "sha512-+J9RrgTKOmlxFSDHo0pI1xM6BLVUv+o0ZT9ANtCxGkjIVCCUdx9alUF8Gm+dGLKbkkkidWIHFDZHDMpfITt4+w==", + "license": "MIT", + "engines": { + "node": "*" + } } } } diff --git a/package.json b/package.json index 11dee29..d25dfef 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "vite": "^6.2.0" }, "dependencies": { + "tesseract.js": "^5.1.1", "vite-plugin-pwa": "^0.21.0", "workbox-precaching": "^7.3.0" } diff --git a/src/App.svelte b/src/App.svelte index c67b688..dad4c91 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -5,9 +5,12 @@ import Hijri from './lib/Hijri.svelte'; import Tasbih from './lib/Tasbih.svelte'; import Names99 from './lib/Names99.svelte'; + import HalalFoodFinder from './lib/HalalFoodFinder.svelte'; + import IngredientScanner from './lib/IngredientScanner.svelte'; - const tabs = ['Prayer', 'Qibla', 'Quran', 'Hijri', 'Tasbih', '99 Names']; - const icons = ['πŸ•Œ', '🧭', 'πŸ“–', 'πŸ“…', 'πŸ“Ώ', 'πŸ’š']; + const tabs = ['Prayer', 'Qibla', 'Quran', 'Hijri', 'Tasbih', 'Names', 'Food', 'Scan']; + const icons = ['πŸ•Œ', '🧭', 'πŸ“–', 'πŸ“…', 'πŸ“Ώ', 'πŸ’š', '🍽️', 'πŸ”']; + const premiumTabs = new Set([6, 7]); let activeTab = $state(0); function handleKeydown(e) { @@ -51,6 +54,10 @@ {:else if activeTab === 5} + {:else if activeTab === 6} + + {:else if activeTab === 7} + {/if} diff --git a/src/lib/HalalFoodFinder.svelte b/src/lib/HalalFoodFinder.svelte new file mode 100644 index 0000000..484c30f --- /dev/null +++ b/src/lib/HalalFoodFinder.svelte @@ -0,0 +1,208 @@ + + +
+

🍽️ Halal Food Finder

+ {#if loading} +

πŸ“ Getting your location…

+ {:else if error} +

{error}

+ + {:else} +
+
+ + +
+
Radius:
{#each radiusOptions as r}{/each}
+
+ + + + +
+
{#each cuisineList as c}{/each}
+
+
{filtered.length} restaurant{filtered.length !== 1 ? 's' : ''} within {radius}km
+ {#if viewMode === 'list'} + {#if filtered.length === 0} +

πŸ˜• No restaurants found

Try increasing radius or changing filters

+ {:else} +
+ {#each filtered as resto (resto.id)} +
+ + {#if expandedId === resto.id} +
+

πŸ“ {resto.address}

+ {#if resto.phone}

πŸ“ž {resto.phone}

{/if} + {#if resto.certifications.length > 0} +
Certifications:{#each resto.certifications as certId}{@const cert = getCert(certId)}{#if cert}
{'⭐'.repeat(cert.trustLevel)}{cert.name}({cert.country})
{/if}{/each}
+ {:else if resto.muslimOwned} +

πŸ‘€ Muslim-owned establishment. Awaiting or not yet pursuing halal certification.

+ {:else} +

⚠ No halal certification found. Exercise caution.

+ {/if} + {#if resto.userReviews.length > 0} +
Community Reviews ({resto.userReviews.length}):{#each resto.userReviews as review}
{'⭐'.repeat(review.rating)}{review.comment}{review.date}
{/each}
+ {/if} +
+ {/if} +
+ {/each} +
+ {/if} + {:else} +
+ {/if} + {/if} +
+ + diff --git a/src/lib/IngredientScanner.svelte b/src/lib/IngredientScanner.svelte new file mode 100644 index 0000000..70a0b97 --- /dev/null +++ b/src/lib/IngredientScanner.svelte @@ -0,0 +1,291 @@ + + +
+

πŸ” Halal Ingredient Scanner

+ + +
+ + +
+ + + {#if scanMode === 'paste'} +
+ +
+ + {#if ingredientText} + + {/if} +
+
+ + + + {/if} + + + {#if scanMode === 'camera'} +
+ {#if cameraState === 'idle' || cameraState === 'starting'} +
+

πŸ“· {cameraState === 'starting' ? 'Starting camera…' : 'Tap to start camera'}

+ {#if cameraState === 'idle'}{/if} +
+ {:else if cameraState === 'active'} + +
+ + +
+ {:else if cameraState === 'error'} +

❌ {cameraError}

+ {/if} +
+ {/if} + + + {#if scanned} +
+ +
0} class:mushbooh={summary.haram === 0 && summary.mushbooh > 0} class:halal={summary.haram === 0 && summary.mushbooh === 0 && summary.halal > 0}> + {summary.verdict} +
+ {#if summary.haram > 0}🚫 {summary.haram} Haram{/if} + {#if summary.mushbooh > 0}⚠ {summary.mushbooh} Doubtful{/if} + {#if summary.halal > 0}βœ… {summary.halal} Halal{/if} + {#if summary.unknown > 0}❓ {summary.unknown} Unknown{/if} +
+
+ + +
+ {#each results as item (item.id)} +
+
+ {statusEmoji(item.ingredient?.status || 'unknown')} + + {item.raw} + {#if item.eNumber}({item.eNumber}){/if} + +
+ {#if item.ingredient} +
+

Category: {item.ingredient.category}

+

{item.ingredient.explanation}

+

πŸ“š {item.ingredient.scholarlyNote}

+
+ {:else} +

Not found in our database. Check JAKIM portal or MUI.

+ {/if} +
+ {/each} +
+
+ {/if} + + + {#if history.length > 0} +
+ πŸ“œ Recent Scans ({history.length}) +
+ {#each history as h} +
+ {new Date(h.timestamp).toLocaleString()} + {h.results} ingredient{h.results !== 1 ? 's' : ''} +
+ {/each} + +
+
+ {/if} +
+ + diff --git a/src/lib/halalDB.js b/src/lib/halalDB.js new file mode 100644 index 0000000..adbc4dc --- /dev/null +++ b/src/lib/halalDB.js @@ -0,0 +1,542 @@ +// Halal Food Finder & Ingredient Scanner β€” Shared Data Module +// Nur Falah Muslim Companion β€” Premium Features + +export const CERTIFICATION_BODIES = [ + { id: 'jakim', name: 'JAKIM', country: 'Malaysia', url: 'https://www.halal.gov.my', trustLevel: 5 }, + { id: 'mui', name: 'MUI', country: 'Indonesia', url: 'https://www.halalmui.org', trustLevel: 5 }, + { id: 'muis', name: 'MUIS', country: 'Singapore', url: 'https://www.muis.gov.sg', trustLevel: 5 }, + { id: 'esma', name: 'ESMA', country: 'UAE', url: 'https://www.esma.gov.ae', trustLevel: 5 }, + { id: 'hfa', name: 'HFA', country: 'UK', url: 'https://www.halalfoodauthority.com', trustLevel: 4 }, + { id: 'hmc', name: 'HMC', country: 'UK', url: 'https://halalhmc.org', trustLevel: 4 }, + { id: 'ifanca', name: 'IFANCA', country: 'USA', url: 'https://www.ifanca.org', trustLevel: 4 }, + { id: 'sanha', name: 'SANHA', country: 'South Africa', url: 'https://www.sanha.org.za', trustLevel: 4 }, + { id: 'gac', name: 'GAC', country: 'Philippines', url: 'https://halal.da.gov.ph', trustLevel: 4 }, + { id: 'cicot', name: 'CICOT', country: 'Thailand', url: 'https://www.cicot.or.th', trustLevel: 4 }, + { id: 'bpjph', name: 'BPJPH', country: 'Indonesia', url: 'https://bpjph.halal.go.id', trustLevel: 5 }, + { id: 'halal_au',name: 'Halal Australia', country: 'Australia', url: 'https://halalaustralia.com.au', trustLevel: 3 }, +]; + +export const INGREDIENT_ALIASES = { + 'carmine': 'E120', 'cochineal': 'E120', 'natural red 4': 'E120', + 'carminic acid': 'E120', 'crimson lake': 'E120', + 'shellac': 'E904', 'confectioners glaze': 'E904', 'resinous glaze': 'E904', + 'glycerin': 'E422', 'glycerine': 'E422', 'glycerol': 'E422', + 'lecithin': 'E322', 'soy lecithin': 'E322', 'soya lecithin': 'E322', + 'bone char': 'E542', 'bone phosphate': 'E542', 'edible bone phosphate': 'E542', + 'l-cysteine': 'E920', 'cysteine': 'E920', 'l cysteine': 'E920', + 'mono and diglycerides': 'E471', 'monoglycerides': 'E471', 'diglycerides': 'E471', + 'mono diglycerides': 'E471', 'mono- and diglycerides': 'E471', + 'stearic acid': 'E570', 'octadecanoic acid': 'E570', + 'sodium stearoyl lactylate': 'E481', 'ssl': 'E481', + 'calcium stearoyl lactylate': 'E482', 'csl': 'E482', + 'polysorbate 60': 'E435', 'polysorbate 65': 'E436', 'polysorbate 80': 'E433', + 'gelatin': 'E441', 'gelatine': 'E441', + 'lanolin': 'E913', 'wool fat': 'E913', 'wool grease': 'E913', + 'beeswax': 'E901', 'white beeswax': 'E901', 'yellow beeswax': 'E901', + 'xanthan': 'E415', 'xanthan gum': 'E415', + 'guar': 'E412', 'guar gum': 'E412', + 'citric acid': 'E330', + 'sorbic acid': 'E200', + 'benzoic acid': 'E210', + 'sodium benzoate': 'E211', + 'potassium sorbate': 'E202', + 'silicon dioxide': 'E551', 'silica': 'E551', + 'titanium dioxide': 'E171', + 'magnesium stearate': 'E470b', + 'propylene glycol': 'E1520', + 'sodium bicarbonate': 'E500', 'baking soda': 'E500', + 'msg': 'E621', 'monosodium glutamate': 'E621', + 'aspartame': 'E951', + 'acesulfame k': 'E950', 'acesulfame potassium': 'E950', + 'sucralose': 'E955', + 'saccharin': 'E954', + 'beta carotene': 'E160a', 'Ξ²-carotene': 'E160a', + 'carrageenan': 'E407', + 'carboxymethylcellulose': 'E466', 'cmc': 'E466', + 'sodium carboxymethylcellulose': 'E466', + 'acetic acid': 'E260', + 'lactic acid': 'E270', + 'malic acid': 'E296', + 'tartaric acid': 'E334', + 'phosphoric acid': 'E338', + 'ascorbic acid': 'E300', 'vitamin c': 'E300', + 'tocopherols': 'E306', 'vitamin e': 'E306', + 'riboflavin': 'E101', 'vitamin b2': 'E101', + 'curcumin': 'E100', 'turmeric': 'E100', + 'chlorophyll': 'E140', + 'annatto': 'E160b', + 'paprika extract': 'E160c', + 'lycopene': 'E160d', + 'beetroot red': 'E162', 'betanin': 'E162', + 'anthocyanins': 'E163', + 'calcium carbonate': 'E170', + 'agar': 'E406', 'agar agar': 'E406', + 'locust bean gum': 'E410', 'carob gum': 'E410', + 'gum arabic': 'E414', 'acacia gum': 'E414', + 'pectin': 'E440', + 'sodium alginate': 'E401', + 'potassium alginate': 'E402', + 'ammonium alginate': 'E403', + 'alginic acid': 'E400', + 'propylene glycol alginate': 'E405', + 'modified starch': 'E1404', + 'sorbitol': 'E420', + 'mannitol': 'E421', + 'maltitol': 'E965', + 'xylitol': 'E967', + 'erythritol': 'E968', + 'isomalt': 'E953', + 'lactitol': 'E966', + 'invertase': 'E1103', + 'lysozyme': 'E1105', + 'carbon dioxide': 'E290', + 'nitrogen': 'E941', + 'sodium carbonate': 'E500', + 'potassium carbonate': 'E501', + 'ammonium carbonate': 'E503', + 'magnesium carbonate': 'E504', + 'sodium hydroxide': 'E524', + 'potassium hydroxide': 'E525', + 'calcium hydroxide': 'E526', + 'hydrochloric acid': 'E507', + 'sulphuric acid': 'E513', +}; + +export const HARAM_INGREDIENTS = [ + // ── HARAM ────────────────────────────────────────────── + { eNumber: 'E120', name: 'Carmine / Cochineal', status: 'haram', + category: 'Colouring Agent', + explanation: 'Derived from crushed cochineal insects (Dactylopius coccus). All major schools of fiqh consider insects impure and impermissible to consume, except locusts.', + scholarlyNote: 'Fatwa by JAKIM and MUI: carmine is haram as it comes from insects which are najis.' }, + { eNumber: 'E441', name: 'Gelatin', status: 'haram', + category: 'Gelling Agent', + explanation: 'Typically sourced from pig skin (Type A) or cattle bones (Type B). Unless explicitly certified halal (from zabiha-slaughtered cattle or fish), it is haram.', + scholarlyNote: 'If the source is confirmed halal (fish or Islamically slaughtered cattle), gelatin becomes halal. Look for halal certification on packaging.' }, + { eNumber: 'E542', name: 'Edible Bone Phosphate', status: 'haram', + category: 'Anti-caking Agent', + explanation: 'Produced from animal bones, often from non-halal slaughtered animals.', + scholarlyNote: 'Derived from animal bones; haram unless sourced from halal-slaughtered animals.' }, + { eNumber: 'E904', name: 'Shellac', status: 'haram', + category: 'Glazing Agent', + explanation: 'Secreted by the lac insect (Kerria lacca). Used to glaze confectionery, pills, and fruits. Considered impure by most scholars.', + scholarlyNote: 'JAKIM and MUI classify shellac as haram. Some Hanafi scholars permit it on the basis of istihala (complete transformation), but the majority position is haram.' }, + { eNumber: 'E913', name: 'Lanolin', status: 'haram', + category: 'Glazing Agent', + explanation: 'Wool grease from sheep. While sheep is halal, lanolin is extracted in a way that may involve non-halal processing aids.', + scholarlyNote: 'Controversial β€” some scholars consider it halal as it comes from a halal animal, but its extraction often uses non-halal materials.' }, + { eNumber: 'E920', name: 'L-Cysteine', status: 'haram', + category: 'Flour Treatment Agent', + explanation: 'Commonly derived from human hair, poultry feathers, or hog hair. Used in bread and bakery products as a dough conditioner.', + scholarlyNote: 'If sourced from human hair or pig, it is haram. Synthetic/fermentation-derived L-cysteine is halal but rarely labeled.' }, + { eNumber: 'E921', name: 'L-Cystine', status: 'haram', + category: 'Flour Treatment Agent', + explanation: 'Oxidized form of L-cysteine. Same animal-derived sourcing concerns.', + scholarlyNote: 'Same ruling as E920.' }, + { eNumber: 'LARD', name: 'Lard', status: 'haram', + category: 'Fat', + explanation: 'Rendered pig fat. Strictly haram under all circumstances in Islamic dietary law.', + scholarlyNote: 'Quran 2:173 explicitly forbids pork. Lard is pork-derived and carries unanimous prohibition across all madhabs.' }, + { eNumber: 'CASTOR', name: 'Castoreum', status: 'haram', + category: 'Flavouring', + explanation: 'Exudate from castor sacs of beavers. Used in vanilla and raspberry flavourings.', + scholarlyNote: 'Derived from an animal not slaughtered Islamically; classified as haram.' }, + { eNumber: 'CIVET', name: 'Civet', status: 'haram', + category: 'Flavouring', + explanation: 'Musk-like secretion from the civet cat. Used in some traditional flavourings.', + scholarlyNote: 'Impure secretion from a non-halal slaughtered source.' }, + { eNumber: 'AMBER', name: 'Ambergris', status: 'haram', + category: 'Flavouring', + explanation: 'Waxy substance from sperm whale digestive tract. Debate exists among scholars.', + scholarlyNote: 'Some Maliki scholars permit it (sea creature, dies naturally). Majority Hanafi/Shafi\'i consider it impure.' }, + + // ── MUSHBOOH (DOUBTFUL β€” NEEDS VERIFICATION) ────────── + { eNumber: 'E471', name: 'Mono- and Diglycerides', status: 'mushbooh', + category: 'Emulsifier', + explanation: 'Can be derived from plant oils (soy, palm) OR animal fats. Very common in bread, ice cream, margarine, and processed foods.', + scholarlyNote: 'Check the source. Plant-derived E471 is halal. Animal-derived E471 is haram unless from halal-slaughtered animals. Halal certification determines the ruling.' }, + { eNumber: 'E472a', name: 'Acetic Acid Esters of MG', status: 'mushbooh', + category: 'Emulsifier', + explanation: 'Esters of mono- and diglycerides. The fatty acid source determines halal status.', + scholarlyNote: 'Same ruling as E471 β€” depends on the fatty acid source.' }, + { eNumber: 'E472b', name: 'Lactic Acid Esters of MG', status: 'mushbooh', + category: 'Emulsifier', + explanation: 'Lactic acid esters. Fatty acid source may be animal or plant.', + scholarlyNote: 'Dependent on the source of the fatty acids used.' }, + { eNumber: 'E472c', name: 'Citric Acid Esters of MG', status: 'mushbooh', + category: 'Emulsifier', + explanation: 'Citric acid esters. The glycerol/fat source needs verification.', + scholarlyNote: 'Depends on the origin of the raw materials.' }, + { eNumber: 'E472d', name: 'Tartaric Acid Esters of MG', status: 'mushbooh', + category: 'Emulsifier', + explanation: 'Tartaric acid esters, common in bakery products.', + scholarlyNote: 'Source of fatty acids determines halal status.' }, + { eNumber: 'E472e', name: 'MAG of Diacetyl Tartaric Acid', status: 'mushbooh', + category: 'Emulsifier', + explanation: 'DATEM β€” a dough strengthener used in bread-making.', + scholarlyNote: 'Plant-derived DATEM is halal. Verify with manufacturer or halal certification.' }, + { eNumber: 'E472f', name: 'Mixed Acetic & Tartaric Esters', status: 'mushbooh', + category: 'Emulsifier', + explanation: 'Mixed esters. Common emulsifier blend.', + scholarlyNote: 'Check source. Plant-based = halal.' }, + { eNumber: 'E473', name: 'Sucrose Esters of Fatty Acids', status: 'mushbooh', + category: 'Emulsifier', + explanation: 'Sugar ester. Fatty acid source may be animal or plant.', + scholarlyNote: 'Must verify the fatty acid source.' }, + { eNumber: 'E474', name: 'Sucroglycerides', status: 'mushbooh', + category: 'Emulsifier', + explanation: 'Combination of sucrose esters and glycerides.', + scholarlyNote: 'Same verification needed as E471/E473.' }, + { eNumber: 'E475', name: 'Polyglycerol Esters of FA', status: 'mushbooh', + category: 'Emulsifier', + explanation: 'Polyglycerol esters. Used in margarine, cakes, and desserts.', + scholarlyNote: 'Animal or plant source β€” halal certification recommended.' }, + { eNumber: 'E476', name: 'Polyglycerol Polyricinoleate', status: 'mushbooh', + category: 'Emulsifier', + explanation: 'PGPR β€” used in chocolate to reduce cocoa butter content. The glycerol source determines status.', + scholarlyNote: 'The glycerol backbone may be animal or plant-derived. With halal certification on chocolate, PGPR is typically plant-based.' }, + { eNumber: 'E477', name: 'Propylene Glycol Esters of FA', status: 'mushbooh', + category: 'Emulsifier', + explanation: 'Propylene glycol esters. Fatty acid component needs verification.', + scholarlyNote: 'Verify source of fatty acids.' }, + { eNumber: 'E478', name: 'Lactylated Fatty Acid Esters', status: 'mushbooh', + category: 'Emulsifier', + explanation: 'Glycerol and fatty acid esters, lactylated.', + scholarlyNote: 'Dependent on source materials.' }, + { eNumber: 'E479b', name: 'Thermally Oxidized Soy Bean Oil', status: 'mushbooh', + category: 'Emulsifier', + explanation: 'Used as a release agent in frying. Soy is halal, but processing aids may not be.', + scholarlyNote: 'Generally halal if no cross-contamination with haram oils.' }, + { eNumber: 'E481', name: 'Sodium Stearoyl Lactylate', status: 'mushbooh', + category: 'Emulsifier', + explanation: 'SSL β€” a common dough strengthener in commercial bread. The stearic acid may be animal or plant-derived.', + scholarlyNote: 'Stearic acid source determines halal status. Most commercial SSL is plant-derived but verification is recommended.' }, + { eNumber: 'E482', name: 'Calcium Stearoyl Lactylate', status: 'mushbooh', + category: 'Emulsifier', + explanation: 'CSL β€” same concerns as SSL (E481).', + scholarlyNote: 'Same ruling as E481.' }, + { eNumber: 'E483', name: 'Stearyl Tartrate', status: 'mushbooh', + category: 'Flour Treatment Agent', + explanation: 'Dough strengthener. The stearyl component may be from animal fat.', + scholarlyNote: 'Verify source with manufacturer.' }, + { eNumber: 'E491', name: 'Sorbitan Monostearate', status: 'mushbooh', + category: 'Emulsifier', + explanation: 'The stearic acid portion may be animal or plant-derived.', + scholarlyNote: 'Plant-based = halal. Animal-based requires halal slaughter.' }, + { eNumber: 'E492', name: 'Sorbitan Tristearate', status: 'mushbooh', + category: 'Emulsifier', explanation: 'Same concerns as E491.', scholarlyNote: 'Same ruling as E491.' }, + { eNumber: 'E493', name: 'Sorbitan Monolaurate', status: 'mushbooh', + category: 'Emulsifier', explanation: 'Lauric acid from coconut or palm. Generally plant-based.', + scholarlyNote: 'Likely halal β€” lauric acid is typically from coconut/palm kernel oil.' }, + { eNumber: 'E494', name: 'Sorbitan Monooleate', status: 'mushbooh', + category: 'Emulsifier', explanation: 'Oleic acid component.', scholarlyNote: 'Verify oleic acid source.' }, + { eNumber: 'E495', name: 'Sorbitan Monopalmitate', status: 'mushbooh', + category: 'Emulsifier', explanation: 'Palmitic acid component.', scholarlyNote: 'Verify palmitic acid source.' }, + { eNumber: 'E570', name: 'Stearic Acid', status: 'mushbooh', + category: 'Anti-caking Agent', + explanation: 'Can be derived from animal fat (tallow) or plant oils (palm, coconut). Very widely used.', + scholarlyNote: 'Plant-derived stearic acid is halal. Animal-derived requires halal slaughter. Halal certification clarifies this.' }, + { eNumber: 'E322', name: 'Lecithin', status: 'mushbooh', + category: 'Emulsifier', + explanation: 'Commonly from soy (halal) or egg yolk (halal if from halal chicken). Rarely animal-derived. Most commercial lecithin is soy-based.', + scholarlyNote: 'Soy lecithin is halal. Egg lecithin is halal. Only concern is if animal-derived (rare).' }, + { eNumber: 'E422', name: 'Glycerol / Glycerin', status: 'mushbooh', + category: 'Humectant', + explanation: 'Can be from plant oils, animal fats, or petroleum. Used as a sweetener, humectant, and solvent.', + scholarlyNote: 'Plant/petroleum glycerol is halal. Animal-derived requires halal slaughter. Halal-certified products use plant glycerol.' }, + { eNumber: 'E626', name: 'Guanylic Acid', status: 'mushbooh', + category: 'Flavour Enhancer', explanation: 'May use animal enzymes in production.', + scholarlyNote: 'Check with manufacturer if animal enzymes were used.' }, + { eNumber: 'E627', name: 'Sodium Guanylate', status: 'mushbooh', + category: 'Flavour Enhancer', explanation: 'Sodium salt of guanylic acid. Same concerns.', + scholarlyNote: 'Same ruling as E626.' }, + { eNumber: 'E628', name: 'Potassium Guanylate', status: 'mushbooh', + category: 'Flavour Enhancer', explanation: 'Potassium salt of guanylic acid.', + scholarlyNote: 'Same ruling as E626.' }, + { eNumber: 'E629', name: 'Calcium Guanylate', status: 'mushbooh', + category: 'Flavour Enhancer', explanation: 'Calcium salt of guanylic acid.', + scholarlyNote: 'Same ruling as E626.' }, + { eNumber: 'E630', name: 'Inosinic Acid', status: 'mushbooh', + category: 'Flavour Enhancer', explanation: 'May be produced using animal enzymes.', + scholarlyNote: 'Verify production method with manufacturer.' }, + { eNumber: 'E631', name: 'Sodium Inosinate', status: 'mushbooh', + category: 'Flavour Enhancer', explanation: 'Common in instant noodles and snacks. Often produced with animal enzymes.', + scholarlyNote: 'JAKIM requires halal-certified E631. Without certification, it is doubtful.' }, + { eNumber: 'E632', name: 'Potassium Inosinate', status: 'mushbooh', + category: 'Flavour Enhancer', explanation: 'Same concerns as E631.', + scholarlyNote: 'Same ruling as E631.' }, + { eNumber: 'E633', name: 'Calcium Inosinate', status: 'mushbooh', + category: 'Flavour Enhancer', explanation: 'Same concerns.', + scholarlyNote: 'Same ruling as E631.' }, + { eNumber: 'E634', name: 'Calcium 5-Ribonucleotides', status: 'mushbooh', + category: 'Flavour Enhancer', explanation: 'Blend of guanylate and inosinate.', + scholarlyNote: 'Halal certification recommended due to potential animal enzymes.' }, + { eNumber: 'E635', name: 'Disodium 5-Ribonucleotides', status: 'mushbooh', + category: 'Flavour Enhancer', explanation: 'I+G β€” very common in snacks, chips, instant noodles.', + scholarlyNote: 'Commonly uses animal enzymes. Look for halal logo or avoid if uncertified.' }, + { eNumber: 'E640', name: 'Glycine', status: 'mushbooh', + category: 'Flavour Enhancer', explanation: 'Can be from animal or plant protein.', + scholarlyNote: 'Plant-derived glycine is halal. Verify source.' }, + { eNumber: 'E901', name: 'Beeswax', status: 'mushbooh', + category: 'Glazing Agent', + explanation: 'Natural wax from honeybees. Generally considered halal by most scholars as it is a plant/insect secretion, not the insect itself.', + scholarlyNote: 'Majority of scholars (Shafi\'i, Hanafi) consider beeswax halal. Some scholars are cautious.' }, + { eNumber: 'E1103', name: 'Invertase', status: 'mushbooh', + category: 'Enzyme', explanation: 'May be animal or microbial source.', + scholarlyNote: 'Microbial invertase is halal. Animal-derived needs verification.' }, + { eNumber: 'E1105', name: 'Lysozyme', status: 'mushbooh', + category: 'Preservative', + explanation: 'Usually derived from egg white. Halal if from halal chicken eggs. Rarely from other animal sources.', + scholarlyNote: 'Egg-derived lysozyme from halal sources is halal. Check source.' }, + + // ── NON-E-NUMBER MUSHBOOH ────────────────────────────── + { eNumber: 'RENNET', name: 'Rennet', status: 'mushbooh', + category: 'Enzyme', + explanation: 'Used in cheese-making. Can be animal-derived (calf stomach), microbial, or plant-based. Animal rennet requires halal slaughter.', + scholarlyNote: 'Microbial/plant rennet is halal. Animal rennet from non-halal-slaughtered animals is haram. Many commercial cheeses now use microbial rennet.' }, + { eNumber: 'WHEY', name: 'Whey', status: 'mushbooh', + category: 'Dairy Derivative', + explanation: 'By-product of cheese-making. Halal status depends on the rennet used in the cheese production.', + scholarlyNote: 'If made with microbial/plant rennet or halal animal rennet, whey is halal. If made with haram rennet, whey is haram.' }, + { eNumber: 'PEPSIN', name: 'Pepsin', status: 'mushbooh', + category: 'Enzyme', + explanation: 'Digestive enzyme, commonly from pig stomach. Can also be synthetic.', + scholarlyNote: 'Porcine pepsin is haram. Synthetic or plant-derived alternatives exist β€” verify source.' }, + { eNumber: 'TRYPSIN', name: 'Trypsin', status: 'mushbooh', + category: 'Enzyme', explanation: 'Digestive enzyme from animal pancreas.', + scholarlyNote: 'Animal-derived trypsin from non-halal sources is haram. Microbial alternatives are halal.' }, + { eNumber: 'LIPASE', name: 'Lipase', status: 'mushbooh', + category: 'Enzyme', + explanation: 'Fat-digesting enzyme. Can be from animal pancreas, calf stomach, or microbial sources.', + scholarlyNote: 'Microbial lipase is halal. Animal-derived lipase requires verification of halal slaughter.' }, + + // ── HALAL (CONFIRMED SAFE) ───────────────────────────── + { eNumber: 'E100', name: 'Curcumin', status: 'halal', + category: 'Colouring Agent', explanation: 'Natural yellow colour from turmeric root. Plant-derived.', + scholarlyNote: 'Universally halal β€” pure plant extract.' }, + { eNumber: 'E101', name: 'Riboflavin (Vitamin B2)', status: 'halal', + category: 'Colouring Agent', explanation: 'Naturally present in many foods. Synthetically produced for food use.', + scholarlyNote: 'Halal β€” synthetic production avoids animal concerns.' }, + { eNumber: 'E140', name: 'Chlorophyll', status: 'halal', + category: 'Colouring Agent', explanation: 'Green pigment from plants (alfalfa, nettles).', + scholarlyNote: 'Pure plant derivative β€” halal.' }, + { eNumber: 'E150a', name: 'Plain Caramel', status: 'halal', + category: 'Colouring Agent', explanation: 'Caramelized sugar. No animal products involved.', + scholarlyNote: 'Halal β€” sugar-based.' }, + { eNumber: 'E160a', name: 'Beta-Carotene', status: 'halal', + category: 'Colouring Agent', explanation: 'Orange pigment from carrots, sweet potatoes, or synthetic production.', + scholarlyNote: 'Halal β€” plant-derived or synthetic.' }, + { eNumber: 'E162', name: 'Beetroot Red', status: 'halal', + category: 'Colouring Agent', explanation: 'Natural red colour from beetroot.', + scholarlyNote: 'Pure plant extract β€” halal.' }, + { eNumber: 'E163', name: 'Anthocyanins', status: 'halal', + category: 'Colouring Agent', explanation: 'Red/blue/purple pigments from grape skins and other fruits.', + scholarlyNote: 'Plant-derived β€” halal.' }, + { eNumber: 'E170', name: 'Calcium Carbonate', status: 'halal', + category: 'Colouring Agent / Mineral', explanation: 'Natural mineral (limestone, chalk).', + scholarlyNote: 'Mineral source β€” halal.' }, + { eNumber: 'E200', name: 'Sorbic Acid', status: 'halal', + category: 'Preservative', explanation: 'Synthetic or from rowan berries.', + scholarlyNote: 'Halal β€” no animal involvement.' }, + { eNumber: 'E201', name: 'Sodium Sorbate', status: 'halal', + category: 'Preservative', explanation: 'Sodium salt of sorbic acid.', + scholarlyNote: 'Halal.' }, + { eNumber: 'E202', name: 'Potassium Sorbate', status: 'halal', + category: 'Preservative', explanation: 'Potassium salt of sorbic acid.', + scholarlyNote: 'Halal.' }, + { eNumber: 'E203', name: 'Calcium Sorbate', status: 'halal', + category: 'Preservative', explanation: 'Calcium salt of sorbic acid.', + scholarlyNote: 'Halal.' }, + { eNumber: 'E210', name: 'Benzoic Acid', status: 'halal', + category: 'Preservative', explanation: 'Synthetic preservative.', + scholarlyNote: 'Halal β€” synthetic.' }, + { eNumber: 'E211', name: 'Sodium Benzoate', status: 'halal', + category: 'Preservative', explanation: 'Sodium salt of benzoic acid.', + scholarlyNote: 'Halal.' }, + { eNumber: 'E260', name: 'Acetic Acid', status: 'halal', + category: 'Acidity Regulator', explanation: 'Vinegar component. Synthetically produced for food use.', + scholarlyNote: 'Halal β€” synthetic or fermentation-derived.' }, + { eNumber: 'E270', name: 'Lactic Acid', status: 'halal', + category: 'Acidity Regulator', explanation: 'Fermentation-derived, typically from plant sugars.', + scholarlyNote: 'Halal unless specifically from animal milk in a non-halal context (extremely rare).' }, + { eNumber: 'E290', name: 'Carbon Dioxide', status: 'halal', + category: 'Propellant', explanation: 'The gas that makes drinks fizzy.', + scholarlyNote: 'Universally halal.' }, + { eNumber: 'E296', name: 'Malic Acid', status: 'halal', + category: 'Acidity Regulator', explanation: 'Fruit acid from apples. Synthetic production common.', + scholarlyNote: 'Halal.' }, + { eNumber: 'E300', name: 'Ascorbic Acid (Vitamin C)', status: 'halal', + category: 'Antioxidant', explanation: 'Synthetic vitamin C. No animal source.', + scholarlyNote: 'Halal.' }, + { eNumber: 'E306', name: 'Tocopherols (Vitamin E)', status: 'halal', + category: 'Antioxidant', explanation: 'From plant oils (soy, sunflower, corn).', + scholarlyNote: 'Plant-derived β€” halal.' }, + { eNumber: 'E330', name: 'Citric Acid', status: 'halal', + category: 'Acidity Regulator', explanation: 'Fermented from molasses or corn. Most common food acid.', + scholarlyNote: 'Universally halal β€” fermentation product.' }, + { eNumber: 'E334', name: 'Tartaric Acid', status: 'halal', + category: 'Acidity Regulator', explanation: 'From grapes (wine industry by-product).', + scholarlyNote: 'Halal β€” the acid is chemically distinct from alcohol and does not intoxicate.' }, + { eNumber: 'E338', name: 'Phosphoric Acid', status: 'halal', + category: 'Acidity Regulator', explanation: 'Mineral acid. Used in cola drinks.', + scholarlyNote: 'Mineral source β€” halal.' }, + { eNumber: 'E400', name: 'Alginic Acid', status: 'halal', + category: 'Thickener', explanation: 'From brown seaweed.', + scholarlyNote: 'Plant/marine source β€” halal.' }, + { eNumber: 'E401', name: 'Sodium Alginate', status: 'halal', + category: 'Thickener', explanation: 'Sodium salt of alginic acid.', + scholarlyNote: 'Halal.' }, + { eNumber: 'E406', name: 'Agar', status: 'halal', + category: 'Gelling Agent', explanation: 'From red algae (seaweed). Common in Asian desserts.', + scholarlyNote: 'Universally halal β€” pure seaweed extract.' }, + { eNumber: 'E407', name: 'Carrageenan', status: 'halal', + category: 'Thickener', explanation: 'From red seaweed (Irish moss).', + scholarlyNote: 'Plant-derived β€” halal.' }, + { eNumber: 'E410', name: 'Locust Bean Gum', status: 'halal', + category: 'Thickener', explanation: 'From carob tree seeds.', + scholarlyNote: 'Plant-derived β€” halal.' }, + { eNumber: 'E412', name: 'Guar Gum', status: 'halal', + category: 'Thickener', explanation: 'From guar beans.', + scholarlyNote: 'Plant-derived β€” halal.' }, + { eNumber: 'E414', name: 'Gum Arabic', status: 'halal', + category: 'Thickener', explanation: 'From acacia tree sap.', + scholarlyNote: 'Plant-derived β€” halal.' }, + { eNumber: 'E415', name: 'Xanthan Gum', status: 'halal', + category: 'Thickener', explanation: 'Fermentation product from corn sugar.', + scholarlyNote: 'Halal β€” fermentation-derived from plant sugars.' }, + { eNumber: 'E440', name: 'Pectin', status: 'halal', + category: 'Gelling Agent', explanation: 'From apple pomace and citrus peel.', + scholarlyNote: 'Universally halal β€” pure fruit extract.' }, + { eNumber: 'E466', name: 'Carboxymethylcellulose', status: 'halal', + category: 'Thickener', explanation: 'Modified plant cellulose.', + scholarlyNote: 'Plant-derived β€” halal.' }, + { eNumber: 'E500', name: 'Sodium Carbonates', status: 'halal', + category: 'Acidity Regulator', explanation: 'Baking soda, soda ash. Mineral source.', + scholarlyNote: 'Mineral source β€” halal.' }, + { eNumber: 'E501', name: 'Potassium Carbonates', status: 'halal', + category: 'Acidity Regulator', explanation: 'Mineral source.', + scholarlyNote: 'Halal β€” mineral-derived.' }, + { eNumber: 'E509', name: 'Calcium Chloride', status: 'halal', + category: 'Firming Agent', explanation: 'Mineral salt.', + scholarlyNote: 'Halal.' }, + { eNumber: 'E551', name: 'Silicon Dioxide', status: 'halal', + category: 'Anti-caking Agent', explanation: 'Silica β€” sand-derived mineral.', + scholarlyNote: 'Mineral β€” halal.' }, + { eNumber: 'E621', name: 'MSG (Monosodium Glutamate)', status: 'halal', + category: 'Flavour Enhancer', explanation: 'Fermented from molasses, tapioca, or corn. Plant-based fermentation.', + scholarlyNote: 'Halal β€” fermentation product. Note: not the same as E631/E635 which are often animal-derived.' }, + { eNumber: 'E901', name: 'Beeswax', status: 'mushbooh', + category: 'Glazing Agent', explanation: 'See mushbooh section.', + scholarlyNote: 'Majority halal per JAKIM/MUI. Some scholars more cautious.' }, + { eNumber: 'E950', name: 'Acesulfame K', status: 'halal', + category: 'Sweetener', explanation: 'Synthetic sweetener.', + scholarlyNote: 'Halal β€” synthetic.' }, + { eNumber: 'E951', name: 'Aspartame', status: 'halal', + category: 'Sweetener', explanation: 'Synthetic dipeptide sweetener.', + scholarlyNote: 'Halal β€” synthetic. No animal-derived components.' }, + { eNumber: 'E954', name: 'Saccharin', status: 'halal', + category: 'Sweetener', explanation: 'Synthetic sweetener.', + scholarlyNote: 'Halal.' }, + { eNumber: 'E955', name: 'Sucralose', status: 'halal', + category: 'Sweetener', explanation: 'Chlorinated sugar derivative.', + scholarlyNote: 'Halal β€” synthetic.' }, + { eNumber: 'E965', name: 'Maltitol', status: 'halal', + category: 'Sweetener', explanation: 'Sugar alcohol from starch.', + scholarlyNote: 'Halal β€” plant starch source.' }, + { eNumber: 'E967', name: 'Xylitol', status: 'halal', + category: 'Sweetener', explanation: 'Sugar alcohol from birch bark or corn.', + scholarlyNote: 'Halal β€” plant source.' }, + { eNumber: 'E968', name: 'Erythritol', status: 'halal', + category: 'Sweetener', explanation: 'Fermented sugar alcohol.', + scholarlyNote: 'Halal.' }, + { eNumber: 'E1404', name: 'Oxidized Starch', status: 'halal', + category: 'Thickener', explanation: 'Modified plant starch.', + scholarlyNote: 'Halal β€” plant source.' }, + { eNumber: 'E1520', name: 'Propylene Glycol', status: 'halal', + category: 'Humectant', explanation: 'Synthetic. Not to be confused with animal-derived glycerol.', + scholarlyNote: 'Halal β€” petrochemical derivative, not animal-based.' }, +]; + +/** Look up an ingredient by E-number (case-insensitive) */ +export function findByENumber(code) { + const upper = String(code).toUpperCase().trim(); + return HARAM_INGREDIENTS.find(i => i.eNumber.toUpperCase() === upper) || null; +} + +/** Look up ingredient by name (fuzzy) */ +export function findByName(name) { + const lower = name.toLowerCase().trim(); + return HARAM_INGREDIENTS.find(i => i.name.toLowerCase().includes(lower)) + || HARAM_INGREDIENTS.find(i => lower.includes(i.name.toLowerCase())) + || null; +} + +/** Resolve an ingredient string (E-number, alias, or name) to its DB entry */ +export function resolveIngredient(raw) { + const trimmed = raw.trim(); + if (!trimmed) return null; + + // Try direct E-number match + const enMatch = findByENumber(trimmed); + if (enMatch) return enMatch; + + // Try alias lookup + const key = trimmed.toLowerCase(); + const canonical = INGREDIENT_ALIASES[key]; + if (canonical) { + const aliasMatch = findByENumber(canonical); + if (aliasMatch) return aliasMatch; + } + + // Try name match + return findByName(trimmed); +} + +/** Get certification body by ID */ +export function getCert(id) { + return CERTIFICATION_BODIES.find(c => c.id === id) || null; +} + +/** Calculate halal confidence score (0-100) */ +export function calculateConfidence(restaurant) { + if (!restaurant) return 0; + const certs = (restaurant.certifications || []).map(c => getCert(c)).filter(Boolean); + const maxTrust = certs.length > 0 ? Math.max(...certs.map(c => c.trustLevel)) : 0; + const certScore = maxTrust * 15; + const ratingScore = (restaurant.rating || 0) * 4; + const reviewCount = (restaurant.userReviews || []).length; + const communityScore = Math.min(reviewCount * 2, 20); + const ownerClaimScore = restaurant.muslimOwned ? 5 : 0; + return Math.min(Math.round(certScore + ratingScore + communityScore + ownerClaimScore), 100); +} + +/** Get confidence color class */ +export function confidenceColor(score) { + if (score >= 80) return '#16a34a'; + if (score >= 50) return '#d97706'; + if (score >= 30) return '#f59e0b'; + return '#9ca3af'; +} + +/** Get certification badge color */ +export function certBadgeColor(restaurant) { + const certs = (restaurant.certifications || []).map(c => getCert(c)).filter(Boolean); + if (certs.some(c => c.trustLevel >= 4)) return '#16a34a'; + if (certs.length > 0) return '#d97706'; + if (restaurant.muslimOwned) return '#d97706'; + return '#9ca3af'; +} + +/** Get certification status label */ +export function certStatusLabel(restaurant) { + const certs = (restaurant.certifications || []).map(c => getCert(c)).filter(Boolean); + if (certs.some(c => c.trustLevel >= 4)) { + const names = certs.filter(c => c.trustLevel >= 4).map(c => c.name).join(', '); + return `βœ… Certified: ${names}`; + } + if (certs.length > 0) { + const names = certs.map(c => c.name).join(', '); + return `πŸ“‹ Self-Claimed: ${names}`; + } + if (restaurant.muslimOwned) return 'πŸ‘€ Muslim-Owned (Uncertified)'; + return '❓ Status Unknown'; +} diff --git a/vite.config.js b/vite.config.js index c8d4d51..b5678e0 100644 --- a/vite.config.js +++ b/vite.config.js @@ -27,6 +27,21 @@ export default defineConfig({ urlPattern: /^https:\/\/api\.alquran\.cloud\/.*/i, handler: 'StaleWhileRevalidate', options: { cacheName: 'quran-api', expiration: { maxEntries: 50, maxAgeSeconds: 86400 * 7 } } + }, + { + urlPattern: /^https:\/\/unpkg\.com\/leaflet.*/i, + handler: 'CacheFirst', + options: { cacheName: 'leaflet-lib', expiration: { maxEntries: 5, maxAgeSeconds: 86400 * 30 } } + }, + { + urlPattern: /^https:\/\/.*\.tile\.openstreetmap\.org\/.*/i, + handler: 'StaleWhileRevalidate', + options: { cacheName: 'osm-tiles', expiration: { maxEntries: 200, maxAgeSeconds: 86400 * 7 } } + }, + { + urlPattern: /^https:\/\/cdn\.jsdelivr\.net\/npm\/tesseract\.js.*/i, + handler: 'CacheFirst', + options: { cacheName: 'tesseract', expiration: { maxEntries: 10, maxAgeSeconds: 86400 * 30 } } } ] }