Banner.vue (3262B)
1 <script setup> 2 import { ref, onMounted } from 'vue' 3 4 const isVisible = ref(false) 5 const isFirstVisit = ref(true) 6 7 const STORAGE_KEY = 'terms_acknowledged_deploy_id' 8 const currentDeployId = import.meta.env.VITE_DEPLOY_ID || 'deploy_v1' 9 10 onMounted(() => { 11 const lastAcknowledgedId = localStorage.getItem(STORAGE_KEY) 12 13 if (!lastAcknowledgedId) { 14 isFirstVisit.value = true 15 isVisible.value = true 16 } else if (lastAcknowledgedId !== currentDeployId) { 17 isFirstVisit.value = false 18 isVisible.value = true 19 } 20 }) 21 22 const acceptTerms = () => { 23 localStorage.setItem(STORAGE_KEY, currentDeployId) 24 isVisible.value = false 25 } 26 </script> 27 28 <template> 29 <Transition name="slide-up"> 30 <div v-if="isVisible" class="terms-banner-wrapper" role="alert"> 31 <div class="terms-banner"> 32 <p class="banner-text"> 33 <template v-if="isFirstVisit"> 34 This is an open-source, <a href="https://github.com/notamitgamer/bsc/blob/main/LICENSE" class="banner-link"><strong>MIT-licensed</strong></a> 35 code index — please use it as a study reference, not a copy-paste source. 36 By browsing, you agree to our <a href="/terms" class="banner-link">Terms</a>. 37 </template> 38 <template v-else> 39 Updated with new materials — see the <a href="/changelog" class="banner-link">Changelog</a>. 40 </template> 41 </p> 42 <div class="banner-actions"> 43 <button class="banner-btn" @click="acceptTerms" aria-label="Dismiss">OK</button> 44 </div> 45 </div> 46 </div> 47 </Transition> 48 </template> 49 50 <style scoped> 51 .terms-banner-wrapper { 52 position: fixed; 53 bottom: 0; 54 left: 0; 55 width: 100vw; 56 z-index: 99999; 57 pointer-events: none; 58 } 59 60 .terms-banner { 61 pointer-events: auto; 62 width: 100%; 63 background-color: color-mix(in srgb, var(--banner-bg) 96%, transparent); 64 color: #ffffff; 65 padding: 10px 20px calc(10px + env(safe-area-inset-bottom)) 20px; 66 box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.15); 67 font-family: var(--vp-font-family-base); 68 display: flex; 69 align-items: center; 70 gap: 16px; 71 max-width: var(--vp-layout-max-width, 100%); 72 margin: 0 auto; 73 } 74 75 .banner-text { 76 margin: 0; 77 font-size: 13px; 78 font-weight: 500; 79 line-height: 1.4; 80 opacity: 0.95; 81 flex: 1; 82 } 83 84 .banner-link { 85 color: #ffffff; 86 text-decoration: underline; 87 font-weight: 700; 88 cursor: pointer; 89 transition: opacity 0.2s; 90 } 91 92 .banner-link:hover { 93 opacity: 0.8; 94 } 95 96 .banner-actions { 97 flex-shrink: 0; 98 } 99 100 .banner-btn { 101 background-color: #ffffff; 102 color: #5b21b6; 103 padding: 6px 16px; 104 border-radius: 6px; 105 font-size: 13px; 106 font-weight: 700; 107 border: none; 108 cursor: pointer; 109 white-space: nowrap; 110 transition: transform 0.1s ease, background-color 0.2s ease; 111 } 112 113 .banner-btn:hover { 114 transform: scale(1.02); 115 } 116 117 @media (max-width: 640px) { 118 .terms-banner { 119 padding: 12px 16px calc(12px + env(safe-area-inset-bottom)) 16px; 120 } 121 122 .banner-text { 123 font-size: 12.5px; 124 } 125 126 .banner-btn { 127 padding: 7px 14px; 128 font-size: 12.5px; 129 } 130 } 131 132 .slide-up-enter-active, 133 .slide-up-leave-active { 134 transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.3s ease; 135 } 136 137 .slide-up-enter-from, 138 .slide-up-leave-to { 139 transform: translateY(100%); 140 opacity: 0; 141 } 142 </style>