Name Generator Rework
This commit is contained in:
+198
-317
@@ -1,337 +1,218 @@
|
||||
<template>
|
||||
<div class="name-generator">
|
||||
<h2>Name Generator</h2>
|
||||
<div class="controls">
|
||||
<div class="name-type-switch">
|
||||
<span class="switch-label">Name Type:</span>
|
||||
<div class="switch-container">
|
||||
<button
|
||||
type="button"
|
||||
class="switch-button"
|
||||
:class="{ active: !modern }"
|
||||
@click="modern = false"
|
||||
>
|
||||
Fantasy
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="switch-button button-switch"
|
||||
:class="{ active: modern }"
|
||||
@click="modern = true"
|
||||
>
|
||||
Modern
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<label v-if="!modern">
|
||||
Race:
|
||||
<select v-model="race">
|
||||
<option v-for="r in races" :key="r.value" :value="r.value">
|
||||
{{ r.label }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Gender:
|
||||
<select v-model="gender">
|
||||
<option value="male">Male</option>
|
||||
<option value="female">Female</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Count:
|
||||
<select v-model="count">
|
||||
<option :value="1">1 name</option>
|
||||
<option :value="3">3 names</option>
|
||||
<option :value="5">5 names</option>
|
||||
<option :value="8">8 names</option>
|
||||
<option :value="10">10 names</option>
|
||||
<option :value="15">15 names</option>
|
||||
<option :value="20">20 names</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<button class="button-main" @click="generate">Generate</button>
|
||||
<ul class="results">
|
||||
<li
|
||||
v-for="name in fullNames"
|
||||
:key="name"
|
||||
@click="copyToClipboard(name)"
|
||||
class="name-item"
|
||||
:title="'Click to copy: ' + name"
|
||||
>
|
||||
{{ name }}
|
||||
<svg
|
||||
class="copy-icon"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
<div class="name-generator-layout">
|
||||
<div class="page-header">
|
||||
<h2>Name Generator</h2>
|
||||
<div class="page-controls">
|
||||
<button
|
||||
class="button-secondary"
|
||||
@click="addGenerator"
|
||||
:disabled="generators.length >= 6"
|
||||
>
|
||||
<rect
|
||||
x="9"
|
||||
y="9"
|
||||
width="13"
|
||||
height="13"
|
||||
rx="2"
|
||||
ry="2"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
/>
|
||||
<path
|
||||
d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<line
|
||||
x1="12"
|
||||
y1="5"
|
||||
x2="12"
|
||||
y2="19"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
/>
|
||||
<line
|
||||
x1="5"
|
||||
y1="12"
|
||||
x2="19"
|
||||
y2="12"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
/>
|
||||
</svg>
|
||||
Add Generator
|
||||
</button>
|
||||
<button
|
||||
class="button-secondary"
|
||||
@click="removeGenerator"
|
||||
:disabled="generators.length <= 1"
|
||||
>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
/>
|
||||
</svg>
|
||||
</li>
|
||||
</ul>
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<line
|
||||
x1="5"
|
||||
y1="12"
|
||||
x2="19"
|
||||
y2="12"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
/>
|
||||
</svg>
|
||||
Remove Generator
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Toast notification -->
|
||||
<div v-if="showToast" class="toast">
|
||||
{{ toastMessage }}
|
||||
<div class="generators-grid" :class="`grid-${generators.length}`">
|
||||
<NameGeneratorCard v-for="id in generators" :key="id" :card-id="id" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import NameGeneratorCard from "./NameGeneratorCard.vue";
|
||||
|
||||
const modern = ref(false);
|
||||
const races = [
|
||||
{ label: "Dragonborn", value: "dragonborn" },
|
||||
{ label: "Dwarf", value: "dwarf" },
|
||||
{ label: "Elf", value: "elf" },
|
||||
{ label: "Gnome", value: "gnome" },
|
||||
{ label: "Goblin", value: "goblin" },
|
||||
{ label: "Half-Elf", value: "half-elf" },
|
||||
{ label: "Half-Orc", value: "half-orc" },
|
||||
{ label: "Halfling", value: "halfling" },
|
||||
{ label: "Human", value: "human" },
|
||||
{ label: "Orc", value: "orc" },
|
||||
{ label: "Tiefling", value: "tiefling" },
|
||||
{ label: "Troll", value: "troll" },
|
||||
];
|
||||
const generators = ref([1, 2, 3, 4]);
|
||||
let nextId = 5;
|
||||
|
||||
// Modern name arrays
|
||||
const modernMaleNames = [
|
||||
"James",
|
||||
"John",
|
||||
"Robert",
|
||||
"Michael",
|
||||
"William",
|
||||
"David",
|
||||
"Richard",
|
||||
"Charles",
|
||||
"Joseph",
|
||||
"Thomas",
|
||||
"Christopher",
|
||||
"Daniel",
|
||||
"Paul",
|
||||
"Mark",
|
||||
"Donald",
|
||||
"Steven",
|
||||
"Andrew",
|
||||
"Kenneth",
|
||||
"Joshua",
|
||||
"Kevin",
|
||||
"Brian",
|
||||
"George",
|
||||
"Timothy",
|
||||
"Ronald",
|
||||
"Jason",
|
||||
"Edward",
|
||||
"Jeffrey",
|
||||
"Ryan",
|
||||
"Jacob",
|
||||
"Gary",
|
||||
"Nicholas",
|
||||
"Eric",
|
||||
"Jonathan",
|
||||
"Stephen",
|
||||
"Larry",
|
||||
"Justin",
|
||||
"Scott",
|
||||
"Brandon",
|
||||
"Benjamin",
|
||||
"Samuel",
|
||||
];
|
||||
|
||||
const modernFemaleNames = [
|
||||
"Mary",
|
||||
"Patricia",
|
||||
"Jennifer",
|
||||
"Linda",
|
||||
"Elizabeth",
|
||||
"Barbara",
|
||||
"Susan",
|
||||
"Jessica",
|
||||
"Sarah",
|
||||
"Karen",
|
||||
"Nancy",
|
||||
"Lisa",
|
||||
"Betty",
|
||||
"Helen",
|
||||
"Sandra",
|
||||
"Donna",
|
||||
"Carol",
|
||||
"Ruth",
|
||||
"Sharon",
|
||||
"Michelle",
|
||||
"Laura",
|
||||
"Sarah",
|
||||
"Kimberly",
|
||||
"Deborah",
|
||||
"Dorothy",
|
||||
"Lisa",
|
||||
"Nancy",
|
||||
"Karen",
|
||||
"Betty",
|
||||
"Helen",
|
||||
"Sandra",
|
||||
"Donna",
|
||||
"Carol",
|
||||
"Ruth",
|
||||
"Sharon",
|
||||
"Michelle",
|
||||
"Laura",
|
||||
"Amy",
|
||||
"Angela",
|
||||
"Brenda",
|
||||
];
|
||||
|
||||
const modernLastNames = [
|
||||
"Smith",
|
||||
"Johnson",
|
||||
"Williams",
|
||||
"Brown",
|
||||
"Jones",
|
||||
"Garcia",
|
||||
"Miller",
|
||||
"Davis",
|
||||
"Rodriguez",
|
||||
"Martinez",
|
||||
"Hernandez",
|
||||
"Lopez",
|
||||
"Gonzalez",
|
||||
"Wilson",
|
||||
"Anderson",
|
||||
"Thomas",
|
||||
"Taylor",
|
||||
"Moore",
|
||||
"Jackson",
|
||||
"Martin",
|
||||
"Lee",
|
||||
"Perez",
|
||||
"Thompson",
|
||||
"White",
|
||||
"Harris",
|
||||
"Sanchez",
|
||||
"Clark",
|
||||
"Ramirez",
|
||||
"Lewis",
|
||||
"Robinson",
|
||||
"Walker",
|
||||
"Young",
|
||||
"Allen",
|
||||
"King",
|
||||
"Wright",
|
||||
"Scott",
|
||||
"Torres",
|
||||
"Nguyen",
|
||||
"Hill",
|
||||
"Flores",
|
||||
"Green",
|
||||
"Adams",
|
||||
"Nelson",
|
||||
"Baker",
|
||||
"Hall",
|
||||
"Rivera",
|
||||
"Campbell",
|
||||
"Mitchell",
|
||||
"Carter",
|
||||
"Roberts",
|
||||
];
|
||||
|
||||
const race = ref(races[0].value);
|
||||
const gender = ref("male");
|
||||
const count = ref(5);
|
||||
const fullNames = ref([]);
|
||||
const showToast = ref(false);
|
||||
const toastMessage = ref("");
|
||||
|
||||
function getRandomName(array) {
|
||||
return array[Math.floor(Math.random() * array.length)];
|
||||
}
|
||||
|
||||
function generateModernNames() {
|
||||
const names = [];
|
||||
const firstNames =
|
||||
gender.value === "male" ? modernMaleNames : modernFemaleNames;
|
||||
|
||||
for (let i = 0; i < count.value; i++) {
|
||||
const firstName = getRandomName(firstNames);
|
||||
const lastName = getRandomName(modernLastNames);
|
||||
names.push(`${firstName} ${lastName}`);
|
||||
}
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
async function generate() {
|
||||
fullNames.value = [];
|
||||
|
||||
if (modern.value) {
|
||||
// Generate modern names locally
|
||||
fullNames.value = generateModernNames();
|
||||
} else {
|
||||
// Use API for fantasy names
|
||||
const selectedRace = race.value;
|
||||
const firstUrl = `https://names.ironarachne.com/race/${selectedRace}/${gender.value}/${count.value}`;
|
||||
const familyUrl = `https://names.ironarachne.com/race/${selectedRace}/family/${count.value}`;
|
||||
try {
|
||||
const [firstRes, familyRes] = await Promise.all([
|
||||
fetch(firstUrl),
|
||||
fetch(familyUrl),
|
||||
]);
|
||||
const firstData = await firstRes.json();
|
||||
const familyData = await familyRes.json();
|
||||
fullNames.value = firstData.names.map(
|
||||
(n, i) => `${n} ${familyData.names[i]}`
|
||||
);
|
||||
} catch (e) {
|
||||
console.error("Error fetching names", e);
|
||||
}
|
||||
function addGenerator() {
|
||||
if (generators.value.length < 6) {
|
||||
generators.value.push(nextId++);
|
||||
}
|
||||
}
|
||||
|
||||
async function copyToClipboard(name) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(name);
|
||||
showToastMessage(`"${name}" copied to clipboard!`);
|
||||
} catch (err) {
|
||||
console.error("Failed to copy to clipboard:", err);
|
||||
// Fallback for older browsers
|
||||
const textArea = document.createElement("textarea");
|
||||
textArea.value = name;
|
||||
document.body.appendChild(textArea);
|
||||
textArea.select();
|
||||
document.execCommand("copy");
|
||||
document.body.removeChild(textArea);
|
||||
showToastMessage(`"${name}" copied to clipboard!`);
|
||||
function removeGenerator() {
|
||||
if (generators.value.length > 1) {
|
||||
generators.value.pop();
|
||||
}
|
||||
}
|
||||
|
||||
function showToastMessage(message) {
|
||||
toastMessage.value = message;
|
||||
showToast.value = true;
|
||||
setTimeout(() => {
|
||||
showToast.value = false;
|
||||
}, 2000);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped src="./NameGenerator.css"></style>
|
||||
<style scoped>
|
||||
.name-generator-layout {
|
||||
padding: var(--spacing-lg);
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: var(--spacing-lg);
|
||||
padding-bottom: var(--spacing-md);
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.page-header h2 {
|
||||
margin: 0;
|
||||
color: var(--text-primary);
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.page-controls {
|
||||
display: flex;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.button-secondary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs);
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
background: var(--surface);
|
||||
color: var(--text-primary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--border-radius);
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.button-secondary:hover:not(:disabled) {
|
||||
background: var(--surface-hover);
|
||||
border-color: var(--border-hover);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.button-secondary:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.generators-grid {
|
||||
display: grid;
|
||||
gap: var(--spacing-lg);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.grid-1 {
|
||||
grid-template-columns: 1fr;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.grid-2 {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.grid-3 {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.grid-4 {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
grid-template-rows: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.grid-5 {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-rows: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.grid-6 {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-rows: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 1024px) {
|
||||
.grid-3,
|
||||
.grid-4,
|
||||
.grid-5,
|
||||
.grid-6 {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
grid-template-rows: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.name-generator-layout {
|
||||
padding: var(--spacing-md);
|
||||
}
|
||||
|
||||
.page-header {
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-md);
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.page-controls {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.generators-grid {
|
||||
grid-template-columns: 1fr !important;
|
||||
grid-template-rows: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.page-controls {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.button-secondary {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user