shop-modern.php

main 198 lines · 12.6 KB Raw
Alex Alex Commit Initial commit 01/10/2026 09:20
1<?php
2$znxShopH = static fn($v): string => htmlspecialchars((string)$v, ENT_QUOTES, 'UTF-8');
3
4$shopSession = '';
5if (($loggedin ?? false) === true) {
6 $shopSession = bin2hex(random_bytes(32));
7 $_SESSION['shop_session'] = $shopSession;
8}
9
10if (empty($shop['enabled'])) {
11 echo '<h1>' . t('buypoints.disabled') . '</h1><p>' . t('buypoints.disabled_text') . '</p>';
12 return;
13}
14
15$outfitFallbackIds = array(136,137,138,139,140,141,142,147,148,149,150,155,156,157,158,252,269,270,279,288,324,336,366,431,433,464,466,471,513,514,542,128,129,130,131,132,133,134,143,144,145,146,151,152,153,154,251,268,273,278,289,325,335,367,430,432,463,465,472,512,516,541);
16$shopCategories = array(
17 'outfits' => array('label' => t('shop.cat_outfits'), 'items' => array()),
18 'mounts' => array('label' => t('shop.cat_mounts'), 'items' => array()),
19 'items' => array('label' => t('shop.cat_items'), 'items' => array()),
20 'premium' => array('label' => t('shop.cat_premium'), 'items' => array()),
21 'misc' => array('label' => t('shop.cat_misc'), 'items' => array()),
22);
23
24foreach ((array)$shop_list as $offerId => $offer) {
25 switch ((int)($offer['type'] ?? 0)) {
26 case 1: $shopCategories['items']['items'][$offerId] = $offer; break;
27 case 2: $shopCategories['premium']['items'][$offerId] = $offer; break;
28 case 5: $shopCategories['outfits']['items'][$offerId] = $offer; break;
29 case 6: $shopCategories['mounts']['items'][$offerId] = $offer; break;
30 default: $shopCategories['misc']['items'][$offerId] = $offer; break;
31 }
32}
33
34$visibleCategories = array_filter($shopCategories, static fn($category): bool => !empty($category['items']));
35$firstCategory = true;
36
37$renderImage = static function (array $offer, string $category) use ($config, $outfitFallbackIds, $znxShopH): string {
38 $type = (int)($offer['type'] ?? 0);
39 $itemId = $offer['itemid'] ?? 0;
40 $count = (int)($offer['count'] ?? 0);
41
42 if ($category === 'outfits' && !empty($config['show_outfits']['shop'])) {
43 $ids = is_array($itemId) ? array_values($itemId) : array($itemId);
44 $html = '';
45 foreach (array_slice($ids, 0, 2) as $id) {
46 $html .= '<img class="znx-shop-card__img is-outfit" src="' . $znxShopH($config['show_outfits']['imageServer']) . '?id=' . (int)$id . '&addons=' . $count . '&head=78&body=68&legs=58&feet=76" alt="">';
47 }
48 return $html;
49 }
50
51 if ($category === 'mounts' && !empty($config['show_outfits']['shop'])) {
52 $outfitId = $outfitFallbackIds[array_rand($outfitFallbackIds)];
53 return '<img class="znx-shop-card__img is-mount" src="' . $znxShopH($config['show_outfits']['imageServer']) . '?id=' . (int)$outfitId . '&addons=3&head=78&body=68&legs=58&feet=76&mount=' . (int)$itemId . '&direction=2" alt="">';
54 }
55
56 if (!empty($config['shop']['showImage']) && (int)$itemId > 0 && $type !== 3 && $type !== 4) {
57 return '<img class="znx-shop-card__img" src="' . $znxShopH(znote_item_image_url((int)$itemId)) . '" alt="">';
58 }
59
60 $icon = $type === 2 ? 'VIP' : ($type === 3 ? 'SEX' : ($type === 4 ? 'NAME' : 'ITEM'));
61 return '<span class="znx-shop-card__placeholder" aria-hidden="true">' . $icon . '</span>';
62};
63
64$offerMeta = static function (array $offer, string $category) use ($znxShopH): string {
65 $count = (int)($offer['count'] ?? 0);
66 if ($category === 'premium') return $znxShopH(t('shop.days', array('count' => $count)));
67 if ($category === 'items' && $count > 1) return $count . 'x';
68 if ($category === 'misc') return $count === 0 ? t('common.unlimited') : $count . 'x';
69 if ($category === 'outfits' && $count > 0) return 'Addons ' . $count;
70 return '';
71};
72?>
73
74<style>
75.znx-shop-modern{--shop-surface:var(--box-inner-bg,var(--box-bg,var(--nz-panel,var(--wc-bg-2,var(--z-panel-2,var(--s-panel,var(--primary,rgb(30,33,40))))))));--shop-surface-2:color-mix(in srgb,var(--shop-surface) 82%,#000);--shop-well:color-mix(in srgb,var(--shop-surface) 65%,#000);--shop-text:var(--font-color,var(--text,var(--nz-text,var(--wc-text,var(--z-text,var(--s-text,rgb(155,162,177)))))));--shop-muted:color-mix(in srgb,var(--shop-text) 58%,transparent);--shop-border:var(--border,var(--box-inner-border,var(--box-border,var(--nz-border-soft,var(--wc-line,var(--z-border,var(--s-border2,rgb(19,20,23))))))));--shop-accent:var(--accent,var(--link,var(--nz-accent,#d1a233)));--shop-radius:6px;margin:0 0 18px;color:var(--shop-text)}
76.znx-shop-modern *{box-sizing:border-box}
77.znx-shop-hero{display:flex;align-items:flex-start;justify-content:space-between;gap:14px;margin:0 0 12px;padding:14px 16px;border:1px solid var(--shop-border);border-radius:var(--shop-radius);background:var(--shop-surface);box-shadow:inset 0 1px 0 rgba(255,255,255,.03)}
78.znx-shop-hero h1{margin:0 0 5px;font-size:26px;line-height:1.05;color:var(--shop-accent)}
79.znx-shop-hero p{margin:0;color:var(--shop-muted)}
80.znx-shop-hero__actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:9px}
81.znx-shop-hero__link{display:inline-flex;align-items:center;padding:6px 13px;border:1px solid var(--shop-accent);border-radius:999px;color:var(--shop-accent);font-weight:700;font-size:12px;text-decoration:none;white-space:nowrap}
82.znx-shop-hero__link:hover{background:var(--shop-accent);color:#1a1510}
83.znx-shop-balance{min-width:135px;padding:9px 12px;border:1px solid var(--shop-accent);border-radius:var(--shop-radius);background:var(--shop-well);color:var(--shop-muted);text-align:center}
84.znx-shop-balance strong{display:block;font-size:22px;line-height:1;color:var(--shop-accent)}
85.znx-shop-balance a{color:var(--shop-accent)}
86.znx-shop-tabs{display:flex;flex-wrap:wrap;gap:6px;margin:0 0 12px;padding:7px;border:1px solid var(--shop-border);border-radius:var(--shop-radius);background:var(--shop-surface)}
87.znx-shop-tab{display:inline-flex;align-items:center;justify-content:center;padding:7px 12px;border:1px solid var(--shop-border);border-radius:var(--shop-radius);background:var(--shop-well);color:var(--shop-muted);font-weight:700;cursor:pointer}
88.znx-shop-tab:hover{border-color:var(--shop-accent);color:var(--shop-accent)}
89.znx-shop-tab.is-active{background:var(--shop-accent);border-color:var(--shop-accent);color:#1a1510}
90.znx-shop-panel{display:none}
91.znx-shop-panel.is-active{display:block}
92.znx-shop-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(155px,1fr));align-items:stretch;gap:10px}
93.znx-shop-card{display:flex;min-height:230px;height:100%;flex-direction:column;position:relative;overflow:hidden;padding:10px;border:1px solid var(--shop-border);border-radius:var(--shop-radius);background:var(--shop-surface-2);box-shadow:none}
94.znx-shop-card:hover{transform:translateY(-1px);border-color:var(--shop-accent)}
95.znx-shop-card__media{display:flex;height:82px;align-items:center;justify-content:center;margin:0 0 9px;border:1px solid rgba(0,0,0,.25);border-radius:var(--shop-radius);background:var(--shop-well)}
96.znx-shop-card__img{max-width:66px;max-height:66px;object-fit:contain;image-rendering:auto;filter:drop-shadow(0 5px 8px rgba(0,0,0,.35))}
97.znx-shop-card__img.is-outfit,.znx-shop-card__img.is-mount{max-width:86px;max-height:76px}
98.znx-shop-card__placeholder{display:grid;width:54px;height:42px;place-items:center;border:1px solid var(--shop-accent);border-radius:var(--shop-radius);background:var(--shop-surface);color:var(--shop-accent);font-size:12px;font-weight:900}
99.znx-shop-card__body{display:flex;flex:1;flex-direction:column;text-align:center}
100.znx-shop-card__title{margin:0 0 6px;font-size:14px;font-weight:800;line-height:1.25;color:var(--shop-text)}
101.znx-shop-card__meta{min-height:18px;color:var(--shop-muted);font-size:12px}
102.znx-shop-card__price{display:inline-flex;align-items:center;justify-content:center;gap:5px;margin:9px auto 10px;padding:5px 9px;border:1px solid color-mix(in srgb,var(--shop-accent) 35%,transparent);border-radius:var(--shop-radius);background:var(--shop-well);color:var(--shop-accent);font-size:12px;font-weight:900}
103.znx-shop-card__price::before{content:"";display:inline-block;width:5px;height:5px;border-radius:50%;background:var(--shop-accent)}
104.znx-shop-card form{margin:auto 0 0}
105.znx-shop-buy{width:100%;min-height:34px;border:1px solid var(--shop-accent);border-radius:var(--shop-radius);background:var(--shop-accent);color:#1a1510;font-size:12px;font-weight:900;cursor:pointer;box-shadow:none}
106.znx-shop-buy:hover{filter:brightness(1.1)}
107.znx-shop-login{margin:auto 0 0;padding:8px;border-radius:var(--shop-radius);background:var(--shop-surface);color:var(--shop-muted);font-weight:700;text-align:center}
108@media(max-width:680px){.znx-shop-hero{display:block}.znx-shop-balance{margin-top:10px}.znx-shop-tab{flex:1}.znx-shop-grid{grid-template-columns:repeat(auto-fill,minmax(140px,1fr))}}
109</style>
110
111<section class="znx-shop-modern shop" id="shop">
112 <header class="znx-shop-hero">
113 <div>
114 <h1><?= $znxShopH(t('shop.offers')) ?></h1>
115 <?php if (($loggedin ?? false) === true): ?>
116 <p><?= $znxShopH(t('shop.you_have')) ?> <strong><?= (int)$user_znote_data['points'] ?></strong> <?= $znxShopH(t('common.points')) ?>.</p>
117 <div class="znx-shop-hero__actions">
118 <a href="buypoints.php" class="znx-shop-hero__link"><?= $znxShopH(t('shop.buy_points')) ?></a>
119 <?php if (!empty($config['shop_auction']['characterAuction'])): ?>
120 <a href="auctionChar.php" class="znx-shop-hero__link"><?= $znxShopH(t_default('shop.auction_button', 'Character Auctions')) ?></a>
121 <?php endif; ?>
122 </div>
123 <?php else: ?>
124 <p><?= $znxShopH(t('shop.need_login')) ?></p>
125 <?php endif; ?>
126 </div>
127 <?php if (($loggedin ?? false) === true): ?>
128 <div class="znx-shop-balance"><span><?= $znxShopH(t('common.points')) ?></span><strong><?= number_format((int)$user_znote_data['points']) ?></strong></div>
129 <?php endif; ?>
130 </header>
131
132 <?php if (!$visibleCategories): ?>
133 <p><?= $znxShopH(t('shop.offers')) ?>: 0</p>
134 <?php else: ?>
135 <nav class="znx-shop-tabs" aria-label="<?= $znxShopH(t('shop.offers')) ?>">
136 <?php foreach ($visibleCategories as $key => $category): ?>
137 <button type="button" class="znx-shop-tab<?= $firstCategory ? ' is-active' : '' ?>" data-shop-tab="<?= $znxShopH($key) ?>"><?= $znxShopH($category['label']) ?></button>
138 <?php $firstCategory = false; ?>
139 <?php endforeach; ?>
140 </nav>
141
142 <?php $firstCategory = true; ?>
143 <?php foreach ($visibleCategories as $categoryKey => $category): ?>
144 <section class="znx-shop-panel<?= $firstCategory ? ' is-active' : '' ?>" data-shop-panel="<?= $znxShopH($categoryKey) ?>">
145 <div class="znx-shop-grid">
146 <?php foreach ($category['items'] as $offerId => $offer): ?>
147 <?php
148 $titlePlain = trim(strip_tags((string)($offer['description'] ?? '')));
149 $meta = $offerMeta($offer, $categoryKey);
150 ?>
151 <article class="znx-shop-card shop-offer-card">
152 <div class="znx-shop-card__media"><?= $renderImage($offer, $categoryKey) ?></div>
153 <div class="znx-shop-card__body">
154 <h3 class="znx-shop-card__title"><?= (string)($offer['description'] ?? '') ?></h3>
155 <div class="znx-shop-card__meta"><?= $meta !== '' ? $znxShopH($meta) : '&nbsp;' ?></div>
156 <div class="znx-shop-card__price shop-offer-price"><?= (int)$offer['points'] ?> <?= $znxShopH(t('common.points')) ?></div>
157 <?php if (($loggedin ?? false) === true): ?>
158 <form action="" method="POST">
159 <input type="hidden" name="buy" value="<?= (int)$offerId ?>">
160 <input type="hidden" name="session" value="<?= $znxShopH($shopSession) ?>">
161 <button type="submit" class="znx-shop-buy needconfirmation" data-item-name="<?= $znxShopH($titlePlain) ?>" data-item-cost="<?= (int)$offer['points'] ?>"><?= $znxShopH(t('shop.purchase')) ?></button>
162 </form>
163 <?php else: ?>
164 <div class="znx-shop-login"><?= $znxShopH(t('shop.need_login')) ?></div>
165 <?php endif; ?>
166 </div>
167 </article>
168 <?php endforeach; ?>
169 </div>
170 </section>
171 <?php $firstCategory = false; ?>
172 <?php endforeach; ?>
173 <?php endif; ?>
174</section>
175
176<script>
177document.addEventListener('DOMContentLoaded', function () {
178 document.querySelectorAll('.znx-shop-tab').forEach(function (tab) {
179 tab.addEventListener('click', function () {
180 var code = tab.getAttribute('data-shop-tab');
181 document.querySelectorAll('.znx-shop-tab').forEach(function (t) { t.classList.toggle('is-active', t === tab); });
182 document.querySelectorAll('.znx-shop-panel').forEach(function (panel) { panel.classList.toggle('is-active', panel.getAttribute('data-shop-panel') === code); });
183 });
184 });
185 <?php if (!empty($shop['enableShopConfirmation'])): ?>
186 document.querySelectorAll('.needconfirmation').forEach(function (button) {
187 button.addEventListener('click', function (event) {
188 var itemName = this.getAttribute('data-item-name') || 'this offer';
189 var itemCost = this.getAttribute('data-item-cost') || '0';
190 if (!confirm('Do you really want to purchase ' + itemName + ' for ' + itemCost + ' points?')) {
191 event.preventDefault();
192 }
193 });
194 });
195 <?php endif; ?>
196});
197</script>
198
Top