Repo for dnd.vielle.dev.

home stretch just gotta make the select thing respect the unlock flow and max powers

ie: cant unlock powers early
cant unlock more powers than u have maxPowers

Changed files
+231 -12
src
+216 -4
src/components/power.svelte
··· 1 1 <script lang="ts"> 2 2 import { type Power, type Locked, isUnlocked } from "@/powers"; 3 3 import Self from "./power.svelte"; 4 - import { powers } from "@/stores/powers"; 4 + import { powers, mutatePowers } from "@/stores/powers"; 5 5 6 6 const { power }: { power: Power | Locked } = $props(); 7 7 </script> 8 8 9 - <div class="wrapper"> 9 + <!-- <div class="wrapper"> 10 10 <div 11 - class={`power ${power.status !== "locked" ? "unlocked" : "locked"} ${power.status === "frozen:selected" || (power.status !== "locked" && $powers.powers.includes(power.name)) ? "selected" : ""}`} 11 + class={`power ${power.status !== "locked" ? "unlocked" : "locked"} ${power.frozen === "selected" || (power.status !== "locked" && $powers.powers.includes(power.name)) ? "selected" : ""}`} 12 12 > 13 13 <div class="entry"> 14 14 {#if isUnlocked(power)} ··· 25 25 {/each} 26 26 </dl> 27 27 <p>{power.description}</p> 28 + {#if power.status !== "frozen:selected" && power.status !== "frozen:unselected"}<button>Unlock</button> 28 29 </div> 29 30 </details> 30 31 {:else} ··· 38 39 <Self power={child} /> 39 40 {/each} 40 41 </div> 42 + </div> --> 43 + 44 + <div class="wrapper"> 45 + <!-- locked power --> 46 + {#if power.status === "locked"} 47 + <div class={`power status:${power.status}`}> 48 + <img src="/locked.png" alt="" /> 49 + <p class="headline noto-serif">Locked</p> 50 + </div> 51 + {:else} 52 + <!-- unlocked power --> 53 + <button 54 + class={`power status:${power.status} frozen:${power.frozen} unlocked:${$powers.powers.includes(power.name)}`} 55 + id="preview" 56 + popovertarget={`tooltip-${power.name.replace(/\W/g, "_")}`} 57 + > 58 + <img {...power.image} /> 59 + <div class="headline noto-serif">{power.name}</div> 60 + </button> 61 + <dialog 62 + class="tooltip" 63 + id={`tooltip-${power.name.replace(/\W/g, "_")}`} 64 + popover="auto" 65 + > 66 + {#if !power.frozen} 67 + <button 68 + class={`unlock-btn ${$powers.powers.includes(power.name) ? "unselect" : "select"}`} 69 + onclick={() => { 70 + console.log("clicked", power.name); 71 + if (!$powers.powers.includes(power.name)) { 72 + console.log("select"); 73 + mutatePowers((n) => ({ 74 + ...n, 75 + powers: [...n.powers, power.name], 76 + })); 77 + } else { 78 + console.log("deselect"); 79 + mutatePowers((n) => ({ 80 + ...n, 81 + powers: n.powers.filter((p) => p !== power.name), 82 + })); 83 + } 84 + }} 85 + > 86 + <div class="unselect-txt">Unselect</div> 87 + <div class="select-txt">Select</div> 88 + </button> 89 + {/if} 90 + <img {...power.image} /> 91 + <div class="headline noto-serif">{power.name}</div> 92 + <dl> 93 + {#each power.meta as meta} 94 + <dt>{meta.name}</dt> 95 + <dd>{meta.value}</dd> 96 + {/each} 97 + </dl> 98 + <p>{power.description}</p> 99 + </dialog> 100 + {/if} 101 + <div class="children" style={`--rows: ${power.children.length}`}> 102 + {#each power.children as child} 103 + <Self power={child} /> 104 + {/each} 105 + </div> 41 106 </div> 42 107 43 - <style> 108 + <!-- <style> 44 109 /* positions */ 45 110 .wrapper { 46 111 display: flex; ··· 157 222 } 158 223 } 159 224 } 225 + } 226 + </style> --> 227 + 228 + <style> 229 + /* colours etc */ 230 + .power { 231 + &, 232 + & + .tooltip, 233 + & + .tooltip > .unlock-btn { 234 + border-radius: 1rem; 235 + color: var(--text); 236 + background-color: hsl( 237 + from var(--base, var(--background)) h calc(s * 1.5) 16 238 + ); 239 + border: 0.2rem solid 240 + hsl(from var(--base, var(--background)) h calc(s * 1.5) 48); 241 + } 242 + 243 + &.status\:unlocked, 244 + &.frozen\:unselected, 245 + &.unlocked\:false { 246 + &, 247 + & + .tooltip { 248 + --base: var(--red); 249 + } 250 + } 251 + 252 + &.status\:locked { 253 + --base: var(--gold); 254 + } 255 + 256 + &.frozen\:selected, 257 + &.unlocked\:true { 258 + &, 259 + & + .tooltip { 260 + --base: var(--green); 261 + } 262 + } 263 + } 264 + 265 + /* typo */ 266 + dl:not(:empty) { 267 + padding: 0.5rem; 268 + & dt { 269 + float: left; 270 + padding-right: 1rem; 271 + font-weight: bold; 272 + 273 + &::after { 274 + content: ":"; 275 + } 276 + } 277 + } 278 + 279 + dl + p { 280 + padding: 0.5rem; 281 + } 282 + 283 + .headline { 284 + font-family: "Noto Serif", serif; 285 + font-optical-sizing: auto; 286 + font-weight: 600; 287 + font-style: normal; 288 + font-variation-settings: "wdth" 100; 289 + font-size: 2rem; 290 + } 291 + 292 + /* looks positions */ 293 + img { 294 + width: 5rem; 295 + height: 5rem; 296 + object-fit: cover; 297 + border-radius: 2.5rem; 298 + } 299 + 300 + .power { 301 + display: grid; 302 + grid-template-columns: 5rem 1fr; 303 + align-items: center; 304 + gap: 0.5rem; 305 + padding: 0.5rem; 306 + text-align: left; 307 + 308 + &.status\:locked { 309 + width: 15rem; 310 + } 311 + 312 + &.status\:unlocked { 313 + width: 35rem; 314 + } 315 + } 316 + 317 + .tooltip { 318 + margin: auto; 319 + max-width: 65ch; 320 + 321 + & img { 322 + width: 4rem; 323 + height: 4rem; 324 + margin: 0.5rem; 325 + float: left; 326 + } 327 + 328 + & .headline { 329 + height: 5rem; 330 + line-height: 5rem; 331 + margin-left: 5rem; 332 + } 333 + 334 + & .unlock-btn { 335 + padding-inline: 1rem; 336 + padding-block: 0.2rem; 337 + margin: 1rem; 338 + float: right; 339 + 340 + display: grid; 341 + grid-template-areas: "stack"; 342 + 343 + & > * { 344 + visibility: hidden; 345 + grid-area: stack; 346 + } 347 + 348 + &.select > .select-txt { 349 + visibility: visible; 350 + } 351 + 352 + &.unselect > .unselect-txt { 353 + visibility: visible; 354 + } 355 + } 356 + } 357 + 358 + /* child pos stuff */ 359 + .wrapper { 360 + display: flex; 361 + align-items: center; 362 + width: fit-content; 363 + gap: 1rem; 364 + } 365 + 366 + .children { 367 + width: fit-content; 368 + height: 100%; 369 + display: grid; 370 + gap: 1rem; 371 + grid-template-rows: repeat(var(--rows), 1fr); 160 372 } 161 373 </style>
+11 -6
src/pages/index.astro
··· 9 9 <Base title="Astral Powers"> 10 10 <Fragment slot="head"> 11 11 <style> 12 + @import url("https://fonts.googleapis.com/css2?family=Noto+Serif:ital,wght@0,100..900;1,100..900&display=swap"); 13 + 12 14 @media (prefers-color-scheme: light) { 13 15 :root { 14 16 --text: #151314; 15 17 --background: #f3e7ea; 16 - --primary: #8ac79c; 17 - --secondary: #d77fab; 18 + --green: #53f9c7; 19 + --red: #bb2541; 20 + --gold: #c59c2b; 18 21 --accent: #cc2e7d; 19 22 } 20 23 } ··· 22 25 :root { 23 26 --text: #eceaeb; 24 27 --background: #190d10; 25 - --primary: #39764b; 26 - --secondary: #7f2854; 28 + --green: #06aa79; 29 + --red: #da4460; 30 + --gold: #d4aa3b; 27 31 --accent: #d13181; 28 32 } 29 33 } ··· 36 40 37 41 #head { 38 42 width: calc(100vw - 2rem); 39 - height: 3rem; 43 + height: 4rem; 44 + margin-bottom: 1rem; 40 45 } 41 46 42 47 #body { 43 48 width: calc(100vw - 2rem); 44 - height: calc(100vh - 5rem); 49 + height: calc(100vh - 7rem); 45 50 overflow: auto; 46 51 } 47 52 </style>
+4 -2
src/powers.ts
··· 1 1 export interface Power { 2 - status: "unlocked" | "frozen:selected" | "frozen:unselected"; 2 + status: "unlocked"; 3 + frozen?: "selected" | "unselected"; 3 4 name: string; 4 5 description: string; 5 6 image: { ··· 23 24 } 24 25 25 26 export const powers: Power = { 26 - status: "frozen:selected", 27 + status: "unlocked", 28 + frozen: "selected", 27 29 name: "Eldritch Adaptability", 28 30 description: 29 31 "You become more adaptable to other envirmonents. When entering another world your body automatically adjusts to endure there better. This can include new languages, gills, cultural understanding, and more.",