1 Commits
Author SHA1 Message Date
FernandoJVideira ecc81f0409 chore: ui revamp 2026-07-09 12:08:49 +01:00
16 changed files with 3496 additions and 154 deletions
+11 -2
View File
@@ -1,14 +1,23 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/tt_logo.ico" />
<link rel="icon" type="image/png" href="/tt_logo.png" />
<link rel="icon" type="image/svg+xml" href="/tt_logo.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>TTRPG Utility App</title> <title>TTRPG Utility App</title>
<meta <meta
name="description" name="description"
content="Professional utilities for tabletop RPG players and game masters" content="Professional utilities for tabletop RPG players and game masters"
/> />
<meta name="theme-color" content="#f7f8fa" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700;800&display=swap"
rel="stylesheet"
/>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
+2944
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

+261 -9
View File
@@ -1,22 +1,274 @@
<script setup> <script setup>
// App entry with router-view import { ref, onMounted, onBeforeUnmount, computed } from "vue";
const theme = ref(null); // 'light' | 'dark' | null (system)
function applyThemeClass(t) {
const el = document.documentElement;
el.classList.remove("theme-light", "theme-dark");
if (t === "light") el.classList.add("theme-light");
if (t === "dark") el.classList.add("theme-dark");
}
function setThemePreference(t) {
theme.value = t;
if (t === null) {
localStorage.removeItem("theme");
// remove explicit classes to fallback to system
applyThemeClass(null);
} else {
localStorage.setItem("theme", t);
applyThemeClass(t);
}
}
function toggleTheme() {
const current = effectiveTheme.value;
const next = current === "dark" ? "light" : "dark";
setThemePreference(next);
}
// Track system preference reactively so UI updates immediately when OS theme changes
const systemPrefDark = ref(false);
const effectiveTheme = computed(() => {
if (theme.value === "dark" || theme.value === "light") return theme.value;
return systemPrefDark.value ? "dark" : "light";
});
let mq = null;
function handleMqChange(e) {
systemPrefDark.value = e.matches;
}
onMounted(() => {
const stored = localStorage.getItem("theme");
if (stored === "dark" || stored === "light") {
theme.value = stored;
applyThemeClass(stored);
} else {
theme.value = null; // follow system
}
// set up system preference watcher
mq = window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)");
if (mq) {
systemPrefDark.value = mq.matches;
mq.addEventListener("change", handleMqChange);
}
});
onBeforeUnmount(() => {
if (mq) mq.removeEventListener("change", handleMqChange);
});
</script> </script>
<template> <template>
<nav class="nav"> <div class="app-shell">
<router-link to="/">Home</router-link> <aside class="sidebar">
<div class="brand">
<img src="/tt_logo.png" alt="TTRPG Utilities logo" class="app-logo" />
<div class="brand-text">
<h1>TTRPG Utilities</h1>
</div>
</div>
<nav class="nav-vertical">
<router-link to="/" exact>Home</router-link>
<router-link to="/name-generator">Name Generator</router-link> <router-link to="/name-generator">Name Generator</router-link>
<router-link to="/attribute-calculator">Attribute Calculator</router-link> <router-link to="/attribute-calculator"
>Attribute Calculator</router-link
>
<a href="https://foundry.fernandovideira.com">Foundry</a>
</nav> </nav>
<main>
<div class="sidebar-footer">
<div
style="
display: flex;
align-items: center;
gap: 8px;
justify-content: space-between;
"
>
<small class="muted">v0.1 · production</small>
<small class="muted">by KrioX</small>
<button
class="theme-toggle"
@click="toggleTheme"
:aria-pressed="effectiveTheme === 'dark'"
>
<span class="visually-hidden">Toggle theme</span>
<svg
class="icon icon--sm"
v-if="effectiveTheme === 'dark'"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z" />
</svg>
<svg
class="icon icon--sm"
v-else
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<circle cx="12" cy="12" r="4" />
<path d="M12 2v2" />
<path d="M12 20v2" />
<path d="M2 12h2" />
<path d="M20 12h2" />
<path d="M4.93 4.93l1.41 1.41" />
<path d="M17.66 17.66l1.41 1.41" />
<path d="M4.93 19.07l1.41-1.41" />
<path d="M17.66 6.34l1.41-1.41" />
</svg>
</button>
</div>
</div>
</aside>
<div class="content">
<div class="content-inner">
<router-view /> <router-view />
</main> </div>
</div>
</div>
</template> </template>
<style scoped> <style scoped>
.nav { .app-shell {
display: flex; display: flex;
gap: 1rem; min-height: 100vh;
margin-bottom: 2rem; }
.sidebar {
width: var(--sidebar-width);
background: transparent;
padding: calc(var(--spacing-lg)) var(--spacing-md);
border-right: 1px solid var(--border);
display: flex;
flex-direction: column;
gap: var(--spacing-md);
align-items: stretch;
}
.brand h1 {
margin: 0 0 4px 0;
font-size: 1rem;
font-weight: 700;
color: var(--text-primary);
}
.brand {
display: flex;
align-items: center;
gap: 10px;
}
.app-logo {
width: 36px;
height: 36px;
display: block;
border-radius: 8px;
}
.brand-text h1 {
margin: 0;
font-size: 0.95rem;
}
.brand .tagline {
margin: 0;
font-size: 0.85rem;
color: var(--text-secondary);
}
.nav-vertical {
display: flex;
flex-direction: column;
gap: 8px;
margin-top: var(--spacing-md);
}
.nav-vertical a {
display: inline-block;
padding: 8px 12px;
border-radius: 8px;
color: var(--text-primary);
text-decoration: none;
font-weight: 600;
}
.nav-vertical a.router-link-exact-active {
background: var(--color-accent);
color: white;
}
.sidebar-footer {
margin-top: auto;
color: var(--text-secondary);
font-size: 0.85rem;
}
.theme-toggle {
background: transparent;
border: 1px solid var(--border);
padding: 6px;
width: 36px;
height: 36px;
border-radius: 10px;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
color: var(--text-primary);
}
.theme-toggle:hover {
background: var(--surface-hover);
}
.visually-hidden {
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px, 1px, 1px, 1px);
white-space: nowrap;
border: 0;
padding: 0;
margin: -1px;
}
.content {
flex: 1;
padding: var(--spacing-xl) var(--spacing-lg);
background: transparent;
}
.content-inner {
max-width: var(--content-max-width);
margin: 0 auto;
}
@media (max-width: 900px) {
.sidebar {
display: none;
}
.content {
padding: var(--spacing-lg);
}
} }
</style> </style>
+147 -26
View File
@@ -1,33 +1,144 @@
/* Professional minimalist dark theme */ /* Professional minimalist dark theme */
:root {
--color-background: #f7f8fa; /* page background */
--color-background-soft: #ffffff; /* card backgrounds */
--color-background-mute: #f3f4f6; /* subtle surfaces */
--color-card: #ffffff;
--color-card-border: #eaecf0;
--color-border: #e6e9ee;
--color-border-hover: #d6d9df;
--color-heading: #0f1724;
--color-text: #111827;
--color-text-muted: #6b7280;
--color-accent: #007aff; /* Apple-like blue */
--color-accent-hover: #0064d6;
--color-accent-bg: rgba(0, 122, 255, 0.08);
--color-success: #16a34a;
--color-warning: #d97706;
--color-error: #dc2626;
--radius-sm: 0.375rem;
--radius-md: 0.5rem;
--radius-lg: 0.75rem;
--shadow-sm: 0 4px 10px rgba(15, 23, 42, 0.06);
--shadow-md: 0 10px 30px rgba(15, 23, 42, 0.06);
--shadow-lg: 0 20px 60px rgba(15, 23, 42, 0.08);
--spacing-xs: 0.5rem;
--spacing-sm: 0.75rem;
--spacing-md: 1rem;
--spacing-lg: 1.25rem;
--spacing-xl: 2rem;
--spacing-2xl: 3rem;
/* Compatibility aliases used by some components */
--border: var(--color-border);
--border-hover: var(--color-border-hover);
--text-primary: var(--color-heading);
--text-secondary: var(--color-text-muted);
--surface: var(--color-card);
--surface-hover: var(--color-background-mute);
--surface-secondary: var(--color-background-soft);
--color-primary: var(--color-accent);
--color-primary-alpha: var(--color-accent-bg);
--border-radius: var(--radius-md);
}
/* Icon utilities: consistent sizing, stroke/fill behavior, and spacing */
svg.icon {
width: var(--icon-size, 18px);
height: var(--icon-size, 18px);
display: inline-block;
vertical-align: middle;
color: inherit;
}
svg.icon [stroke] {
stroke: currentColor !important;
stroke-width: 1.5 !important;
vector-effect: non-scaling-stroke;
}
svg.icon [fill]:not([fill="none"]) {
fill: currentColor !important;
}
/* smaller variant */
.icon--sm {
--icon-size: 14px;
}
.icon--lg {
--icon-size: 20px;
}
/* spacing when icon appears inside buttons */
.button-secondary svg.icon {
margin-right: var(--spacing-xs);
}
.reset-btn svg.icon,
.copy-icon.icon {
margin: 0;
}
/* Dark theme: follow system preference */
@media (prefers-color-scheme: dark) {
:root { :root {
--color-background: #0f1419; --color-background: #0f1419;
--color-background-soft: #1a1f26; --color-background-soft: #1a1f26;
--color-background-mute: #1e242c; --color-background-mute: #1e242c;
--color-card: #1a1f26; --color-card: #111214;
--color-card-border: #252b35; --color-card-border: #121417;
--color-border: #2e3440; --color-border: #1f2730;
--color-border-hover: #3d4450; --color-border-hover: #2e3440;
--color-heading: #ffffff; --color-heading: #ffffff;
--color-text: #d8dee9; --color-text: #d8dee9;
--color-text-muted: #a8b2c1; --color-text-muted: #9ca3af;
--color-accent: #5e81ac; --color-accent: #5e81ac;
--color-accent-hover: #4c6b91; --color-accent-hover: #4c6b91;
--color-accent-bg: rgba(94, 129, 172, 0.1); --color-accent-bg: rgba(94, 129, 172, 0.08);
--color-success: #a3be8c; --color-success: #a3be8c;
--color-warning: #ebcb8b; --color-warning: #ebcb8b;
--color-error: #bf616a; --color-error: #bf616a;
--radius-sm: 0.5rem; --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.4);
--radius-md: 0.75rem; --shadow-md: 0 6px 20px rgba(0, 0, 0, 0.5);
--radius-lg: 1rem; --shadow-lg: 0 12px 40px rgba(0, 0, 0, 0.6);
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.12); }
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.15); }
--shadow-lg: 0 8px 25px rgba(0, 0, 0, 0.2);
--spacing-xs: 0.5rem; /* Manual overrides when user explicitly chooses theme */
--spacing-sm: 0.75rem; .theme-dark {
--spacing-md: 1rem; --color-background: #0f1419;
--spacing-lg: 1.5rem; --color-background-soft: #1a1f26;
--spacing-xl: 2rem; --color-background-mute: #1e242c;
--spacing-2xl: 3rem; --color-card: #111214;
--color-card-border: #121417;
--color-border: #1f2730;
--color-border-hover: #2e3440;
--color-heading: #ffffff;
--color-text: #d8dee9;
--color-text-muted: #9ca3af;
--color-accent: #5e81ac;
--color-accent-hover: #4c6b91;
--color-accent-bg: rgba(94, 129, 172, 0.08);
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.4);
--shadow-md: 0 6px 20px rgba(0, 0, 0, 0.5);
}
.theme-light {
--color-background: #f7f8fa;
--color-background-soft: #ffffff;
--color-background-mute: #f3f4f6;
--color-card: #ffffff;
--color-card-border: #eaecf0;
--color-border: #e6e9ee;
--color-border-hover: #d6d9df;
--color-heading: #0f1724;
--color-text: #111827;
--color-text-muted: #6b7280;
--color-accent: #007aff;
--color-accent-hover: #0064d6;
--color-accent-bg: rgba(0, 122, 255, 0.08);
--shadow-sm: 0 4px 10px rgba(15, 23, 42, 0.06);
--shadow-md: 0 10px 30px rgba(15, 23, 42, 0.06);
} }
*, *,
@@ -42,11 +153,14 @@ body {
min-height: 100vh; min-height: 100vh;
color: var(--color-text); color: var(--color-text);
background: var(--color-background); background: var(--color-background);
transition: color 0.3s ease, background-color 0.3s ease; transition:
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, color 0.2s ease,
sans-serif; background-color 0.2s ease;
font-family:
-apple-system, BlinkMacSystemFont, "SF Pro Text", "Inter", "Segoe UI",
Roboto, "Helvetica Neue", Arial, sans-serif;
font-size: 15px; font-size: 15px;
line-height: 1.6; line-height: 1.5;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
@@ -54,12 +168,17 @@ body {
#app { #app {
width: 100%; width: 100%;
min-height: 100vh; min-height: 100vh;
max-width: 800px; max-width: 100%;
margin: 0 auto; margin: 0;
padding: var(--spacing-xl) var(--spacing-lg); padding: 0;
background: var(--color-background); background: var(--color-background);
} }
:root {
--sidebar-width: 260px;
--content-max-width: 1100px;
}
/* Typography */ /* Typography */
h1 { h1 {
color: var(--color-heading); color: var(--color-heading);
@@ -133,7 +252,9 @@ select {
color: var(--color-text); color: var(--color-text);
padding: var(--spacing-sm) var(--spacing-md); padding: var(--spacing-sm) var(--spacing-md);
font-size: 0.95rem; font-size: 0.95rem;
transition: border-color 0.2s ease, box-shadow 0.2s ease; transition:
border-color 0.2s ease,
box-shadow 0.2s ease;
width: 100%; width: 100%;
} }
+4 -3
View File
@@ -2,10 +2,11 @@
/* Main app container override for professional layout */ /* Main app container override for professional layout */
#app { #app {
background: var(--color-background); background: var(--surface);
border-radius: 0; border-radius: 12px;
box-shadow: none; box-shadow: var(--shadow-md);
min-height: 100vh; min-height: 100vh;
margin-top: 24px;
} }
/* Professional link styling */ /* Professional link styling */
+17 -16
View File
@@ -1,28 +1,29 @@
.attribute-calculator { .attribute-calculator {
background: var(--color-card); background: var(--surface);
border: 1px solid var(--color-card-border); border: 1px solid var(--border);
border-radius: var(--radius-lg); border-radius: var(--radius-lg);
box-shadow: var(--shadow-md); box-shadow: var(--shadow-md);
max-width: 600px; max-width: 680px;
margin: 0 auto; margin: 0 auto;
padding: var(--spacing-2xl); padding: var(--spacing-xl);
} }
.attribute-calculator h2 { .attribute-calculator h2 {
margin-bottom: var(--spacing-xl); margin-bottom: var(--spacing-lg);
text-align: center;
} }
.inputs { .inputs {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: var(--spacing-lg); gap: var(--spacing-lg);
margin-bottom: var(--spacing-2xl); margin-bottom: var(--spacing-xl);
} }
.inputs label { .inputs label {
color: var(--color-text); color: var(--text-primary);
font-size: 0.95rem; font-size: 0.95rem;
font-weight: 500; font-weight: 600;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--spacing-sm); gap: var(--spacing-sm);
@@ -95,25 +96,25 @@
} }
.results { .results {
background: var(--color-background-soft); background: var(--surface-secondary);
border: 1px solid var(--color-border); border: 1px solid var(--border);
border-radius: var(--radius-md); border-radius: var(--radius-md);
padding: var(--spacing-xl); padding: var(--spacing-lg);
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: var(--spacing-lg); gap: var(--spacing-lg);
} }
.results p { .results p {
font-weight: 600; font-weight: 700;
font-size: 1.1rem; font-size: 1.05rem;
margin: 0; margin: 0;
text-align: center; text-align: center;
padding: var(--spacing-md); padding: var(--spacing-md);
background: var(--color-card); background: var(--surface);
border: 1px solid var(--color-card-border); border: 1px solid var(--border);
border-radius: var(--radius-sm); border-radius: var(--radius-sm);
color: var(--color-accent); color: var(--color-primary);
box-shadow: var(--shadow-sm); box-shadow: var(--shadow-sm);
} }
+53 -32
View File
@@ -1,13 +1,28 @@
<template> <template>
<section class="home"> <section class="home">
<h1>Utility App for TTRPGs</h1> <h1>Utility App for TTRPGs</h1>
<p>Explore useful tools to enhance your tabletop RPG experience:</p> <p class="lead">Small, focused tools to speed up tabletop prep: generate names, balance attributes, and connect to Foundry VTT resources.</p>
<p class="intro">This app collects lightweight utilities for GMs and players. Use the sidebar to navigate between tools each page offers quick controls and sensible defaults so you can get results without fumbling through settings.</p>
<ul class="tools-list"> <ul class="tools-list">
<li><router-link to="/name-generator">Name Generator</router-link></li>
<li> <li>
<router-link to="/attribute-calculator" <div class="tool-card">
>Attribute Calculator</router-link <h3>Name Generator</h3>
> <p class="muted">Generate character names by race, language and gender. Create reusable lists and quickly copy results into your notes.</p>
</div>
</li>
<li>
<div class="tool-card">
<h3>Attribute Calculator</h3>
<p class="muted">Experiment with point-buy and rolled stats, see derived modifiers, and compare different allocation strategies.</p>
</div>
</li>
<li>
<div class="tool-card">
<h3>Foundry</h3>
<p class="muted">Quick link and notes for integrating with Foundry VTT (external). Use this as a launch/reference point rather than a full integration.</p>
</div>
</li> </li>
</ul> </ul>
</section> </section>
@@ -21,18 +36,20 @@
.home { .home {
text-align: center; text-align: center;
padding: var(--spacing-2xl) 0; padding: var(--spacing-2xl) 0;
max-width: 600px; max-width: 720px;
margin: 0 auto; margin: 0 auto;
} }
.home h1 { .home h1 {
margin-bottom: var(--spacing-md); margin-bottom: var(--spacing-sm);
font-size: 2rem;
letter-spacing: -0.02em;
} }
.home p { .home p {
color: var(--color-text-muted); color: var(--text-secondary);
font-size: 1.1rem; font-size: 1rem;
margin-bottom: var(--spacing-2xl); margin-bottom: var(--spacing-lg);
line-height: 1.6; line-height: 1.6;
} }
@@ -40,56 +57,60 @@
list-style: none; list-style: none;
padding: 0; padding: 0;
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: var(--spacing-lg); gap: var(--spacing-lg);
margin-top: var(--spacing-xl); margin-top: var(--spacing-xl);
} }
.tools-list li { .tools-list li {
transform: translateY(0); transform: translateY(0);
transition: transform 0.2s ease; transition: transform 0.16s ease;
} }
.tools-list li:hover { .tools-list li:hover {
transform: translateY(-2px); transform: translateY(-4px);
} }
.tools-list a { .tools-list .tool-card {
display: block; display: block;
font-size: 1.1rem; font-size: 1.05rem;
color: var(--color-heading); color: var(--text-primary);
background: var(--color-card); background: var(--surface);
border: 1px solid var(--color-card-border); border: 1px solid var(--border);
border-radius: var(--radius-lg); border-radius: var(--radius-lg);
padding: var(--spacing-xl); padding: var(--spacing-lg);
text-decoration: none; text-decoration: none;
font-weight: 600; font-weight: 700;
box-shadow: var(--shadow-md); box-shadow: var(--shadow-md);
transition: all 0.2s ease; transition: all 0.16s ease;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
} }
.tools-list a::before { .tools-list .tool-card::before {
content: ""; content: "";
position: absolute; position: absolute;
top: 0; top: 0;
left: -100%; left: -100%;
width: 100%; width: 100%;
height: 2px; height: 3px;
background: var(--color-accent); background: linear-gradient(
transition: left 0.3s ease; 90deg,
var(--color-primary),
var(--color-accent-hover)
);
transition: left 0.28s ease;
} }
.tools-list a:hover::before { .tools-list li:hover .tool-card::before {
left: 0; left: 0;
} }
.tools-list a:hover { .tools-list li:hover .tool-card {
background: var(--color-background-mute); background: var(--surface-hover);
border-color: var(--color-accent); border-color: var(--color-primary);
box-shadow: var(--shadow-lg); box-shadow: var(--shadow-lg);
color: var(--color-accent); color: var(--color-primary);
} }
@media (max-width: 768px) { @media (max-width: 768px) {
@@ -98,8 +119,8 @@
gap: var(--spacing-md); gap: var(--spacing-md);
} }
.tools-list a { .tools-list .tool-card {
padding: var(--spacing-lg); padding: var(--spacing-md);
} }
} }
</style> </style>
+14 -11
View File
@@ -9,6 +9,7 @@
:disabled="generators.length >= 6" :disabled="generators.length >= 6"
> >
<svg <svg
class="icon"
width="16" width="16"
height="16" height="16"
viewBox="0 0 24 24" viewBox="0 0 24 24"
@@ -40,6 +41,7 @@
:disabled="generators.length <= 1" :disabled="generators.length <= 1"
> >
<svg <svg
class="icon"
width="16" width="16"
height="16" height="16"
viewBox="0 0 24 24" viewBox="0 0 24 24"
@@ -81,7 +83,6 @@ import { ref } from "vue";
import NameGeneratorCard from "./NameGeneratorCard.vue"; import NameGeneratorCard from "./NameGeneratorCard.vue";
const generators = ref([1, 2, 3, 4]); const generators = ref([1, 2, 3, 4]);
let nextId = 5;
// Toast state // Toast state
const showToast = ref(false); const showToast = ref(false);
@@ -96,13 +97,15 @@ function handleToast(event) {
() => { () => {
showToast.value = false; showToast.value = false;
}, },
event.type === "error" ? 4000 : 2000 event.type === "error" ? 4000 : 2000,
); );
} }
function addGenerator() { function addGenerator() {
if (generators.value.length < 6) { if (generators.value.length < 6) {
generators.value.push(nextId++); // compute next id based on current count so removed ids are reused
const id = generators.value.length + 1;
generators.value.push(id);
} }
} }
@@ -115,7 +118,7 @@ function removeGenerator() {
<style scoped> <style scoped>
.name-generator-layout { .name-generator-layout {
padding: var(--spacing-lg); padding: calc(var(--spacing-lg) + 0.25rem);
max-width: 1400px; max-width: 1400px;
margin: 0 auto; margin: 0 auto;
} }
@@ -145,13 +148,13 @@ function removeGenerator() {
align-items: center; align-items: center;
gap: var(--spacing-xs); gap: var(--spacing-xs);
padding: var(--spacing-sm) var(--spacing-md); padding: var(--spacing-sm) var(--spacing-md);
background: var(--surface); background: transparent;
color: var(--text-primary); color: var(--text-primary);
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: var(--border-radius); border-radius: var(--border-radius);
cursor: pointer; cursor: pointer;
font-weight: 500; font-weight: 600;
transition: all 0.2s ease; transition: all 0.12s ease;
} }
.button-secondary:hover:not(:disabled) { .button-secondary:hover:not(:disabled) {
@@ -259,15 +262,15 @@ function removeGenerator() {
} }
.toast-success { .toast-success {
background: #10b981; background: var(--color-success);
color: white; color: white;
border: 1px solid #059669; border: 1px solid rgba(0, 0, 0, 0.06);
} }
.toast-error { .toast-error {
background: #ef4444; background: var(--color-error);
color: white; color: white;
border: 1px solid #dc2626; border: 1px solid rgba(0, 0, 0, 0.06);
} }
@keyframes slideInRight { @keyframes slideInRight {
+17 -16
View File
@@ -4,6 +4,7 @@
<h3>Generator {{ cardId }}</h3> <h3>Generator {{ cardId }}</h3>
<button class="reset-btn" @click="resetGenerator" title="Reset Generator"> <button class="reset-btn" @click="resetGenerator" title="Reset Generator">
<svg <svg
class="icon icon--sm"
width="16" width="16"
height="16" height="16"
viewBox="0 0 24 24" viewBox="0 0 24 24"
@@ -102,7 +103,7 @@
> >
{{ name }} {{ name }}
<svg <svg
class="copy-icon" class="copy-icon icon icon--sm"
width="14" width="14"
height="14" height="14"
viewBox="0 0 24 24" viewBox="0 0 24 24"
@@ -241,7 +242,7 @@ async function generate() {
} else { } else {
console.error( console.error(
"Failed to fetch modern names:", "Failed to fetch modern names:",
data.message || "Unknown error" data.message || "Unknown error",
); );
emit("show-toast", { emit("show-toast", {
message: `Error: ${ message: `Error: ${
@@ -272,7 +273,7 @@ async function generate() {
} else { } else {
console.error( console.error(
"Failed to fetch fantasy names:", "Failed to fetch fantasy names:",
data.message || "Unknown error" data.message || "Unknown error",
); );
emit("show-toast", { emit("show-toast", {
message: `Error: ${ message: `Error: ${
@@ -319,15 +320,15 @@ async function copyToClipboard(name) {
<style scoped> <style scoped>
.name-generator-card { .name-generator-card {
background: var(--color-card); background: var(--surface);
border: 1px solid var(--color-card-border); border: 1px solid var(--border);
border-radius: var(--radius-lg); border-radius: var(--radius-lg);
box-shadow: var(--shadow-md); box-shadow: var(--shadow-md);
padding: var(--spacing-md); padding: var(--spacing-md);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--spacing-sm); gap: var(--spacing-sm);
min-height: 400px; min-height: 320px;
position: relative; position: relative;
} }
@@ -407,10 +408,10 @@ async function copyToClipboard(name) {
} }
.switch-button.active { .switch-button.active {
background: #3b82f6; background: var(--color-primary);
color: white; color: white;
font-weight: 600; font-weight: 600;
box-shadow: 0 2px 4px rgba(59, 130, 246, 0.3); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12);
} }
.switch-button + .switch-button { .switch-button + .switch-button {
@@ -443,21 +444,21 @@ select:focus {
.button-generate { .button-generate {
padding: var(--spacing-sm) var(--spacing-md); padding: var(--spacing-sm) var(--spacing-md);
background: #3b82f6; background: var(--color-primary);
color: white; color: white;
border: none; border: none;
border-radius: var(--radius-md); border-radius: var(--border-radius);
cursor: pointer; cursor: pointer;
font-weight: 600; font-weight: 600;
transition: all 0.2s ease; transition: all 0.16s ease;
margin-top: var(--spacing-sm); margin-top: var(--spacing-sm);
box-shadow: 0 2px 4px rgba(59, 130, 246, 0.2); box-shadow: var(--shadow-sm);
} }
.button-generate:hover:not(:disabled) { .button-generate:hover:not(:disabled) {
background: #2563eb; background: var(--color-accent-hover);
transform: translateY(-1px); transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(59, 130, 246, 0.3); box-shadow: var(--shadow-md);
} }
.button-generate:disabled { .button-generate:disabled {
@@ -484,11 +485,11 @@ select:focus {
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: var(--border-radius); border-radius: var(--border-radius);
cursor: pointer; cursor: pointer;
transition: all 0.2s ease; transition: all 0.14s ease;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
font-size: 0.9rem; font-size: 0.95rem;
} }
.name-item:hover { .name-item:hover {
+4 -4
View File
@@ -1,7 +1,7 @@
<template> <template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor"> <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path <circle cx="9" cy="8" r="2" />
d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z" <circle cx="15" cy="8" r="2" />
/> <path d="M7 17c1.5-2 4.5-2 7 0" />
</svg> </svg>
</template> </template>
+5 -4
View File
@@ -1,7 +1,8 @@
<template> <template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor"> <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z" <path d="M14 2v6h6" />
/> <path d="M8 12h8" />
<path d="M8 16h8" />
</svg> </svg>
</template> </template>
+3 -4
View File
@@ -1,7 +1,6 @@
<template> <template>
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor"> <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path <circle cx="12" cy="12" r="3" />
d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z" <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V20a2 2 0 1 1-4 0v-.09a1.65 1.65 0 0 0-1-1.51 1.65 1.65 0 0 0-1.82.33l-.06.06A2 2 0 1 1 6.4 16.9l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H4a2 2 0 1 1 0-4h.09a1.65 1.65 0 0 0 1.51-1 1.65 1.65 0 0 0-.33-1.82L5.2 6.4A2 2 0 1 1 8 3.57l.06.06a1.65 1.65 0 0 0 1.82.33H10a1.65 1.65 0 0 0 1-1.51V2a2 2 0 1 1 4 0v.09c0 .6.4 1.12 1 1.51h.12a1.65 1.65 0 0 0 1.82-.33L19.4 4A2 2 0 1 1 22.23 6.58l-.06.06a1.65 1.65 0 0 0-.33 1.82V10c.6 0 1.12.4 1.51 1h.09a2 2 0 1 1 0 4h-.09c-.39.6-.91 1-1.51 1z" />
/>
</svg> </svg>
</template> </template>
+4 -4
View File
@@ -1,7 +1,7 @@
<template> <template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor"> <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path <path d="M12 2a10 10 0 0 0-7.07 17.07A10 10 0 1 0 12 2z" />
d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z" <path d="M12 8v4" />
/> <path d="M12 16h.01" />
</svg> </svg>
</template> </template>
+4 -15
View File
@@ -1,19 +1,8 @@
<!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license--> <!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
<template> <template>
<svg <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
xmlns="http://www.w3.org/2000/svg" <path d="M14.7 10.3l-1-1 2-2 1 1a3 3 0 0 1 0 4.24l-1.24 1.24" />
xmlns:xlink="http://www.w3.org/1999/xlink" <path d="M11 13l-6 6" />
aria-hidden="true" <path d="M7 15l6-6" />
role="img"
class="iconify iconify--mdi"
width="24"
height="24"
preserveAspectRatio="xMidYMid meet"
viewBox="0 0 24 24"
>
<path
d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"
fill="currentColor"
></path>
</svg> </svg>
</template> </template>