commit 94e1502aa2707ed4e5468e16856d26ac724cadc8 Author: xbl Date: Wed Oct 22 11:51:33 2025 +0200 final diff --git a/app.py b/app.py new file mode 100755 index 0000000..0632b87 --- /dev/null +++ b/app.py @@ -0,0 +1,57 @@ +from flask import Flask, render_template, request +import math +from profiles import profiles + +app = Flask(__name__) + +# --- Matching logic (server-side copy, used for initial render only) --- +def euclidean_distance(a, b): + return math.sqrt(sum((x - y) ** 2 for x, y in zip(a, b))) + +@app.route("/") +def index(): + return render_template("about.html") + +@app.route("/inactive") +def inactive(): + # Default user values (all 20). Client-side UI will live-update from there. + user_values = [20, 20, 20, 20, 20, 20] + sorted_matches = sorted( + profiles.items(), key=lambda p: euclidean_distance(user_values, p[1]['values']) + ) + top_matches = sorted_matches[:3] + + return render_template( + "inactive.html", + user=user_values, + top_matches=top_matches, + profiles=profiles, + ) + +@app.route("/profiles") +def profiles_page(): + return render_template("profiles.html", profiles=profiles) + +@app.route("/quiz", methods=["GET", "POST"]) +def quiz(): + if request.method == "POST": + user_values = [ + int(request.form.get(f"point{i}", 20)) for i in range(1, 7) + ] + sorted_matches = sorted( + profiles.items(), + key=lambda p: euclidean_distance(user_values, p[1]['values']) + ) + top_matches = sorted_matches[:3] + return render_template("result.html", + user=user_values, + top_matches=top_matches, + profiles=profiles) + return render_template("quiz.html", profiles=profiles) + +@app.route("/result") +def result(): + return render_template("result.html", profiles=profiles) + +if __name__ == "__main__": + app.run(host='localhost', port=1337, debug=False) diff --git a/profiles.py b/profiles.py new file mode 100644 index 0000000..14bade9 --- /dev/null +++ b/profiles.py @@ -0,0 +1,107 @@ +# Predefined radar profiles +profiles = { + "Alpenglow": { + "values": [25, 35, 45, 70, 50, 25], + "info_text0": "15%", + "info_text1": "<1%", + "info_text2": "Indica-dominant Hybrid", + "info_text3": "spicy-fruity, sour apple", + "info_text4": "relaxing, sleep promoting" + }, + "Amnesia Haze": { + "values": [35, 65, 40, 35, 30, 35], + "info_text0": "20%", + "info_text1": "<1%", + "info_text2": "Sativa-dominant Hybrid", + "info_text3": "incense, citrus fruits", + "info_text4": "energizing, calming" + }, + "Blue Dream": { + "values": [25, 35, 40, 41, 75, 35], + "info_text0": "15%", + "info_text1": "<1%", + "info_text2": "Sativa-dominant Hybrid", + "info_text3": "berries, sweet, slighty hazy", + "info_text4": "focusing, relaxing, slightly sleep promoting" + }, + "Bubba Kush": { + "values": [30, 61, 40, 41, 31, 35], + "info_text0": "15%", + "info_text1": "<1%", + "info_text2": "Indica-dominant Hybrid", + "info_text3": "lavender, cocoa, roasted coffee", + "info_text4": "calming, relaxing" + }, + "Cherry Valley Cake": { + "values": [30, 59, 65, 35, 30, 55], + "info_text0": "18%", + "info_text1": "<1%", + "info_text2": "Indica-dominant Hybrid", + "info_text3": "creamy, spicy, fermented cherry", + "info_text4": "inspring, relaxing, calming" + }, + "Ciskei": { + "values": [70, 40, 35, 39, 41, 40], + "info_text0": "5%", + "info_text1": "7.5%", + "info_text2": "Sativa", + "info_text3": "herbaceous, cheesy, spicy", + "info_text4": "energizing, focusing" + }, + "Cleopatra's Milk": { + "values": [30, 65, 45, 35, 35, 55], + "info_text0": "18%", + "info_text1": "<1%", + "info_text2": "Indica-dominant Hybrid", + "info_text3": "cake, oriental spices, fruit", + "info_text4": "inspring, calming" + }, + "Grape Waves": { + "values": [25, 75, 45, 45, 35, 45], + "info_text0": "20%", + "info_text1": "<1%", + "info_text2": "Indica-dominant Hybrid", + "info_text3": "creamy, heavy, raisins", + "info_text4": "calming, inspiring" + }, + "JBT Kush": { + "values": [30, 55, 50, 55, 45, 41], + "info_text0": "20%", + "info_text1": "<1%", + "info_text2": "Indica-dominant Hybrid", + "info_text3": "gassy (fuel), cheesy, pungent", + "info_text4": "calming, relaxing, sleep promoting" + }, + "Mother's Choice": { + "values": [35, 35, 55, 35, 50, 61], + "info_text0": "15%", + "info_text1": "<1%", + "info_text2": "Indica-dominant Hybrid", + "info_text3": "spicy-sweet, candy", + "info_text4": "inspiring, focusing, relaxing" + }, + "Royal Feast": { + "values": [25, 50, 55, 70, 55, 45], + "info_text0": "15%", + "info_text1": "<1%", + "info_text2": "Indica-dominant Hybrid", + "info_text3": "earthy, sour, heavy apple aroma", + "info_text4": "focusing, calming, sleep promoting" + }, + "Strawberry Sundae": { + "values": [28, 32, 50, 42, 41, 47], + "info_text0": "15%", + "info_text1": "<1%", + "info_text2": "Indica-dominant Hybrid", + "info_text3": "fruity-creamy, strawberry yogurt", + "info_text4": "inspiring, relaxing" + }, + "Wedding Cake": { + "values": [25, 70, 60, 50, 41, 50], + "info_text0": "20%", + "info_text1": "<1%", + "info_text2": "Indica-dominant Hybrid", + "info_text3": "creamy, lemon, vanilla, pastry", + "info_text4": "calming, relaxing, inspiring" + } +} diff --git a/static/cic.png b/static/cic.png new file mode 100644 index 0000000..1d0097b Binary files /dev/null and b/static/cic.png differ diff --git a/static/cm.png b/static/cm.png new file mode 100644 index 0000000..acae4bb Binary files /dev/null and b/static/cm.png differ diff --git a/static/favicon.png b/static/favicon.png new file mode 100755 index 0000000..3053620 Binary files /dev/null and b/static/favicon.png differ diff --git a/static/js/inactive.js b/static/js/inactive.js new file mode 100755 index 0000000..73df91b --- /dev/null +++ b/static/js/inactive.js @@ -0,0 +1,168 @@ +document.addEventListener("DOMContentLoaded", () => { + const labels = ['Energy','Calm','Relax','Sleep','Focus','Inspire']; + let userData = [...window.appData.initialUserData]; + const profiles = window.appData.profiles; + + const youLineColor = 'rgba(255,255,255,0.69)'; + const pointBgColor = 'rgba(0,0,0,0.1)'; + const youFillColor = 'rgba(255,255,255,0.1)'; + const matchLineColor = 'rgb(191,181,49)'; + const matchFillColor = 'rgba(191,181,49,0.420)'; + const radialLineColor= 'rgba(255,255,255,0.420)'; + + function distance(a,b){ return Math.sqrt(a.reduce((s,v,i)=>s+(v-b[i])**2,0)); } + + function getTopMatches() { + return Object.entries(profiles) + .sort((a,b)=>distance(userData,a[1].values)-distance(userData,b[1].values)) + .slice(0,3); + } + + function getFontSize(){ return window.innerWidth < 600 ? 10 : 39; } + function getBottomChartFontSize(){ return window.innerWidth < 600 ? 8 : 24; } + function getPointRadius(){ return window.innerWidth < 600 ? 11 : 24; } + function getPointHoverRadius(){ return window.innerWidth < 600 ? 12 : 39; } + function getPointBorderColor(){ return '#BFB531'; } + + // === Initialize charts === + const charts = [1,2,3].map(i => { + const ctx = document.getElementById(`chart-${i}`).getContext('2d'); + const isBest = i === 1; + + const datasets = isBest ? [ + { + label:'You', + data:[...userData], + fill:true, + borderColor: youLineColor, + backgroundColor: youFillColor, + pointBackgroundColor: pointBgColor, + pointBorderColor: getPointBorderColor(), + pointRadius: getPointRadius(), + pointHoverRadius: getPointHoverRadius() + }, + { + label:'Match', + data:[], + fill:true, + borderColor: matchLineColor, + backgroundColor: matchFillColor, + pointBackgroundColor: matchLineColor, + pointRadius:0 + } + ] : [ + { + label:'Match', + data:[], + fill:true, + borderColor: matchLineColor, + backgroundColor: matchFillColor, + pointBackgroundColor: matchLineColor, + pointRadius:0 + } + ]; + + return new Chart(ctx,{ + type:'radar', + data:{ labels, datasets }, + options:{ + responsive:true, + maintainAspectRatio:false, + scales:{ + r:{ + min:0, max:80, + ticks:{ display:false, stepSize:20 }, + pointLabels:{ font:{ size:isBest?getFontSize():getBottomChartFontSize(), weight:'bold' }, color:'#FFF' }, + angleLines:{ color:radialLineColor }, + grid:{ drawticks:false, drawOnChartArea:false, circular:false } + } + }, + plugins:{ + tooltip:{ enabled:false }, + legend:{ display:false } + } + } + }); + }); + + // === Update charts & DOM === + function updateCharts(){ + const matches = getTopMatches(); + + matches.forEach(([name, prof], idx)=>{ + const chart = charts[idx]; + if(idx===0){ + chart.data.datasets[0].data = [...userData]; + chart.data.datasets[1].data = prof.values; + chart.data.datasets[1].label = name; + } else { + chart.data.datasets[0].data = prof.values; + chart.data.datasets[0].label = name; + } + chart.options.scales.r.pointLabels.font.size = idx===0 ? getFontSize() : getBottomChartFontSize(); + chart.update('none'); + + // Update main card + document.getElementById(`name-${idx+1}`).textContent = name; + for (let i = 0; i <= 4; i++) { + const el = document.getElementById(`info${i}-${idx+1}`); + if (el) el.textContent = prof[`info_text${i}`] || ''; + } + + // Update popup + const popupName = document.getElementById(`popup-name-${idx+1}`); + if (popupName) popupName.textContent = name; + + for (let i = 0; i <= 4; i++) { + const popupEl = document.getElementById(`popup-info${i}-${idx+1}`); + if (popupEl) popupEl.textContent = prof[`info_text${i}`] || ''; + } + }); + } + + updateCharts(); + + // === Reset button === + document.getElementById('resetBtn').addEventListener('click', ()=>{ + userData = [...window.appData.initialUserData]; + updateCharts(); + }); + + // === Dragging top chart === + const topChart = charts[0]; + const topCanvas = document.getElementById('chart-1'); + topCanvas.style.touchAction = 'none'; + let draggingIndex = null; + + topCanvas.addEventListener('pointerdown', e => { + const points = topChart.getElementsAtEventForMode(e, 'nearest', {intersect:true}, true); + if(points.length && points[0].datasetIndex===0) draggingIndex = points[0].index; + }); + + topCanvas.addEventListener('pointermove', e => { + if(draggingIndex===null) return; + const rect = topCanvas.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + const cx = rect.width/2; + const cy = rect.height/2; + const dx = x-cx; + const dy = cy-y; + const dist = Math.sqrt(dx*dx + dy*dy); + const rMax = Math.min(rect.width, rect.height)/2 * 0.9; + let value = (dist/rMax)*80; + value = Math.min(Math.max(Math.round(value),0),80); + userData[draggingIndex] = value; + updateCharts(); + }); + + topCanvas.addEventListener('pointerup', ()=> draggingIndex=null); + topCanvas.addEventListener('pointerleave', ()=> draggingIndex=null); + + // bottom charts scroll normally + [2,3].forEach(i => { + document.getElementById(`chart-${i}`).style.touchAction='auto'; + }); + + window.addEventListener('resize', updateCharts); +}); diff --git a/static/js/popup.js b/static/js/popup.js new file mode 100755 index 0000000..794d172 --- /dev/null +++ b/static/js/popup.js @@ -0,0 +1,16 @@ +function togglePopup(event, id) { + event.stopPropagation(); + const popup = document.getElementById(id); + + if (popup.classList.contains('active')) { + popup.classList.remove('active'); + } else { + document.querySelectorAll('.popup').forEach(p => p.classList.remove('active')); + popup.classList.add('active'); + } +} + +// Close on outside click +document.addEventListener('click', () => { + document.querySelectorAll('.popup').forEach(p => p.classList.remove('active')); +}); diff --git a/static/scc.png b/static/scc.png new file mode 100644 index 0000000..d1b0de5 Binary files /dev/null and b/static/scc.png differ diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..9718569 --- /dev/null +++ b/static/style.css @@ -0,0 +1,687 @@ +/* === Reset & Base Styles === */ +* { box-sizing: border-box; } + +html, body { + margin: 0; + padding: 0; + height: 100%; + width: 100%; + font-family: Arial, sans-serif; + color: #FFFFFF; + overflow-x: hidden; +} + +h1, h2, h3 { + line-height: 1.3; + font-weight: bold; + text-align: center; + margin: 0 0 0 0; +} + +h1 { + font-size: 2.25rem; + } + +.top-match h2 { + font-size: 4rem; +} + +.top-match h3 { + margin-top: 0.25em; + font-size: 2rem; +} + +.bottom-row h3 { + margin-top: 0.25em; + font-size: 1.5rem; +} + +.top-match p { + font-size: 1.5rem; + line-height: 1; + margin: 0.5em 0 0.5em; + color: #FFFFFF; +} + +.bottom-row h2 { + font-size: 3rem; +} + +.bottom-row p { + font-size: 1.25rem; + margin: 0 0 0; +} + +.info-block { + #border: 3px solid #BFB531; + border-radius: 25px; + padding: 0.5em 0.5em; + margin: 0.5em 4em; + background-color: rgba(191,181,49,0.1); + text-align: center; + box-sizing: border-box; +} + +.profile-info-block { + width: 100%; +} + +/* ABOUT PAGE*/ +.aboutbody { + display: flex; + flex-direction: column; +} + +.abouttext a, p { + font-size: 1.25rem; + text-align: center; + text-decoration: none; +} + +.abouttextcontainer { + margin-top: 3vh; + margin-bottom: 3vh; +} + +.aboutlink { + color: #BFB531; +} + +.made { + font-weight: bold; + color: #FF6600; +} + +.aboutlogocontainer { + display: flex; + justify-content: center; + align-items: center; +} + +.aboutlogo { + display: flex; + justify-content: center; + align-items: center; + overflow: hidden; + flex: 1; + max-height: 150px; + max-width: 95%; + width: auto; + margin-top: 1em; + margin-bottom: 3em; +} + +.aboutlogo img { + max-height: 100%; + max-width: 100%; + object-fit: contain; +} + +input[type="hidden"] { display: none; } + +/* === Body & Sticky Footer === */ +body { + display: flex; + flex-direction: column; + min-height: 95vh; /* ensures footer sticks at bottom */ + background: #1A202C; +} + +/* === Page container fills remaining space === */ +.page-container { + flex: 1; + display: flex; + flex-direction: column; + width: 100%; + padding: 20px; +} + +/* === Logo === */ +.logo-container { + display: flex; + justify-content: center; + align-items: center; +} + +.logo { + width: 100%; + height: auto; + max-width: 480px; +} + +/* === Grid Layout (profiles page) === */ +.profile-grid { + display: grid; + padding: 20px; + grid-template-columns: repeat(auto-fit, minmax(420px, 420px)); + gap: 25px; + width: 100%; + margin: 0 auto; + justify-content: center; +} + +.profile-grid canvas { + width: 100%; + max-width: 420px; + height: auto; + max-height: 420px; + display: block; + object-fit: contain; +} + +/* === Footer === */ +.footer { + padding: 15px; + flex-shrink: 0; /* ensure footer doesn't shrink */ + text-align: center; +} +.footer p { + font-size: 1rem; + color: #FFFFFF; +} + +/* === Cards === */ +.profile-card { + background: rgba(0,0,0,0.39); + border-radius: 8px; + box-shadow: 0 2px 6px rgba(0,0,0,0.12); + display: flex; + flex-direction: column; + width: 100%; +} + +.profile-profile-card { + padding: 1em; + flex-direction: column; + align-items: center; + background: rgba(0,0,0,0.39); + border-radius: 8px; + box-shadow: 0 2px 6px rgba(0,0,0,0.12); + display: flex; + max-width: 420px; + max-height: 690px; +} + +.profile-profile-card canvas { + display: block; + width: 100%; + height: auto; + max-width: 420px; + max-height: 420px; + margin: 1em auto; +} + +/* Canvas sizing */ +.profile-card canvas { + margin: 3em 0; + touch-action: pan-y; + #height: auto; + max-height: 840px; + margin: 0.5em; + display: block; + object-fit: contain; + z-index: 0; +} + +/* === Matches container === */ +.matches-container { + display: flex; + flex-direction: column; + gap: 18px; + align-items: stretch; + width: 100%; +} + +/* Top row: full width */ +.best-row { + width: 100%; +} + +/* Bottom row: use CSS grid for auto-equal height columns */ +.bottom-row { + display: grid; + grid-template-columns: 1fr; /* mobile default: one column */ + gap: 15px; + width: 100%; + justify-content: center; + #align-items: stretch; +} + +/* Profile-card inside grid cells fill the cell */ +.bottom-row .profile-card { + width: 100%; + height: auto; + align-items: stretch; + display: flex; + flex-direction: column; + text-align: center; +} + +/* Top match card styling */ +.best-card { + border: 2px solid #BFB531; + box-shadow: 0 6px 24px rgba(0,0,0,0.18); + width: 100%; + display: flex; + flex-direction: column; +} + +/* Side match default layout (stacked) */ +.side-match { + width: 100%; + display: flex; + flex-direction: column; +} + +/* === Buttons === */ +button[type="submit"], +button.magenta-button { + display: block; + margin: 16px 0; + padding: 11px 22px; + font-size: 1em; + font-weight: 700; + color: rgba(0,0,0,1); + border: none; + border-radius: 8px; + cursor: pointer; + box-shadow: 0 4px 10px rgba(0,0,0,0.18); + transition: background-color 0.18s ease, transform 0.12s ease; +} + +button[type="submit"] { background-color: #808080; } +button[type="submit"]:hover { background-color: #FFFFFF; transform: translateY(1px); } +button[type="submit"]:active { background-color: #000000; transform: translateY(0); } + +button.magenta-button { background-color: #BFB531; } +button.magenta-button:hover { background-color: #9E9928; transform: translateY(1px); } +button.magenta-button:active { background-color: #E6E66B; transform: translateY(0); } + +.button-row { + display: flex; + justify-content: center; + gap: 12px; + flex-wrap: wrap; + margin: 0.5em 0; + width: 100%; +} + +/* Popup hidden initially */ +.popup { + display: none; + position: fixed; + inset: 0; + background: rgba(0,0,0,0.6); + z-index: 9999; + justify-content: center; + align-items: center; +} + +/* Show when active */ +.popup.active { + display: flex; +} + +.popup-content { + background: #262626; + padding: 1.5em 0; + border-radius: 12px; + border: 2px solid #BFB531; + max-width: 500px; + width: 90%; + color: #FFF; + text-align: center; + box-shadow: 0 8px 28px rgba(0,0,0,0.5); + font-size: 2rem; +} + +.glow-text { + text-shadow: + 0 0 5px #BFB531, + 0 0 10px #BFB531, + 0 0 20px #BFB531, + 0 0 40px #BFB531; +} + +.glow-info { + padding-bottom: 0.25em; + text-shadow: + 0 0 5px #000000, + 0 0 10px #000000, + 0 0 20px #000000, + 0 0 40px #000000; +} + +.info-text { + padding: 0.1em 0; + margin: 0.1em 0; +} + +.popup-content h3 { + font-size: 3rem; + margin: 0.5em; +} + +.popup-content h4 { + font-size: 2rem; + margin: 0.5em 0 0 0; + color: rgba(181,191,49,0.42); +} + +.popup-content p { + font-size: 1.5rem; + margin: 0 0 1em 0; +} + +/* Circle "i" info icon */ +.info-icon { + display: inline-flex; + justify-content: center; + align-items: center; + width: 69px; + height: 69px; + border-radius: 50%; + background-color: rgba(191,181,49,0.25); /* golden circle */ + color: #BFB531; + font-weight: bold; + cursor: pointer; + position: relative; + font-size: 42px; +} + +.profile-info-icon { + width: 42px; + height: 42px; + font-size: 2rem; +} + +.bottom-row .info-icon { + width: 32px; /* smaller circle */ + height: 32px; + margin-right: 0; +} + +.bottom-row .info-icon::before { + font-size: 24px; +} + +.info-icon::before { + content: "i"; /* the actual "i" */ + font-family: Arial, sans-serif; + line-height: 1; + font-weight: 900; +} + +/* Hover effect */ +.info-icon:hover { + background-color: #FFF; +} + +.title-with-icon { + display: block; + #align-items: center; + margin: 0.25em 1em 0.25em 1em; +} + +/* === Animations === */ +@keyframes gradientMove { + 0% { background-position: 0% 50%; } + 50% { background-position: 100% 50%; } + 100% { background-position: 0% 50%; } +} + +/* === Small mobile tweaks (≤600px) === */ +@media (max-width: 600px) { + .profile-card { + width: 100%; + max-width: 100%; + padding: 0.5em; + margin: 0; + } + + .profile-card canvas { + max-height: 360px; + } + .top-match h2, .bottom-row h2 { + font-size: 1.5rem; + } + + .top-match h3, .bottom-row h3 { + font-size: 1rem; + margin: 0; + padding: 0; + } + .top-match p, .bottom-row p { + font-size: 1rem; + } + .info-block { + padding: 0.25em 2em; + margin: 0.25em; + } + .button-row { + gap: 5px; + } + button.magenta-button { + padding: 5px 10px; + } + +input[type="hidden"] { display: none; } + .info-icon { + width: 32px; + height: 32px; + } + .info-icon::before { + font-size: 17px; + } + .bottom-row .info-icon { + width: 24px; + height: 24px; + } + .bottom-row .info-icon::before { + font-size: 13px; + } + .popup-content h3 { + font-size: 2rem; + margin: 0.5em; + } + + .popup-content h4 { + font-size: 1.5rem; + margin: 0.5em 0 0 0; + color: rgba(181,191,49,0.42); + } + + .popup-content p { + font-size: 1rem; + margin: 0 0 1em 0; + } + .cards-container { + grid-template-columns: 1fr; + align-items: center; + } + .footer p { + font-size: 0.5rem; + } +} + +/* === Large screen width (≥1000px) === */ +@media (min-width: 1000px) { + /* Bottom row: two equal columns */ + .bottom-row { + grid-template-columns: repeat(2, 1fr); + justify-content: center; + } + + /* Top card canvas larger */ + .best-card canvas { + max-height: 50vh; + } + + /* Side match canvas */ + .bottom-row .profile-card canvas { + } + .cards-container { + grid-template-columns: repeat(2, 1fr); + align-items: center; + } +} + +/* === Landscape Center Hero + Sidecards layout === */ +@media only screen and (orientation: landscape) { + .aboutbody { + flex-direction: row; + align-items: flex-start; + justify-content: center; + } + .abouttextcontainer { + min-width: 500px; + } + .matches-container { + display: grid; + grid-template-columns: 1fr 2fr 1fr; /* left, center, right */ + gap: 20px; + align-items: start; + } + .footer { + display: flex; + justify-content: center; + left: 0; + width: 100%; + bottom: 0; + position: fixed; + } + .best-row { + grid-column: 2; + grid-row: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + } + .best-card { + max-width: 50vw; + width: 100%; + } + .info-icon { + height: 39px; + width: 39px; + font-size: 1.5rem; + } + .title-with-icon h2 { + font-size: 2rem; + } + .top-match p { + font-size: 1.25rem; + } + .title-with-icon p { + font-size: 1.25rem; + } + .profile-card h3 { + font-size: 1.5rem; + } + + /* Place children explicitly */ + .best-row { + grid-column: 2; + grid-row: 1; + display: flex; + flex-direction: column; + justify-content: center; + margin: 0; + padding: 0; + } + + .bottom-row { + display: contents; /* flatten so its children can go left/right */ + } + + #card-2 { + grid-column: 1; + grid-row: 1; + align-self: center; + } + + #card-3 { + grid-column: 3; + grid-row: 1; + align-self: center; + } + + /* Scale down side cards relative to the hero */ + #card-2, #card-3 { + transform: scale(0.85); + } + + .best-card canvas { + max-height: 39vh; + } + + .profile-card canvas { + max-height: 45vh; + } +} +/* === Category Cards === */ +.cards-container { + display: grid; + gap: 15px; + padding: 20px; + width: 90%; + max-width: 1000px; + margin: 0 auto; +} + +.category-card { + background: rgba(0, 0, 0, 0.24); + border-radius: 10px; + padding: 20px; + text-align: center; + cursor: pointer; + border: 3px solid transparent; + transition: all 0.3s ease; + user-select: none; + position: relative; +} + +.category-card.active { + border-color: #; + background: rgba(0, 0, 0, 0.69); +} + +.category-card.emphasis { + border-color: #BFB531; + background: rgba(0, 0, 0, 0.69); +} + +.category-card .checkmark { + position: absolute; + top: 8px; + right: 16px; + font-size: 25px; + font-weight: bold; + color: #BFB531; + opacity: 1; +} + +.category-card .checkmark span { + opacity: 0.39; +} + +.category-card.active .checkmark span.first { + opacity: 1; +} + +.category-card.emphasis .checkmark span.first, +.category-card.emphasis .checkmark span.second { + opacity: 1; +} +/* === Confetti canvas fix === */ +canvas.confetti-canvas { + display: block; + position: fixed; + top: 0; + left: 0; + width: 100% !important; + height: 100% !important; + pointer-events: none; /* don’t block buttons/links */ + z-index: 9999; /* stays above your charts/cards */ +} diff --git a/templates/about.html b/templates/about.html new file mode 100755 index 0000000..2481021 --- /dev/null +++ b/templates/about.html @@ -0,0 +1,42 @@ + + + + + + cannamatch about + + + + +
+

HUEEEEEEEEEEEEED 42069

+
+ + + +
+
+ + + + +
+
+
+

Made for:

+
+ +
+

Learn about Swiss Cannabis Research here.

+
+
+

Inspired by:

+
+ +
+

Learn about the Terpene Wheel here.

+
+
+
+ + diff --git a/templates/inactive.html b/templates/inactive.html new file mode 100755 index 0000000..72deeb1 --- /dev/null +++ b/templates/inactive.html @@ -0,0 +1,139 @@ + + + + + +cannamatch interactive + + + + + + + + +
+
+ + + +
+ +
+ + + + + +
+ +
+ +
+
+

Best

+
+
+

+ +
+

+

+
+ +
+ + + +
+ + +
+ +
+

Runner-up

+
+
+

+ +
+

+

+
+ +
+ + + + + +
+

Maybe...

+
+
+

+ +
+

+

+
+ +
+ + + +
+
+
+ + + + + diff --git a/templates/profiles.html b/templates/profiles.html new file mode 100755 index 0000000..dfbee59 --- /dev/null +++ b/templates/profiles.html @@ -0,0 +1,102 @@ + + + + + + cannamatch profiles + + + + + + + +
+
+ + + +
+
+ + + + +
+
+ {% for name, values in profiles.items() %} +
+
+
+

{{ name }}

+ +

{{ values.info_text0 }}

+

{{ values.info_text2 }}

+
+
+ + +
+ {% endfor %} +
+
+ + + + + diff --git a/templates/quiz.html b/templates/quiz.html new file mode 100755 index 0000000..33a7815 --- /dev/null +++ b/templates/quiz.html @@ -0,0 +1,67 @@ + + + + + + cannamatch quiz + + + + +
+
+ + + +
+
+ + + + +
+

What are you looking for?

+
+
+ {% set categories = [ + {'name': 'Energy', 'desc': 'Boost endurance and alertness.'}, + {'name': 'Calm', 'desc': 'Stay grounded and centered.'}, + {'name': 'Relax', 'desc': 'Ease tension in body and mind.'}, + {'name': 'Sleep', 'desc': 'Support restfulness and recovery.'}, + {'name': 'Focus', 'desc': 'Enhance concentration and clarity.'}, + {'name': 'Inspire', 'desc': 'Spark creativity and new ideas.'} + ] %} + {% for cat in categories %} +
+

{{ cat.name }}

+

{{ cat.desc }}

+ +
+ ++ +
+
+ {% endfor %} +
+
+ +
+
+
+ + + diff --git a/templates/result.html b/templates/result.html new file mode 100755 index 0000000..059762c --- /dev/null +++ b/templates/result.html @@ -0,0 +1,299 @@ + + + + + + cannamatch result + + + + + + + + + + + + + +
+ + +
+ + + +
+ + +
+ + + + +
+ +
+ +
+
+

Best

+
+
+

{{ top_matches[0][0] if top_matches|length > 0 else '' }}

+ +
+ {% if top_matches|length > 0 %} +

{{ top_matches[0][1].get('info_text0','') }}

+

{{ top_matches[0][1].get('info_text2','') }}

+ {% endif %} +
+ +
+ + + +
+ + +
+ +
+

Runner-up

+
+
+

{{ top_matches[1][0] if top_matches|length > 1 else '' }}

+ +
+ {% if top_matches|length > 1 %} +

{{ top_matches[1][1].get('info_text0','') }}

+

{{ top_matches[1][1].get('info_text2','') }}

+ {% endif %} +
+ +
+ + + + + +
+

Maybe...

+
+
+

{{ top_matches[2][0] if top_matches|length > 2 else '' }}

+ +
+ {% if top_matches|length > 2 %} +

{{ top_matches[2][1].get('info_text0','') }}

+

{{ top_matches[2][1].get('info_text2','') }}

+ {% endif %} +
+ +
+ + + +
+
+
+ + + +