diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 940bd98..528aa6e 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -467,7 +467,7 @@ h1, h2, h3, h4, h5, h6 { position: absolute; bottom: 0; left: 0; - width: clamp(150px, 20vw, 280px); + width: clamp(800px, 20vw, 280px); z-index: 2; } diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 483c840..49caa6e 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -6,6 +6,8 @@ <%= content_for?(:title) ? yield(:title) : "Reboot" %> <%= csrf_meta_tags %> + + diff --git a/app/views/pages/shop.html.erb b/app/views/pages/shop.html.erb index 775103e..2d9de2d 100644 --- a/app/views/pages/shop.html.erb +++ b/app/views/pages/shop.html.erb @@ -4,39 +4,80 @@ <%= render "shared/navbar" %>
-
+

Shop

Recent Purchases
-
-
+
+ Working progress! We're building the shop page, more items will be added soon! +
- - - + + diff --git a/app/views/pages/signin.html.erb b/app/views/pages/signin.html.erb index 05b0ec3..b456fcb 100644 --- a/app/views/pages/signin.html.erb +++ b/app/views/pages/signin.html.erb @@ -2,16 +2,25 @@ <% content_for :page, "signin" %>
- Hack Club 2025 + + +
- - <%= form_tag("/auth/hack_club", method: :post) do %> - + <% end %> + +
Mole diff --git a/db/schema.rb b/db/schema.rb index dc736dd..d7e21df 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -97,9 +97,7 @@ t.string "last_name" t.date "birthday" t.decimal "balance", precision: 10, scale: 2, default: "0.0" - t.string "hackatime_uid" t.index ["email"], name: "index_users_on_email", unique: true - t.index ["hackatime_uid"], name: "index_users_on_hackatime_uid", unique: true t.index ["provider", "uid"], name: "index_users_on_provider_and_uid", unique: true t.index ["role"], name: "index_users_on_role" end diff --git a/img/bolt.png b/img/bolt.png new file mode 100644 index 0000000..498e457 Binary files /dev/null and b/img/bolt.png differ diff --git a/img/signin/startButton.PNG b/img/signin/startButton.PNG new file mode 100644 index 0000000..1a0c4bd Binary files /dev/null and b/img/signin/startButton.PNG differ diff --git a/public/javascripts/application.js b/public/javascripts/application.js index 845df34..78b066e 100644 --- a/public/javascripts/application.js +++ b/public/javascripts/application.js @@ -66,6 +66,121 @@ }); } + /** + * Shop page interactions: + * - Clicking a category item on the left shows its detail panel on the right + * - Visually toggles the active state + */ + function initShopPage() { + const buttons = document.querySelectorAll(".shop-category-item"); + const panels = document.querySelectorAll(".shop-item-detail"); + if (!buttons.length || !panels.length) return; + + // Modal elements + const modal = document.getElementById("shop-item-modal"); + const modalClose = modal?.querySelector(".modal__close"); + const modalBackdrop = modal?.querySelector(".modal__backdrop"); + const modalTitle = document.getElementById("shop-modal-title"); + const modalImage = document.getElementById("shop-modal-image"); + const modalDesc = document.getElementById("shop-modal-desc"); + const modalPrice = document.getElementById("shop-modal-price"); + const modalQty = document.getElementById("shop-modal-qty"); + const modalWarning = document.getElementById("shop-modal-warning"); + const modalShortage = document.getElementById("shop-modal-shortage"); + const modalForm = document.getElementById("shop-modal-form"); + const modalItemId = document.getElementById("shop-modal-item-id"); + const modalVariant = document.getElementById("shop-modal-variant"); + const modalBuy = document.getElementById("shop-modal-buy"); + const userBalance = Number(modal?.dataset.userBalance || 0); + + function activate(targetId) { + buttons.forEach((b) => b.classList.remove("is-active")); + panels.forEach((p) => p.classList.remove("is-active")); + const btn = Array.from(buttons).find((b) => b.dataset.target === targetId); + const panel = document.getElementById(targetId); + if (btn) btn.classList.add("is-active"); + if (panel) panel.classList.add("is-active"); + } + + function updateModalTotals() { + if (!modal) return; + const baseBolts = Number(modal.dataset.baseBolts || 0); + const grant = Number(modal.dataset.grant || 0); + const name = modal.dataset.itemName || "Item"; + const qty = Math.max(1, Number(modalQty?.value || 1)); + const totalBolts = baseBolts * qty; + if (modalPrice) modalPrice.textContent = String(totalBolts); + if (modalDesc) { + modalDesc.textContent = `This item provide a ${grant}$ HCB card grant to spend on a ${name}. Quantity: ${qty}.`; + } + // enable/disable buy based on computed total bolts + const canBuy = Boolean(modalItemId?.value) && userBalance >= totalBolts; + if (modalBuy) modalBuy.disabled = !canBuy; + if (modalWarning) { + const shortage = Math.max(0, totalBolts - userBalance); + modalWarning.style.display = canBuy ? "none" : "block"; + if (modalShortage) modalShortage.textContent = String(shortage); + } + } + + function openModal(data) { + if (!modal) return; + const variantLower = (data.variant || "").toLowerCase(); + const display = data.display || (data.name ? `${data.name}_${variantLower}` : 'Item'); + modalTitle.textContent = display; + modalImage.src = data.img || "/images/signin/hackclub.svg"; + // store computation inputs on modal dataset + modal.dataset.baseBolts = String(Number(data.bolts || 0)); + modal.dataset.grant = String(Number(data.grant || 0)); + modal.dataset.itemName = data.name || "Item"; + // reset qty to 1 each open + if (modalQty) modalQty.value = "1"; + // compute totals, description, and buy state + updateModalTotals(); + modalItemId.value = data.itemId || ""; + modalVariant.value = data.variant || ""; + modal.classList.remove("modal--hidden"); + } + + function closeModal() { + if (!modal) return; + modal.classList.add("modal--hidden"); + } + + document.addEventListener("click", (e) => { + const btn = e.target.closest(".price-btn"); + if (btn) { + const data = { + itemId: btn.dataset.itemId, + name: btn.dataset.name, + variant: btn.dataset.variant, + display: btn.dataset.display, + price: Number(btn.dataset.price || 0), + grant: Number(btn.dataset.grant || 0), + bolts: Number(btn.dataset.bolts || 0), + img: btn.dataset.img, + desc: btn.dataset.desc, + }; + openModal(data); + } + }); + + // React to quantity changes + modalQty?.addEventListener("input", updateModalTotals); + + modalClose?.addEventListener("click", closeModal); + modalBackdrop?.addEventListener("click", closeModal); + document.addEventListener("keydown", (e) => { + if (e.key === "Escape") closeModal(); + }); + + buttons.forEach((btn) => { + btn.addEventListener("click", () => { + activate(btn.dataset.target); + }); + }); + } + // Page initialization document.addEventListener("DOMContentLoaded", () => { initSignout(); @@ -74,5 +189,8 @@ if (page === "projects") { initProjectModal(); } + if (page === "shop") { + initShopPage(); + } }); })(); diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css index d353727..dc61f4e 100644 --- a/public/stylesheets/application.css +++ b/public/stylesheets/application.css @@ -547,6 +547,223 @@ a.project-row:hover { font-size: 13px; } +/* New Shop layout (category left, detail right) */ +.shop-layout { + display: grid; + grid-template-columns: 280px 1fr; + gap: 20px; +} +.shop-heading { + font-family: 'Jolly Lodger', cursive; + font-size: 28px; + margin: 0 0 10px; +} +.shop-categories { + position: sticky; + top: 20px; + height: fit-content; +} +.shop-category-list { + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-direction: column; + gap: 12px; +} +.shop-category-item { + width: 100%; + text-align: left; + background: #f5f5f3; + border: 1px solid var(--border); + color: var(--ink); + padding: 12px 16px; + border-radius: 12px; + cursor: pointer; + font-weight: 700; + font-size: 18px; + transition: background-color 160ms ease, box-shadow 160ms ease, transform 120ms ease; +} +.shop-category-item:hover { + background: #efefeb; + box-shadow: 0 6px 12px var(--shadow); + transform: translateY(-1px); +} +.shop-category-item.is-active { + background: #e8dcc8; + border-color: var(--gold); + box-shadow: 0 0 0 2px rgba(214, 161, 97, 0.2); +} +.shop-items { + padding: 16px; +} +.shop-items__header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 8px; +} +.shop-cart { + font-size: 0; +} +/* (Removed rules button animation) */ +.shop-item-detail { + display: none; + padding: 8px 0 0; + margin-top: 8px; +} +.shop-item-detail.is-active { + display: block; +} +.shop-item-title { + margin: 0 0 6px; + font-size: 28px; +} +.shop-item-desc { + margin: 0 0 12px; + color: var(--ink-muted); +} +.shop-item-image { + width: 100%; + height: 180px; + border-radius: 10px; + background: #f0f0f0; + display: flex; + align-items: center; + justify-content: center; + font-size: 48px; + object-fit: cover; +} +.shop-item-image img { + width: 100%; + height: 100%; + object-fit: cover; + border-radius: 10px; +} +.shop-item-image--placeholder { + background: linear-gradient(135deg, var(--paper-alt), #f0f0ee); +} +.shop-item-detail__variants { + display: grid; + gap: 12px; +} +.shop-variant { + display: grid; + grid-template-columns: 120px 1fr auto; + gap: 12px; + align-items: center; + padding: 12px; + border-radius: 12px; + background: var(--paper); + border: 1px solid var(--border); +} +.shop-variant__thumb { + width: 120px; + height: 90px; + border-radius: 10px; + background: var(--paper-alt); + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + border: 1px solid var(--border); +} +.shop-variant__thumb img { + width: 100%; + height: 100%; + object-fit: cover; +} +.shop-variant__meta { + display: flex; + flex-direction: column; + gap: 8px; + align-items: flex-end; + justify-self: end; +} +.shop-variant__name { + font-family: 'Jolly Lodger', cursive; + font-size: 22px; + color: var(--ink); +} +.shop-variant__price { + font-weight: 800; + color: #e05a2a; + font-size: 20px; +} +.shop-variant__priceBox { + display: inline-flex; + align-items: center; + gap: 8px; + background: #efd1a4; + color: #3a2a18; + padding: 8px 12px; + border-radius: 12px; + border: 1px solid var(--border); + cursor: pointer; +} +.bolt-icon { + width: 20px; + height: 20px; + object-fit: contain; + vertical-align: middle; +} +.bolt-svg { + width: 20px; + height: 20px; +} +.shop-cart .bolt-svg { + width: 24px; + height: 24px; +} +.shop-cart .bolt-icon { + width: 24px; + height: 24px; +} +.shop-variant__pill { + justify-self: start; + padding: 6px 10px; + border-radius: 10px; + font-weight: 800; + font-size: 16px; +} +.shop-variant__pill--economic { + background: #e7f4e7; + color: #326f3d; +} +.shop-variant__pill--quality { + background: #e5f2ff; + color: #1e4f7a; +} +.shop-variant__pill--standard { + background: #f3ecd8; + color: #6b4f1d; +} +.shop-variant__pill--advanced { + background: #f0e7ff; + color: #4e2f91; +} +.shop-variant__pill--professional { + background: #e6f6ef; + color: #0c6b4a; +} + +@media (max-width: 900px) { + .shop-layout { + grid-template-columns: 1fr; + } + .shop-categories { + position: static; + } + .shop-item-detail { + grid-template-columns: 1fr; + } + .shop-variant { + grid-template-columns: 100px 1fr auto; + } + .shop-variant .purchase-form { + grid-column: 1 / -1; + } +} /* Purchases */ .purchases-list { display: flex; @@ -688,7 +905,8 @@ a.project-row:hover { /* Signin Page */ [data-page="signin"] { - background: transparent; + background: var(--bg) url('/images/signin/background.PNG') no-repeat center center fixed; + background-size: cover; min-height: 100vh; overflow: hidden; } @@ -706,8 +924,8 @@ a.project-row:hover { .signin-flag { position: absolute; top: 16px; - left: 16px; - width: 120px; + left: 0px; + width: 200px; z-index: 10; } @@ -721,14 +939,21 @@ a.project-row:hover { } .signin-crane-text { - width: clamp(300px, 80vw, 800px); + width: clamp(550px, 90vw, 1100px); height: auto; - margin-bottom: 20px; + margin-bottom: 0px; order: 1; } +/* Start button image */ +.signin-start-img { + width: clamp(100px, 22vw, 200px); + height: auto; +} + .signin-content form { - order: 4; + order: 2; + margin-top: -150px; } .signin-subtitle { @@ -750,7 +975,7 @@ a.project-row:hover { color: #000; box-shadow: 0 2px 4px rgba(0,0,0,0.1); transition: transform 0.2s; - margin-top: 16px; + margin-top: 0; } .signin-btn:hover { @@ -761,7 +986,7 @@ a.project-row:hover { position: absolute; bottom: 0; left: 0; - width: clamp(150px, 20vw, 280px); + width: clamp(400px, 20vw, 280px); z-index: 2; }