d095131eec0e | atagen | 2026-10-24 08:39 UTC | 1 | <script lang="ts"> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 2 | import { createEventDispatcher } from 'svelte'; |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 3 | import PackCloneDialog from './PackCloneDialog.svelte'; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 4 | import MxcAvatar from './MxcAvatar.svelte'; |
445bf199a9cf | atagen | 2026-10-25 02:56 UTC | 5 | import EmojiText from './EmojiText.svelte'; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 6 | import { messageMediaUrl, runCommand, uploadMedia } from './api'; |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 7 | import { disabledPackImages, setPackImageEnabled } from './pack-preferences'; |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 8 | import { imageEnabled } from './pack-preferences-model'; |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 9 | import type { CloneSource } from './pack-clone'; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 10 | import { |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 11 | allPacks, |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 12 | freePackStateKey, |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 13 | hasUsage, |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 14 | packLabel, |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 15 | packTarget, |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 16 | shortcodeFor, |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 17 | validShortcode |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 18 | } from './packs'; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 19 | import type { PackTarget } from './transport'; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 20 | import type { ImagePack, ImagePacks, PackImage, PackUsage, RoomSummary } from './types'; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 21 | |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 22 | export let packs: ImagePacks; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 23 | export let rooms: RoomSummary[]; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 24 | export let onError: (message: string) => void; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 25 | |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 26 | const dispatch = createEventDispatcher<{ close: void }>(); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 27 | |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 28 | let selectedId: string | null = 'user'; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 29 | let creating = false; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 30 | let newRoom = ''; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 31 | let newName = ''; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 32 | let confirmDelete = false; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 33 | let uploading: { done: number; total: number } | null = null; |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 34 | let cloneSource: CloneSource | null = null; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 35 | let fileInput: HTMLInputElement; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 36 | let avatarInput: HTMLInputElement; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 37 | |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 38 | const roomName = (roomId: string) => rooms.find((r) => r.room_id === roomId)?.name ?? roomId; |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 39 | const sidebarPackLabel = (pack: ImagePack) => { |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 40 | const label = packLabel(pack, roomName); |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 41 | if (pack.source === 'User' || !pack.display_name) return label; |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 42 | return `${label} · ${roomName(pack.source.Room.room_id)}`; |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 43 | }; |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 44 | const packScope = (pack: ImagePack) => { |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 45 | if (pack.source === 'User') return 'Personal pack synced to your account'; |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 46 | const roomId = pack.source.Room.room_id; |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 47 | return `${rooms.find((room) => room.room_id === roomId)?.space ? 'Space' : 'Room'} pack in ${roomName(roomId)}`; |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 48 | }; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 49 | |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 50 | $: list = allPacks(packs); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 51 | $: userPack = list.find((p) => p.source === 'User') ?? null; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 52 | $: roomPacks = list.filter((p) => p.source !== 'User' && p.editable); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 53 | $: otherPacks = list.filter((p) => p.source !== 'User' && !p.editable); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 54 | $: packRooms = rooms.filter((r) => r.can_edit_packs && r.membership === 'Joined'); |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 55 | $: spacePackRooms = packRooms.filter((room) => room.space); |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 56 | $: regularPackRooms = packRooms.filter((room) => !room.space); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 57 | $: selected = selectedId === 'user' ? userPack : (list.find((p) => p.id === selectedId) ?? null); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 58 | $: target = selectedId === 'user' ? ({ kind: 'user' } as PackTarget) : selected ? packTarget(selected) : null; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 59 | $: editable = selectedId === 'user' || (selected?.editable ?? false); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 60 | $: if (selectedId) confirmDelete = false; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 61 | |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 62 | async function run(work: () => Promise<unknown>, failure: string): Promise<boolean> { |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 63 | try { |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 64 | await work(); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 65 | return true; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 66 | } catch (e) { |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 67 | onError(e instanceof Error ? e.message : failure); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 68 | return false; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 69 | } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 70 | } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 71 | |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 72 | async function createPack(): Promise<void> { |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 73 | const name = newName.trim(); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 74 | if (!newRoom || !name) return; |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 75 | const state_key = freePackStateKey(packs, newRoom, name); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 76 | const ok = await run( |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 77 | () => runCommand({ op: 'create_room_pack', room_id: newRoom, state_key, display_name: name }), |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 78 | 'Could not create the pack' |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 79 | ); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 80 | if (!ok) return; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 81 | creating = false; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 82 | newName = ''; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 83 | selectedId = `room:${newRoom}:${state_key}`; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 84 | } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 85 | |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 86 | async function rename(event: Event): Promise<void> { |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 87 | const name = (event.currentTarget as HTMLInputElement).value.trim(); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 88 | if (!target || !name || name === (selected?.display_name ?? '')) return; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 89 | await run(() => runCommand({ op: 'set_pack_meta', target: target!, display_name: name }), 'Could not rename the pack'); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 90 | } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 91 | |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 92 | async function changeAvatar(): Promise<void> { |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 93 | const file = avatarInput.files?.[0]; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 94 | avatarInput.value = ''; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 95 | if (!file || !target) return; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 96 | const t = target; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 97 | await run(async () => { |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 98 | const { mxc } = await uploadMedia(file); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 99 | await runCommand({ op: 'set_pack_meta', target: t, avatar: mxc }); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 100 | }, 'Could not change the pack avatar'); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 101 | } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 102 | |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 103 | async function addImages(): Promise<void> { |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 104 | const files = [...(fileInput.files ?? [])].filter((f) => f.type.startsWith('image/')); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 105 | fileInput.value = ''; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 106 | if (!files.length || !target) return; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 107 | const t = target; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 108 | const taken = new Set(selected?.images.map((i) => i.shortcode) ?? []); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 109 | uploading = { done: 0, total: files.length }; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 110 | for (const file of files) { |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 111 | const shortcode = shortcodeFor(file.name, taken); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 112 | await run(async () => { |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 113 | const { mxc } = await uploadMedia(file); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 114 | const size = await imageSize(file); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 115 | const info = { mimetype: file.type || null, size_bytes: file.size, width: size?.width ?? null, height: size?.height ?? null }; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 116 | await runCommand({ op: 'put_pack_image', target: t, shortcode, mxc, body: null, usage: null, info }); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 117 | }, `Could not add ${file.name}`); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 118 | uploading = { done: uploading.done + 1, total: files.length }; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 119 | } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 120 | uploading = null; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 121 | } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 122 | |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 123 | async function imageSize(file: File): Promise<{ width: number; height: number } | null> { |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 124 | try { |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 125 | const bitmap = await createImageBitmap(file); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 126 | const size = { width: bitmap.width, height: bitmap.height }; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 127 | bitmap.close(); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 128 | return size; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 129 | } catch { |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 130 | return null; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 131 | } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 132 | } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 133 | |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 134 | async function renameImage(image: PackImage, event: Event): Promise<void> { |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 135 | const input = event.currentTarget as HTMLInputElement; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 136 | const to = input.value.trim(); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 137 | if (!target || to === image.shortcode) return; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 138 | if (!validShortcode(to) || selected?.images.some((i) => i.shortcode === to)) { |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 139 | input.value = image.shortcode; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 140 | onError(`Can't rename to "${to}"`); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 141 | return; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 142 | } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 143 | await run(() => runCommand({ op: 'rename_pack_image', target: target!, from: image.shortcode, to }), 'Could not rename'); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 144 | } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 145 | |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 146 | async function renameImageBody(image: PackImage, event: Event): Promise<void> { |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 147 | const input = event.currentTarget as HTMLInputElement; |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 148 | const body = input.value.trim() || image.shortcode; |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 149 | const currentBody = image.body?.trim() || image.shortcode; |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 150 | if (!target || body === currentBody) { |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 151 | input.value = currentBody; |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 152 | return; |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 153 | } |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 154 | const ok = await run( |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 155 | () => runCommand({ |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 156 | op: 'put_pack_image', |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 157 | target: target!, |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 158 | shortcode: image.shortcode, |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 159 | mxc: image.media_id, |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 160 | body, |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 161 | usage: null |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 162 | }), |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 163 | 'Could not rename the sticker' |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 164 | ); |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 165 | if (!ok) input.value = currentBody; |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 166 | } |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 167 | |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 168 | async function toggleUsage(image: PackImage, usage: PackUsage): Promise<void> { |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 169 | if (!target) return; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 170 | const current: PackUsage[] = image.usage.length ? image.usage : ['Emoticon', 'Sticker']; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 171 | const next = current.includes(usage) ? current.filter((u) => u !== usage) : [...current, usage]; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 172 | if (next.length === 0) return; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 173 | await run( |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 174 | () => |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 175 | runCommand({ |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 176 | op: 'put_pack_image', |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 177 | target: target!, |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 178 | shortcode: image.shortcode, |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 179 | mxc: image.media_id, |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 180 | body: image.body, |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 181 | usage: next |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 182 | }), |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 183 | 'Could not change how the image is used' |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 184 | ); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 185 | } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 186 | |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 187 | async function removeImage(image: PackImage): Promise<void> { |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 188 | if (!target) return; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 189 | await run(() => runCommand({ op: 'remove_pack_image', target: target!, shortcode: image.shortcode }), 'Could not remove'); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 190 | } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 191 | |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 192 | async function setEverywhere(enabled: boolean): Promise<void> { |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 193 | if (!selected || selected.source === 'User') return; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 194 | const { room_id, state_key } = selected.source.Room; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 195 | await run(() => runCommand({ op: 'set_pack_enabled', room_id, state_key, enabled }), 'Could not change the pack'); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 196 | } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 197 | |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 198 | async function deletePack(): Promise<void> { |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 199 | if (!target) return; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 200 | const ok = await run(() => runCommand({ op: 'delete_pack', target: target! }), 'Could not delete the pack'); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 201 | if (ok) selectedId = 'user'; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 202 | } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 203 | |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 204 | function openImageClone(image: PackImage): void { |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 205 | if (selected) cloneSource = { kind: 'image', pack: selected, image }; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 206 | } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 207 | |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 208 | async function togglePersonalImage(image: PackImage, enabled: boolean): Promise<void> { |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 209 | if (!selected) return; |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 210 | await run( |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 211 | () => setPackImageEnabled(selected!.id, image.shortcode, enabled), |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 212 | 'Could not save your image preference' |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 213 | ); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 214 | } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 215 | </script> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 216 | |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 217 | <div class="modal packs" role="dialog" aria-modal="true" aria-label="Sticker and emoji packs"> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 218 | <div class="modal-tabs" role="tablist" aria-label="Packs"> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 219 | <button type="button" role="tab" aria-selected="true" class="active">Packs</button> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 220 | <button type="button" class="icon-btn modal-x" aria-label="Close packs" on:click={() => dispatch('close')}> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 221 | <span aria-hidden="true">✕</span> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 222 | </button> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 223 | </div> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 224 | |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 225 | <div class="layout"> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 226 | <nav class="pack-list" aria-label="Packs"> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 227 | <button type="button" class="pack-item" class:active={selectedId === 'user'} on:click={() => (selectedId = 'user')}> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 228 | <MxcAvatar mxc={userPack?.avatar ?? null} label="Your pack" size={28} shape="rounded" /> |
445bf199a9cf | atagen | 2026-10-25 02:56 UTC | 229 | <span><EmojiText text={userPack?.display_name ?? 'Your pack'} /></span> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 230 | </button> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 231 | {#if roomPacks.length} |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 232 | <h4>Room and space packs</h4> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 233 | {#each roomPacks as pack (pack.id)} |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 234 | <button type="button" class="pack-item" class:active={selectedId === pack.id} on:click={() => (selectedId = pack.id)}> |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 235 | <MxcAvatar mxc={pack.avatar} label={sidebarPackLabel(pack)} size={28} shape="rounded" /> |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 236 | <span><EmojiText text={sidebarPackLabel(pack)} /></span> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 237 | </button> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 238 | {/each} |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 239 | {/if} |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 240 | {#if otherPacks.length} |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 241 | <h4>Other packs</h4> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 242 | {#each otherPacks as pack (pack.id)} |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 243 | <button type="button" class="pack-item" class:active={selectedId === pack.id} on:click={() => (selectedId = pack.id)}> |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 244 | <MxcAvatar mxc={pack.avatar} label={sidebarPackLabel(pack)} size={28} shape="rounded" /> |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 245 | <span><EmojiText text={sidebarPackLabel(pack)} /></span> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 246 | </button> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 247 | {/each} |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 248 | {/if} |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 249 | {#if packRooms.length} |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 250 | {#if creating} |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 251 | <form class="new-pack" on:submit|preventDefault={() => void createPack()}> |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 252 | <select bind:value={newRoom} aria-label="Space or room"> |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 253 | <option value="" disabled>Space or room</option> |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 254 | {#each spacePackRooms as room (room.room_id)} |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 255 | <option value={room.room_id}>Space · {room.name}</option> |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 256 | {/each} |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 257 | {#each regularPackRooms as room (room.room_id)} |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 258 | <option value={room.room_id}>Room · {room.name}</option> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 259 | {/each} |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 260 | </select> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 261 | <input type="text" placeholder="Pack name" bind:value={newName} aria-label="Pack name" /> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 262 | <div class="row"> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 263 | <button type="button" class="ghost-btn" on:click={() => (creating = false)}>Cancel</button> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 264 | <button type="submit" class="primary-btn" disabled={!newRoom || !newName.trim()}>Create</button> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 265 | </div> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 266 | </form> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 267 | {:else} |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 268 | <button type="button" class="ghost-btn new-btn" on:click={() => (creating = true)}>New pack</button> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 269 | {/if} |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 270 | {/if} |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 271 | </nav> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 272 | |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 273 | <section class="editor" aria-label="Pack"> |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 274 | {#if selectedId === 'user'} |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 275 | <p class="scope">Personal pack synced to your account</p> |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 276 | {:else if selected} |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 277 | <p class="scope">{packScope(selected)}</p> |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 278 | {/if} |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 279 | {#if editable && target} |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 280 | <div class="meta"> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 281 | <button type="button" class="avatar-btn" title="Change pack avatar" on:click={() => avatarInput.click()}> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 282 | <MxcAvatar mxc={selected?.avatar ?? null} label={selected?.display_name ?? 'Pack'} size={48} shape="rounded" /> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 283 | </button> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 284 | <input |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 285 | class="name" |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 286 | type="text" |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 287 | value={selected?.display_name ?? ''} |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 288 | placeholder={selectedId === 'user' ? 'Your pack' : 'Pack name'} |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 289 | aria-label="Pack name" |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 290 | on:change={(e) => void rename(e)} |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 291 | /> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 292 | <input type="file" accept="image/*" class="visually-hidden" bind:this={avatarInput} on:change={() => void changeAvatar()} /> |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 293 | {#if selected} |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 294 | <button type="button" class="ghost-btn" on:click={() => selected && (cloneSource = { kind: 'pack', pack: selected })}>Clone pack</button> |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 295 | {/if} |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 296 | </div> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 297 | <div class="row"> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 298 | {#if selected && selected.source !== 'User'} |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 299 | <label class="check"> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 300 | <input type="checkbox" checked={selected.enabled_everywhere} on:change={(e) => void setEverywhere(e.currentTarget.checked)} /> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 301 | Use everywhere |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 302 | </label> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 303 | {/if} |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 304 | <span class="spacer"></span> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 305 | <button type="button" class="primary-btn" disabled={uploading !== null} on:click={() => fileInput.click()}> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 306 | {uploading ? `Uploading ${uploading.done}/${uploading.total}` : 'Add images'} |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 307 | </button> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 308 | <input type="file" accept="image/*" multiple class="visually-hidden" bind:this={fileInput} on:change={() => void addImages()} /> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 309 | {#if selected} |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 310 | {#if confirmDelete} |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 311 | <button type="button" class="ghost-btn" on:click={() => (confirmDelete = false)}>Keep</button> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 312 | <button type="button" class="ghost-btn danger" on:click={() => void deletePack()}>Delete pack</button> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 313 | {:else} |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 314 | <button type="button" class="ghost-btn danger" on:click={() => (confirmDelete = true)}>Delete</button> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 315 | {/if} |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 316 | {/if} |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 317 | </div> |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 318 | <p class="preference-note">Hiding an image with Use for me only affects you, and syncs to your account. Emoji and sticker settings apply for everyone.</p> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 319 | <div class="images"> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 320 | {#each selected?.images ?? [] as image (image.shortcode)} |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 321 | <div class="image"> |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 322 | <img class:hidden={!imageEnabled($disabledPackImages, selected?.id ?? 'user', image.shortcode)} src={messageMediaUrl(image.media_id, 'Thumbnail')} alt={image.shortcode} loading="lazy" /> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 323 | <input type="text" value={image.shortcode} aria-label="Shortcode" on:change={(e) => void renameImage(image, e)} /> |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 324 | <input type="text" value={image.body?.trim() || image.shortcode} aria-label="Sticker name" on:change={(e) => void renameImageBody(image, e)} /> |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 325 | <label class="personal-check"> |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 326 | <input type="checkbox" checked={imageEnabled($disabledPackImages, selected?.id ?? 'user', image.shortcode)} on:change={(e) => void togglePersonalImage(image, e.currentTarget.checked)} /> |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 327 | Use for me |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 328 | </label> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 329 | <div class="usage"> |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 330 | <button type="button" aria-pressed={hasUsage(image, 'Emoticon')} title="Emoji for everyone" aria-label="Emoji for everyone" on:click={() => void toggleUsage(image, 'Emoticon')}>☺</button> |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 331 | <button type="button" aria-pressed={hasUsage(image, 'Sticker')} title="Sticker for everyone" aria-label="Sticker for everyone" on:click={() => void toggleUsage(image, 'Sticker')}>▣</button> |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 332 | <button type="button" class="ghost-btn clone" on:click={() => openImageClone(image)}>Clone</button> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 333 | <button type="button" class="remove" title="Remove" aria-label={`Remove ${image.shortcode}`} on:click={() => void removeImage(image)}>✕</button> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 334 | </div> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 335 | </div> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 336 | {:else} |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 337 | <p class="empty">No images yet</p> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 338 | {/each} |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 339 | </div> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 340 | {:else if selected} |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 341 | <div class="meta"> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 342 | <MxcAvatar mxc={selected.avatar} label={packLabel(selected, roomName)} size={48} shape="rounded" /> |
445bf199a9cf | atagen | 2026-10-25 02:56 UTC | 343 | <strong class="name"><EmojiText text={packLabel(selected, roomName)} /></strong> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 344 | <span class="spacer"></span> |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 345 | <button type="button" class="primary-btn" on:click={() => (cloneSource = { kind: 'pack', pack: selected })}>Clone pack</button> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 346 | </div> |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 347 | {#if selected.source !== 'User'} |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 348 | <label class="check"> |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 349 | <input type="checkbox" checked={selected.enabled_everywhere} on:change={(e) => void setEverywhere(e.currentTarget.checked)} /> |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 350 | Use everywhere |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 351 | </label> |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 352 | {/if} |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 353 | <p class="preference-note">Hiding an image with Use for me only affects you, and syncs to your account. Emoji and sticker settings apply for everyone.</p> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 354 | <div class="images"> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 355 | {#each selected.images as image (image.shortcode)} |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 356 | <div class="image"> |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 357 | <img class:hidden={!imageEnabled($disabledPackImages, selected?.id ?? 'user', image.shortcode)} src={messageMediaUrl(image.media_id, 'Thumbnail')} alt={image.shortcode} loading="lazy" /> |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 358 | <span class="code">{image.body?.trim() ? `${image.body.trim()} · ${image.shortcode}` : image.shortcode}</span> |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 359 | <label class="personal-check"> |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 360 | <input type="checkbox" checked={imageEnabled($disabledPackImages, selected?.id ?? 'user', image.shortcode)} on:change={(e) => void togglePersonalImage(image, e.currentTarget.checked)} /> |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 361 | Use for me |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 362 | </label> |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 363 | <button type="button" class="ghost-btn" on:click={() => openImageClone(image)}>Clone</button> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 364 | </div> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 365 | {/each} |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 366 | </div> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 367 | {/if} |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 368 | </section> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 369 | </div> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 370 | </div> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 371 | |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 372 | {#if cloneSource} |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 373 | <div class="modal-wrap"> |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 374 | <PackCloneDialog |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 375 | source={cloneSource} |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 376 | {packs} |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 377 | {rooms} |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 378 | onClose={() => (cloneSource = null)} |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 379 | {onError} |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 380 | /> |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 381 | </div> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 382 | {/if} |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 383 | |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 384 | <style> |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 385 | .packs { width: min(820px, calc(100vw - 32px)); max-height: calc(100vh - 64px); display: flex; flex-direction: column; } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 386 | .layout { display: flex; gap: 14px; min-height: 0; flex: 1; } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 387 | .pack-list { flex: none; width: 210px; display: flex; flex-direction: column; gap: 2px; overflow-y: auto; } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 388 | h4 { margin: 10px 6px 4px; font-size: 12px; color: var(--muted); font-weight: 650; } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 389 | .pack-item { |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 390 | display: flex; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 391 | align-items: center; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 392 | gap: 8px; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 393 | padding: 6px 8px; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 394 | border: none; |
05a48e9e7942 | atagen | 2026-10-24 09:32 UTC | 395 | border-radius: var(--r-md); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 396 | background: transparent; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 397 | color: var(--text); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 398 | font: inherit; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 399 | font-size: 13.5px; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 400 | text-align: left; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 401 | cursor: pointer; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 402 | } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 403 | .pack-item span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 404 | .pack-item:hover { background: var(--hover); } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 405 | .pack-item.active { background: color-mix(in srgb, var(--accent) 20%, transparent); } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 406 | .new-btn { margin-top: 8px; } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 407 | .new-pack { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; } |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 408 | .scope, .preference-note { margin: 0; color: var(--muted); font-size: 12px; } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 409 | .editor { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 10px; overflow-y: auto; } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 410 | .meta { display: flex; align-items: center; gap: 10px; } |
05a48e9e7942 | atagen | 2026-10-24 09:32 UTC | 411 | .avatar-btn { padding: 0; border: none; background: none; cursor: pointer; border-radius: var(--r-md); } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 412 | .name { flex: 1; font-size: 15px; } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 413 | .row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 414 | .spacer { flex: 1; } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 415 | .check { display: flex; align-items: center; gap: 6px; font-size: 13px; } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 416 | input[type='text'], select { |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 417 | background: var(--bg-soft); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 418 | border: 1px solid var(--border); |
05a48e9e7942 | atagen | 2026-10-24 09:32 UTC | 419 | border-radius: var(--r-sm); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 420 | color: var(--text); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 421 | padding: 6px 8px; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 422 | font: inherit; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 423 | font-size: 13px; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 424 | min-width: 0; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 425 | } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 426 | .images { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 8px; } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 427 | .image { |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 428 | display: flex; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 429 | flex-direction: column; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 430 | gap: 4px; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 431 | padding: 6px; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 432 | border: 1px solid var(--border); |
05a48e9e7942 | atagen | 2026-10-24 09:32 UTC | 433 | border-radius: var(--r-md); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 434 | background: var(--bg-soft); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 435 | } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 436 | .image img { width: 100%; aspect-ratio: 1; object-fit: contain; } |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 437 | .image img.hidden { opacity: 0.35; } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 438 | .image input { width: 100%; font-size: 12px; padding: 3px 6px; } |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 439 | .personal-check { display: flex; align-items: center; gap: 5px; font-size: 12px; color: var(--muted); } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 440 | .code { font-size: 12px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 441 | .usage { display: flex; gap: 4px; } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 442 | .usage button { |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 443 | flex: 1; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 444 | padding: 2px 0; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 445 | border: 1px solid var(--border); |
05a48e9e7942 | atagen | 2026-10-24 09:32 UTC | 446 | border-radius: var(--r-sm); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 447 | background: transparent; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 448 | color: var(--muted); |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 449 | cursor: pointer; |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 450 | } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 451 | .usage button[aria-pressed='true'] { color: var(--accent); border-color: var(--accent); } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 452 | .usage .remove { flex: none; padding: 2px 6px; } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 453 | .usage .remove:hover { color: var(--danger); border-color: var(--danger); } |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 454 | .usage .clone { flex: none; padding: 2px 6px; font-size: 12px; } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 455 | .empty { color: var(--muted); font-size: 13px; } |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 456 | .modal-wrap { position: fixed; inset: 0; z-index: 70; display: grid; place-items: center; background: rgb(0 0 0 / 55%); } |
d095131eec0e | atagen | 2026-10-24 08:39 UTC | 457 | </style> |