Carry illustrated parchment styling into editor and drawers; hide native import controls
This commit is contained in:
parent
f064017f59
commit
10b754eb57
13
src/App.vue
13
src/App.vue
@ -8,6 +8,7 @@ import {abilities,modifier,proficiency,signed,skills,skillBonus,changeHP,newChar
|
||||
const store=useCharacters(),activeId=ref(''),tab=ref('Sheet'),drawer=ref(''),error=ref(''),message=ref(''),user=ref<{sub:string,name:string,expiresAt:number}|null>(null),loading=ref(true),offline=ref(!navigator.onLine);
|
||||
const c=computed(()=>store.all.find(x=>x.id===activeId.value));
|
||||
const draft=ref<Character|null>(null),hpAmount=ref(1),restType=ref<'short-rest'|'long-rest'>('short-rest'),rollResult=ref(''),selectedAction=ref<Character['actions'][number]|null>(null);
|
||||
const drawerIcon=computed<IconName>(()=>({Character:'sheet',Account:'notes','Hit points':'hp','Add action':'actions','Add resource':'charges','Manage resources':'slots','Add spell':'spells',Roll:'actions',Dice:'initiative',Rest:restType.value==='long-rest'?'long-rest':'short-rest'} as Record<string,IconName>)[drawer.value]||'sheet');
|
||||
const restChanges=computed(()=>c.value?restPreview(c.value,restType.value):[]);
|
||||
const actionDraft=ref({name:'',bonus:0,damage:'',notes:''}),resourceDraft=ref({name:'',current:1,max:1,reset:'long-rest' as 'short-rest'|'long-rest'|'manual'}),spellDraft=ref({name:'',level:1,prepared:true,notes:''});
|
||||
watch(c,()=>{if(c.value&&user.value)store.persist(c.value)},{deep:true});
|
||||
@ -57,9 +58,17 @@ function openAction(action:Character['actions'][number]){selectedAction.value=ac
|
||||
<footer>✦ ARROWSTACK CHARACTER <span>v0.2 · Made for game night</span></footer>
|
||||
</main>
|
||||
<nav class="bottom-nav" v-if="c"><button v-for="item in navigation" :key="item.label" :class="{active:tab===item.label}" :aria-current="tab===item.label?'page':undefined" @click="tab=item.label"><UiIcon :name="item.icon" />{{item.label}}</button></nav>
|
||||
<div v-if="drawer" class="overlay" @click.self="drawer=''"><section class="drawer" role="dialog" aria-modal="true" :aria-label="drawer"><div class="section-title"><h2>{{drawer==='Rest'?(restType==='long-rest'?'Long rest':'Short rest'):drawer}}</h2><button class="close" @click="drawer=''" aria-label="Close dialog">×</button></div>
|
||||
<div v-if="drawer" class="overlay" @click.self="drawer=''"><section class="drawer" :class="{'character-drawer':drawer==='Character'}" role="dialog" aria-modal="true" :aria-label="drawer"><div class="section-title drawer-heading"><h2><UiIcon :name="drawerIcon" />{{drawer==='Rest'?(restType==='long-rest'?'Long rest':'Short rest'):drawer}}</h2><button class="close" @click="drawer=''" aria-label="Close dialog">×</button></div>
|
||||
<template v-if="drawer==='Account'"><p class="hint">Signed in as {{user.name}}. Characters sync to your account and remain available on this device for offline play.</p><button class="primary wide" @click="logout" :disabled="offline">Sign out of Character</button><p class="hint" v-if="offline">Reconnect to sign out.</p></template>
|
||||
<form v-if="drawer==='Character'&&draft" @submit.prevent="saveDraft"><label>Name<input v-model="draft.name" required maxlength="120"></label><div class="form-grid"><label>Class<input v-model="draft.className" maxlength="120"></label><label>Level<input type="number" v-model.number="draft.level" min="1" max="20" required></label><label>Species<input v-model="draft.species" maxlength="120"></label><label>Background<input v-model="draft.background" maxlength="120"></label></div><h3>Ability scores</h3><div class="form-grid three"><label v-for="a in abilities">{{a.toUpperCase()}}<input type="number" v-model.number="draft.abilities[a]" min="1" max="30" required></label></div><div class="form-grid three"><label>Max HP<input type="number" v-model.number="draft.hp.max" min="1" max="9999" required></label><label>Current HP<input type="number" v-model.number="draft.hp.current" min="0" :max="draft.hp.max" required></label><label>Temp HP<input type="number" v-model.number="draft.hp.temp" min="0" max="9999" required></label><label>AC<input type="number" v-model.number="draft.ac" min="0" max="9999" required></label><label>Speed (ft)<input type="number" v-model.number="draft.speed" min="0" max="9999" required></label></div><h3>Saving throw proficiency</h3><div class="check-row"><label v-for="a in abilities"><input type="checkbox" :value="a" v-model="draft.saves">{{a.toUpperCase()}}</label></div><h3>Skill proficiency</h3><div class="form-grid"><label v-for="(a,name) in skills">{{name}}<select v-model="draft.skills[name]"><option value="none">None</option><option value="proficient">Proficient</option><option value="expert">Expertise</option></select></label></div><button class="primary wide">Save character</button></form>
|
||||
<form v-if="drawer==='Character'&&draft" class="character-form" @submit.prevent="saveDraft">
|
||||
<div class="editor-intro"><UiIcon name="sheet" /><div><p class="eyebrow">THE NEXT CHAPTER IS YOURS</p><h3>Bring your hero to life.</h3><p>A name, a calling, a story worth telling.</p></div></div>
|
||||
<fieldset class="folio-section"><legend><UiIcon name="notes" />Identity</legend><label>Name<input v-model="draft.name" required maxlength="120" autocomplete="off"></label><div class="form-grid"><label>Class<input v-model="draft.className" maxlength="120" placeholder="Your calling"></label><label>Level<input type="number" v-model.number="draft.level" min="1" max="20" required></label><label>Species<input v-model="draft.species" maxlength="120" placeholder="Your ancestry"></label><label>Background<input v-model="draft.background" maxlength="120" placeholder="Your beginnings"></label></div></fieldset>
|
||||
<fieldset class="folio-section"><legend><UiIcon name="str" />Ability scores</legend><div class="form-grid three ability-fields"><label v-for="a in abilities" :key="a"><span class="field-caption"><UiIcon :name="a" />{{a.toUpperCase()}}</span><input type="number" v-model.number="draft.abilities[a]" min="1" max="30" required></label></div></fieldset>
|
||||
<fieldset class="folio-section"><legend><UiIcon name="hp" />Vital statistics</legend><div class="form-grid three"><label>Max HP<input type="number" v-model.number="draft.hp.max" min="1" max="9999" required></label><label>Current HP<input type="number" v-model.number="draft.hp.current" min="0" :max="draft.hp.max" required></label><label>Temp HP<input type="number" v-model.number="draft.hp.temp" min="0" max="9999" required></label></div><div class="form-grid"><label><span class="field-caption"><UiIcon name="ac" />Armour class</span><input type="number" v-model.number="draft.ac" min="0" max="9999" required></label><label><span class="field-caption"><UiIcon name="speed" />Speed (ft)</span><input type="number" v-model.number="draft.speed" min="0" max="9999" required></label></div></fieldset>
|
||||
<fieldset class="folio-section"><legend><UiIcon name="ac" />Saving throws</legend><div class="check-row"><label v-for="a in abilities" :key="a"><input type="checkbox" :value="a" v-model="draft.saves">{{a.toUpperCase()}}</label></div></fieldset>
|
||||
<fieldset class="folio-section"><legend><UiIcon name="wis" />Skills & talents</legend><div class="form-grid"><label v-for="(a,name) in skills" :key="name">{{name}}<select v-model="draft.skills[name]"><option value="none">None</option><option value="proficient">Proficient</option><option value="expert">Expertise</option></select></label></div></fieldset>
|
||||
<div class="folio-submit"><button class="primary wide"><UiIcon name="sheet" />Save character<span aria-hidden="true">✦</span></button></div>
|
||||
</form>
|
||||
<template v-if="drawer==='Hit points'&&c"><p class="big-number">{{c.hp.current}} <small>/ {{c.hp.max}}</small></p><div class="quick-hp"><button v-for="n in [-10,-5,-1,1,5,10]" @click="hp(n)">{{signed(n)}}</button></div><label>Amount<input type="number" v-model.number="hpAmount" min="1" max="9999"></label><div class="form-grid"><button class="danger-text" @click="hp(-Math.max(0,Math.trunc(hpAmount||0)))">Take damage</button><button class="primary" @click="hp(Math.max(0,Math.trunc(hpAmount||0)))">Heal</button></div><label>Temporary HP<input type="number" :value="c.hp.temp" @change="c.hp.temp=Math.max(0,Math.min(9999,Math.trunc(Number(($event.target as HTMLInputElement).value)||0)))" min="0" max="9999"></label><p class="hint">Damage uses temporary HP first. Healing cannot exceed your maximum.</p></template>
|
||||
<form v-if="drawer==='Add action'" @submit.prevent="addAction"><label>Name<input v-model="actionDraft.name" required maxlength="120"></label><div class="form-grid"><label>Attack bonus<input type="number" v-model.number="actionDraft.bonus" min="-50" max="100" required></label><label>Damage<input v-model="actionDraft.damage" placeholder="1d8 + 4 piercing" maxlength="120"></label></div><label>Notes<textarea v-model="actionDraft.notes" maxlength="12000"></textarea></label><button class="primary wide">Add action</button></form>
|
||||
<form v-if="drawer==='Add resource'" @submit.prevent="addResource"><label>Name<input v-model="resourceDraft.name" placeholder="Level 1 spell slots" required maxlength="120"></label><div class="form-grid"><label>Remaining<input type="number" v-model.number="resourceDraft.current" min="0" :max="resourceDraft.max" required></label><label>Maximum<input type="number" v-model.number="resourceDraft.max" min="0" max="9999" required></label></div><label>Recovery<select v-model="resourceDraft.reset"><option value="short-rest">Short or long rest</option><option value="long-rest">Long rest</option><option value="manual">Manual only</option></select></label><button class="primary wide">Add resource</button></form>
|
||||
|
||||
@ -22,3 +22,47 @@ body{background:#f6e6c4 url('./assets/parchment.webp') repeat;background-size:70
|
||||
.monogram{background:#e7d19c;border-color:#ae8c4e}.monogram span{background:#f8e8bf}
|
||||
.skill-grid button:hover,.save-row button:hover{background:#f0e4bf}
|
||||
@media(max-width:700px){body{background-size:550px}.stat-card>.ui-icon{width:34px;height:34px;margin-bottom:6px}.stat-card{min-height:154px}.ability-row .ui-icon{width:29px;height:29px;margin-bottom:6px}.panel h2 .ui-icon{width:29px;height:29px}.panel h2{font-size:20px;gap:8px}.bottom-nav .ui-icon{width:34px;height:34px}.counter{gap:8px}.counter button{min-width:32px}.resource>.ui-icon{width:26px;height:26px}.section-title>.quiet{white-space:nowrap}.rest-bar .ui-icon{width:26px;height:26px}}
|
||||
|
||||
/* Keep the illustrated folio style through the working UI. */
|
||||
[hidden]{display:none!important}
|
||||
button,.button{border-color:#a7884d;background:linear-gradient(#fff0c9,#ead39d);box-shadow:inset 0 1px 0 #fff9,0 2px 0 #98743735}
|
||||
button:hover,.button:hover{background:linear-gradient(#fff6da,#f2dfb2)}
|
||||
.primary{color:#fff3cf;border:1px solid #a18c48;background:linear-gradient(#375d3e,#203c2b);box-shadow:inset 0 0 0 2px #d7b66530,0 3px 0 #172d22}
|
||||
.primary:hover{background:linear-gradient(#476d4b,#294b35)}
|
||||
.quiet,.edit,.close,.action-row,.skill-grid button{background:transparent;box-shadow:none}
|
||||
.quiet:hover,.edit:hover,.close:hover{background:#b68a3430}
|
||||
.overlay{background:#142718c9;backdrop-filter:blur(3px)}
|
||||
.drawer{background:#f3dfa8 url('./assets/parchment.webp') repeat;background-size:600px;border:1px solid #b88b40;border-top:3px solid #cfac62;border-radius:20px 20px 12px 12px;box-shadow:0 20px 80px #08170ab3,inset 0 0 0 5px #fff4cd55,inset 0 0 35px #a46d2438;padding:24px;scroll-padding-top:90px}
|
||||
.drawer-heading{position:sticky;top:-24px;margin:-24px -24px 20px;padding:18px 24px 14px;z-index:2;background:linear-gradient(#f3dfa8f5,#f5e5bdf5);border-bottom:1px solid #a37c3f;box-shadow:0 3px 10px #6b471218}
|
||||
.drawer-heading h2{display:flex;align-items:center;gap:11px;font-family:Georgia,serif;color:#233e2c;font-size:27px}
|
||||
.drawer-heading .ui-icon{width:40px;height:40px}
|
||||
.close{border:1px solid #9d773e;background:#f4dfaa;border-radius:50%;width:44px;height:44px;padding:0;color:#3c4c2e;flex-shrink:0;box-shadow:inset 0 0 0 3px #fff0c85c,0 2px 0 #76552e40}
|
||||
.drawer label{color:#594a2d;font-size:12px;font-weight:650}
|
||||
input:not([type=checkbox]),select,textarea{border:1px solid #b49254;border-bottom:2px solid #9f7d43;background:linear-gradient(#fff5dcd9,#fff1ced9);box-shadow:inset 0 2px 5px #86612d16,0 1px 0 #fff8;color:#283c28;border-radius:6px;padding:12px;font-size:16px}
|
||||
input::placeholder,textarea::placeholder{color:#88704b;font-style:italic;font-size:13px;opacity:1}
|
||||
input:focus,select:focus,textarea:focus{background:#fff5db;border-color:#567143}
|
||||
input[type=checkbox]{accent-color:#3b603d}
|
||||
.drawer .hint{color:#655538}
|
||||
.drawer .roll-result{background:#fff0c0;border:1px solid #b08b45;box-shadow:inset 0 0 0 4px #fff5d480;color:#2f462c}
|
||||
.drawer .preview-row,.drawer .resource{border-color:#b58c4555}
|
||||
.editor-intro{display:flex;align-items:center;gap:15px;margin:2px 0 24px}
|
||||
.editor-intro>.ui-icon{width:76px;height:90px;filter:drop-shadow(0 3px 2px #71502430)}
|
||||
.editor-intro h3{font-family:Georgia,serif;font-size:27px;line-height:1.1;font-weight:400;margin:0 0 7px;color:#28432c}
|
||||
.editor-intro .eyebrow{font-size:8px;letter-spacing:1.6px;color:#725b2f;margin-bottom:7px}
|
||||
.editor-intro p:last-child{font-size:12px;line-height:1.5;color:#6b5b39;margin:0}
|
||||
.folio-section{min-width:0;margin:0 0 22px;padding:15px 14px 0;border:1px solid #ab8347;border-radius:6px 14px 6px 14px;background:linear-gradient(120deg,#fff5d687,#f8e7b448);box-shadow:inset 0 0 0 3px #fff3d159,0 2px 3px #8c64251a}
|
||||
.folio-section legend{display:flex;align-items:center;gap:9px;padding:0 8px 0 3px;color:#355136;font-family:Georgia,serif;font-size:22px}
|
||||
.folio-section legend .ui-icon{width:37px;height:37px;filter:drop-shadow(0 2px 1px #78592230)}
|
||||
.folio-section .form-grid{gap:12px}
|
||||
.field-caption{display:flex;align-items:center;gap:7px;min-height:31px}
|
||||
.field-caption .ui-icon{width:29px;height:29px}
|
||||
.ability-fields label{padding:4px 7px 9px;text-align:center;background:#e4c88355;border:1px solid #b38b485c;border-radius:7px}
|
||||
.ability-fields .field-caption{justify-content:center;flex-direction:column;gap:5px;font-size:10px;letter-spacing:1.5px}
|
||||
.ability-fields .ui-icon{width:42px;height:42px}
|
||||
.ability-fields input{text-align:center;padding:8px 3px;font-family:Georgia,serif;font-size:24px;min-height:46px}
|
||||
.folio-section .check-row{gap:10px;padding:4px 0 8px}
|
||||
.folio-section .check-row label{padding:9px 10px;margin:0 0 8px;background:#fff0c37d;border:1px solid #b39256;border-radius:6px}
|
||||
.folio-submit{position:sticky;bottom:-24px;margin:0 -24px -24px;padding:12px 24px 24px;background:linear-gradient(#f5e2b900,#f3dfa8 30%);z-index:1}
|
||||
.folio-submit .wide{margin:0;display:flex;align-items:center;justify-content:center;gap:12px;font-family:Georgia,serif;font-size:20px;min-height:58px}
|
||||
.folio-submit .ui-icon{width:32px;height:32px}.folio-submit span{font-size:14px;color:#dec479;margin-left:auto}.folio-submit .ui-icon{margin-right:auto}
|
||||
@media(max-width:700px){.drawer{padding:20px 16px max(20px,env(safe-area-inset-bottom));border-radius:20px 20px 0 0;max-height:94dvh;background-size:500px}.drawer-heading{top:-20px;margin:-20px -16px 18px;padding:13px 16px 12px}.drawer-heading h2{font-size:24px}.drawer-heading .ui-icon{width:36px;height:36px}.editor-intro{gap:12px;margin-bottom:20px}.editor-intro>.ui-icon{width:65px;height:78px}.editor-intro h3{font-size:25px}.folio-section{padding:12px 11px 0;margin-bottom:20px}.folio-section legend{font-size:21px}.folio-section legend .ui-icon{width:33px;height:33px}.ability-fields .ui-icon{width:37px;height:37px}.folio-submit{bottom:-20px;margin:0 -16px -20px;padding:12px 16px max(20px,env(safe-area-inset-bottom))}.folio-section .form-grid{gap:10px}}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user