|
| 1 | +<!doctype html> |
| 2 | +<html lang="en"> |
| 3 | +<head> |
| 4 | +<meta charset="utf-8" /> |
| 5 | +<meta name="viewport" content="width=device-width,initial-scale=1" /> |
| 6 | +<title>For Nicole — Heart Portal</title> |
| 7 | +<meta name="color-scheme" content="light dark" /> |
| 8 | +<style> |
| 9 | + :root{ |
| 10 | + --bg: #1b1b23; /* default: comfy dark */ |
| 11 | + --card: #232334; |
| 12 | + --text: #f5f7ff; |
| 13 | + --muted: #cfd6ffcc; |
| 14 | + --accent: #ff87b5; /* pink */ |
| 15 | + --accent-2: #ffd1e6; |
| 16 | + --ring: #ffb3cf; |
| 17 | + --shadow: 0 10px 30px rgba(0,0,0,.35); |
| 18 | + } |
| 19 | + /* Seafoam theme */ |
| 20 | + .theme-seafoam{ |
| 21 | + --bg:#10221f; --card:#1a2f2b; --text:#eefbf5; --muted:#d1f5e8cc; |
| 22 | + --accent:#58e1b9; --accent-2:#bdf5e4; --ring:#8ff0d2; |
| 23 | + } |
| 24 | + /* Lavender theme */ |
| 25 | + .theme-lavender{ |
| 26 | + --bg:#1a1821; --card:#231f33; --text:#f7f2ff; --muted:#e1d9ffcc; |
| 27 | + --accent:#b08cff; --accent-2:#d8c7ff; --ring:#c8adff; |
| 28 | + } |
| 29 | + |
| 30 | + /* Pink (default) uses :root variables above */ |
| 31 | + |
| 32 | + *{box-sizing:border-box} |
| 33 | + html,body{height:100%} |
| 34 | + body{ |
| 35 | + margin:0; font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji"; |
| 36 | + background: |
| 37 | + radial-gradient(60vmax 60vmax at 20% -20%, color-mix(in srgb, var(--accent) 24%, transparent) 0 50%, transparent 60%), |
| 38 | + radial-gradient(70vmax 70vmax at 120% 20%, color-mix(in srgb, var(--accent-2) 20%, transparent) 0 40%, transparent 60%), |
| 39 | + var(--bg); |
| 40 | + color:var(--text); |
| 41 | + display:grid; place-items:center; |
| 42 | + } |
| 43 | + .wrap{ |
| 44 | + width:min(880px, 92vw); |
| 45 | + display:grid; gap:18px; |
| 46 | + padding:24px; |
| 47 | + } |
| 48 | + |
| 49 | + .card{ |
| 50 | + background:linear-gradient(180deg, color-mix(in srgb, var(--card) 90%, black 0%), var(--card)); |
| 51 | + border:1px solid color-mix(in srgb, var(--ring) 22%, transparent); |
| 52 | + border-radius:24px; |
| 53 | + box-shadow:var(--shadow); |
| 54 | + padding: clamp(20px, 5vw, 34px); |
| 55 | + position:relative; |
| 56 | + overflow:hidden; |
| 57 | + isolation:isolate; |
| 58 | + } |
| 59 | + .title{ |
| 60 | + margin:0 0 8px 0; |
| 61 | + font-size: clamp(20px, 3.6vw, 30px); |
| 62 | + letter-spacing:.2px; |
| 63 | + } |
| 64 | + .subtitle{ |
| 65 | + margin:0 0 18px 0; |
| 66 | + color:var(--muted); |
| 67 | + font-size: clamp(14px, 2.2vw, 16px); |
| 68 | + } |
| 69 | + |
| 70 | + /* Heart container */ |
| 71 | + .heartbox{ |
| 72 | + display:grid; place-items:center; |
| 73 | + margin:12px 0 20px 0; |
| 74 | + } |
| 75 | + svg{ width:min(320px, 68vw); height:auto; display:block; } |
| 76 | + |
| 77 | + /* Pulse animation (respects reduced motion) */ |
| 78 | + @media (prefers-reduced-motion:no-preference){ |
| 79 | + .pulse{ animation: pulse 1800ms ease-in-out infinite; transform-origin: center; } |
| 80 | + @keyframes pulse{ |
| 81 | + 0%{ transform:scale(0.98); filter: drop-shadow(0 0 0 rgba(0,0,0,0)); } |
| 82 | + 50%{ transform:scale(1.03); filter: drop-shadow(0 12px 24px rgba(0,0,0,.25)); } |
| 83 | + 100%{ transform:scale(0.98); } |
| 84 | + } |
| 85 | + .sparkle{ animation: sparkle 6s linear infinite; } |
| 86 | + @keyframes sparkle{ |
| 87 | + 0%{ opacity:.0; transform:translateY(10px) scale(.9) rotate(0deg); } |
| 88 | + 20%{ opacity:.7; } |
| 89 | + 50%{ opacity:.0; transform:translateY(-40px) scale(1.1) rotate(180deg); } |
| 90 | + 100%{ opacity:.0; transform:translateY(10px) scale(.9) rotate(360deg); } |
| 91 | + } |
| 92 | + } |
| 93 | + |
| 94 | + /* Buttons */ |
| 95 | + .buttons{ |
| 96 | + display:grid; grid-template-columns: repeat(2, minmax(0,1fr)); |
| 97 | + gap:12px; |
| 98 | + margin-top:8px; |
| 99 | + } |
| 100 | + @media (max-width:560px){ .buttons{ grid-template-columns:1fr; } } |
| 101 | + |
| 102 | + .btn{ |
| 103 | + --bg-btn: color-mix(in srgb, var(--accent) 18%, var(--card)); |
| 104 | + --bg-btn-hover: color-mix(in srgb, var(--accent) 28%, var(--card)); |
| 105 | + display:inline-flex; align-items:center; justify-content:center; |
| 106 | + gap:10px; |
| 107 | + border:1px solid color-mix(in srgb, var(--ring) 30%, transparent); |
| 108 | + border-radius:14px; |
| 109 | + padding:12px 14px; |
| 110 | + color:var(--text); text-decoration:none; font-weight:600; |
| 111 | + background: var(--bg-btn); |
| 112 | + box-shadow: 0 6px 16px rgba(0,0,0,.20); |
| 113 | + transition: transform .12s ease, background .15s ease, box-shadow .15s ease; |
| 114 | + outline: none; |
| 115 | + } |
| 116 | + .btn:hover, .btn:focus-visible{ |
| 117 | + background: var(--bg-btn-hover); |
| 118 | + box-shadow: 0 10px 22px rgba(0,0,0,.28); |
| 119 | + transform: translateY(-1px); |
| 120 | + } |
| 121 | + .btn:active{ transform: translateY(0); } |
| 122 | + |
| 123 | + /* Theme switcher row */ |
| 124 | + .row{ |
| 125 | + display:flex; flex-wrap:wrap; gap:10px; align-items:center; justify-content:space-between; |
| 126 | + margin-top:8px; |
| 127 | + } |
| 128 | + .swatch{ |
| 129 | + width:36px; height:28px; border-radius:9px; border:1px solid color-mix(in srgb, var(--ring) 35%, transparent); |
| 130 | + cursor:pointer; display:inline-block; |
| 131 | + box-shadow: 0 4px 10px rgba(0,0,0,.2); |
| 132 | + } |
| 133 | + .swatch[data-theme="pink"]{ background: linear-gradient(180deg,#ff9ac2,#ff6ea7); } |
| 134 | + .swatch[data-theme="seafoam"]{ background: linear-gradient(180deg,#74f0c4,#34d1a6); } |
| 135 | + .swatch[data-theme="lavender"]{ background: linear-gradient(180deg,#c3b1ff,#9c7cff); } |
| 136 | + .swatch:focus-visible{ outline:2px solid var(--ring); outline-offset:2px; } |
| 137 | + |
| 138 | + .note{ color:var(--muted); font-size:12px; margin-top:6px; } |
| 139 | + |
| 140 | + /* Decorative sparkles (non-blocking) */ |
| 141 | + .sparkle{ |
| 142 | + position:absolute; inset:auto auto 18px 18px; |
| 143 | + width:80px; height:80px; pointer-events:none; opacity:.35; |
| 144 | + filter: drop-shadow(0 4px 10px rgba(0,0,0,.4)); |
| 145 | + z-index:0; |
| 146 | + } |
| 147 | + .card > *{ position:relative; z-index:1; } /* keep content above sparkles */ |
| 148 | +</style> |
| 149 | +</head> |
| 150 | +<body> |
| 151 | + <main class="wrap" id="app"> |
| 152 | + <section class="card" aria-label="For Nicole — Heart Portal"> |
| 153 | + <!-- Decorative sparkle --> |
| 154 | + <svg class="sparkle" viewBox="0 0 64 64" aria-hidden="true" focusable="false"> |
| 155 | + <path fill="url(#g1)" d="M32 10l4 10 10 4-10 4-4 10-4-10-10-4 10-4 4-10z"/> |
| 156 | + <defs> |
| 157 | + <linearGradient id="g1" x1="0" y1="0" x2="1" y2="1"> |
| 158 | + <stop offset="0" stop-color="var(--accent)"/> |
| 159 | + <stop offset="1" stop-color="var(--accent-2)"/> |
| 160 | + </linearGradient> |
| 161 | + </defs> |
| 162 | + </svg> |
| 163 | + |
| 164 | + <h1 class="title">Hi Nicole — this is your heart portal</h1> |
| 165 | + <p class="subtitle">Tiny reps. Bright wins. A soft place to land and learn.</p> |
| 166 | + |
| 167 | + <div class="heartbox" role="img" aria-label="A glowing animated heart"> |
| 168 | + <!-- SVG Heart with gradient fill and soft pulse --> |
| 169 | + <svg viewBox="0 0 400 360" aria-hidden="true" class="pulse"> |
| 170 | + <defs> |
| 171 | + <radialGradient id="heartGrad" cx="50%" cy="40%" r="70%"> |
| 172 | + <stop offset="0%" stop-color="var(--accent-2)"/> |
| 173 | + <stop offset="70%" stop-color="var(--accent)"/> |
| 174 | + <stop offset="100%" stop-color="var(--accent)"/> |
| 175 | + </radialGradient> |
| 176 | + <filter id="glow" x="-40%" y="-40%" width="180%" height="180%"> |
| 177 | + <feGaussianBlur stdDeviation="8" result="b"/> |
| 178 | + <feMerge> |
| 179 | + <feMergeNode in="b"/> |
| 180 | + <feMergeNode in="SourceGraphic"/> |
| 181 | + </feMerge> |
| 182 | + </filter> |
| 183 | + </defs> |
| 184 | + <!-- Heart path --> |
| 185 | + <path filter="url(#glow)" fill="url(#heartGrad)" d=" |
| 186 | + M200,330 |
| 187 | + C120,270 60,220 40,170 |
| 188 | + C10,90 70,40 120,60 |
| 189 | + C155,75 175,105 200,135 |
| 190 | + C225,105 245,75 280,60 |
| 191 | + C330,40 390,90 360,170 |
| 192 | + C340,220 280,270 200,330 Z"/> |
| 193 | + </svg> |
| 194 | + </div> |
| 195 | + |
| 196 | + <div class="buttons" role="group" aria-label="Quick actions"> |
| 197 | + <a class="btn" href="javascript:void(0)" id="studydateBtn" aria-label="Open Study Date notebook">📓 Study Date</a> |
| 198 | + <a class="btn" href="https://github.com/DaScient/MakeItCute" target="_blank" rel="noopener" aria-label="Open MakeItCute Source">🌐 MakeItCute Source</a> |
| 199 | + <a class="btn" href="https://kdpreports.amazon.com/orders" target="_blank" rel="noopener" aria-label="Open Amazon KDP Orders">📈 KDP Orders</a> |
| 200 | + <a class="btn" href="https://www.tiktok.com/@nicollettesheart" target="_blank" rel="noopener" aria-label="Open Nicole’s TikTok">🎵 TikTok</a> |
| 201 | + </div> |
| 202 | + |
| 203 | + <div class="row" aria-label="Theme picker"> |
| 204 | + <div style="display:flex; gap:10px; align-items:center;"> |
| 205 | + <span class="note" id="themeLabel">Theme:</span> |
| 206 | + <button class="swatch" data-theme="pink" aria-labelledby="themeLabel" title="Pink theme"></button> |
| 207 | + <button class="swatch" data-theme="seafoam" aria-labelledby="themeLabel" title="Seafoam theme"></button> |
| 208 | + <button class="swatch" data-theme="lavender" aria-labelledby="themeLabel" title="Lavender theme"></button> |
| 209 | + </div> |
| 210 | + <span class="note">Remembers your pick • Respects Reduced Motion</span> |
| 211 | + </div> |
| 212 | + </section> |
| 213 | + </main> |
| 214 | + |
| 215 | +<script> |
| 216 | +(function(){ |
| 217 | + const app = document.getElementById('app'); |
| 218 | + |
| 219 | + // Theme management |
| 220 | + const THEME_KEY = 'nicole-heart-theme'; |
| 221 | + const applyTheme = (name) => { |
| 222 | + app.classList.remove('theme-seafoam','theme-lavender'); |
| 223 | + if(name === 'seafoam') app.classList.add('theme-seafoam'); |
| 224 | + if(name === 'lavender') app.classList.add('theme-lavender'); |
| 225 | + localStorage.setItem(THEME_KEY, name); |
| 226 | + }; |
| 227 | + const initial = localStorage.getItem(THEME_KEY) || 'pink'; |
| 228 | + applyTheme(initial); |
| 229 | + |
| 230 | + document.querySelectorAll('.swatch').forEach(btn=>{ |
| 231 | + btn.addEventListener('click', ()=> applyTheme(btn.dataset.theme)); |
| 232 | + btn.addEventListener('keydown', (e)=>{ if(e.key==='Enter' || e.key===' ') { e.preventDefault(); btn.click(); }}); |
| 233 | + }); |
| 234 | + |
| 235 | + // Study Date: in a browser we can’t launch Jupyter, so we nudge kindly. |
| 236 | + const studyBtn = document.getElementById('studydateBtn'); |
| 237 | + studyBtn.addEventListener('click', ()=>{ |
| 238 | + alert([ |
| 239 | + "Study Date opens in your terminal.", |
| 240 | + "Tip: in your pastel shell, type: studydate", |
| 241 | + "Or use the Learn Menu: Alt-m → 2" |
| 242 | + ].join("\n")); |
| 243 | + }); |
| 244 | + |
| 245 | + // Respect reduced motion: pause pulse if user prefers less motion. |
| 246 | + const mq = window.matchMedia('(prefers-reduced-motion: reduce)'); |
| 247 | + const toggleMotion = () => { |
| 248 | + document.querySelectorAll('.pulse').forEach(el=>{ |
| 249 | + el.style.animationPlayState = mq.matches ? 'paused' : 'running'; |
| 250 | + }); |
| 251 | + }; |
| 252 | + mq.addEventListener ? mq.addEventListener('change', toggleMotion) : mq.addListener(toggleMotion); |
| 253 | + toggleMotion(); |
| 254 | +})(); |
| 255 | +</script> |
| 256 | +</body> |
| 257 | +</html> |
0 commit comments