feat: 扩展弹框配置重构
All checks were successful
Extension Build & Release / build (push) Successful in 53s
All checks were successful
Extension Build & Release / build (push) Successful in 53s
This commit is contained in:
@@ -106,131 +106,96 @@ const copyToClipboard = async (reply: any) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<transition
|
||||
enter-active-class="transition-transform duration-500 ease-[cubic-bezier(0.2,0.8,0.2,1)]"
|
||||
enter-from-class="translate-x-full"
|
||||
enter-to-class="translate-x-0"
|
||||
leave-active-class="transition-transform duration-400 ease-[cubic-bezier(0.2,0.8,0.2,1)]"
|
||||
leave-from-class="translate-x-0"
|
||||
leave-to-class="translate-x-full"
|
||||
>
|
||||
<div v-if="isVisible" class="fixed right-0 top-0 bottom-0 w-[400px] flex flex-col bg-zinc-950/90 backdrop-blur-[40px] border-l border-white/10 shadow-[-10px_0_40px_rgba(0,0,0,0.5)] text-[#f8fafc] font-sans z-[2147483647] overflow-hidden selection:bg-rose-500/30">
|
||||
|
||||
<!-- Premium Header -->
|
||||
<div class="px-6 py-5 flex justify-between items-center bg-white/[0.02] border-b border-white/5 shrink-0">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-2 rounded-xl bg-gradient-to-br from-rose-500 to-rose-600 shadow-lg shadow-rose-900/20">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" class="text-white"><path d="m12 14 4-4"/><path d="M3.34 19a10 10 0 1 1 17.32 0"/></svg>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-[15px] font-black tracking-tight block text-white drop-shadow-sm">InsightReply</span>
|
||||
<span class="text-[9px] text-zinc-400 uppercase tracking-[0.2em] font-bold">Spatial Copilot</span>
|
||||
</div>
|
||||
<div v-if="isVisible" class="ir-panel">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="ir-header">
|
||||
<div class="ir-brand">
|
||||
<div class="ir-logo">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="m12 14 4-4"/><path d="M3.34 19a10 10 0 1 1 17.32 0"/></svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="ir-brand-name">InsightReply</div>
|
||||
<div class="ir-brand-sub">Spatial Copilot</div>
|
||||
</div>
|
||||
<button @click="isVisible = false" class="p-2.5 rounded-full bg-white/5 hover:bg-rose-500/20 hover:text-rose-400 border border-white/5 transition-apple group">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" class="text-zinc-400 group-hover:text-rose-400 transition-apple"><path d="M18 6L6 18M6 6l12 12"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<button class="ir-close-btn" @click="isVisible = false">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M18 6L6 18M6 6l12 12"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="p-6 flex-1 overflow-y-auto space-y-8 custom-scrollbar">
|
||||
<div class="ir-content">
|
||||
|
||||
<!-- Context Module -->
|
||||
<div v-if="tweetData" class="space-y-3">
|
||||
<h3 class="text-[10px] font-bold text-zinc-500 uppercase tracking-widest flex items-center gap-2 px-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" class="text-rose-500"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
|
||||
<!-- Tweet Context -->
|
||||
<div v-if="tweetData">
|
||||
<div class="ir-label">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
|
||||
Current Tweet
|
||||
</h3>
|
||||
<div class="bg-black/20 rounded-2xl p-4 border border-white/[0.03] relative group">
|
||||
<div class="text-[13px] text-zinc-400 leading-relaxed font-medium italic">
|
||||
"{{ tweetData.content }}"
|
||||
</div>
|
||||
</div>
|
||||
<div class="ir-tweet-card">
|
||||
<div class="ir-tweet-text">"{{ tweetData.content }}"</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Strategy Grid -->
|
||||
<div class="space-y-4">
|
||||
<h3 class="text-[10px] font-bold text-zinc-500 uppercase tracking-widest flex items-center gap-2 px-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" class="text-rose-500"><path d="m16 4 4 4-4 4"/><path d="M20 8H4v12"/></svg>
|
||||
<div>
|
||||
<div class="ir-label">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><path d="m16 4 4 4-4 4"/><path d="M20 8H4v12"/></svg>
|
||||
Generation Strategy
|
||||
</h3>
|
||||
<div class="grid grid-cols-1 gap-2.5">
|
||||
</div>
|
||||
<div class="ir-strategy-grid">
|
||||
<button
|
||||
v-for="s in strategies"
|
||||
:key="s.id"
|
||||
@click="selectedStrategy = s.id"
|
||||
:class="[
|
||||
'flex items-center gap-4 p-4 rounded-2xl border transition-apple group text-sm font-semibold active:scale-[0.98]',
|
||||
selectedStrategy === s.id
|
||||
? 'bg-rose-500/10 border-rose-500/30 text-rose-50 text-white shadow-[0_8px_16px_rgba(136,19,55,0.15)]'
|
||||
: 'bg-white/[0.02] border-white/5 hover:bg-white/[0.05] text-zinc-400'
|
||||
]"
|
||||
:class="['ir-strategy-btn', selectedStrategy === s.id ? 'active' : '']"
|
||||
>
|
||||
<div :class="['p-2 rounded-lg transition-apple', selectedStrategy === s.id ? 'bg-rose-500/20 text-rose-400' : 'bg-black/20 text-zinc-500 group-hover:text-zinc-300']">
|
||||
<div class="ir-strategy-icon">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" v-html="s.icon"></svg>
|
||||
</div>
|
||||
<span class="flex-1 text-left tracking-tight">{{ s.label }}</span>
|
||||
<div v-if="selectedStrategy === s.id" class="w-2 h-2 rounded-full bg-rose-500 shadow-[0_0_8px_#f43f5e]"></div>
|
||||
<span>{{ s.label }}</span>
|
||||
<div v-if="selectedStrategy === s.id" class="ir-strategy-dot"></div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Result Module -->
|
||||
<div v-if="generatedReplies.length > 0" class="space-y-4 animate-in fade-in slide-in-from-bottom-4 duration-700">
|
||||
<h3 class="text-[10px] font-bold text-zinc-500 uppercase tracking-widest px-1">AI Suggestions</h3>
|
||||
<!-- Reply Results -->
|
||||
<div v-if="generatedReplies.length > 0" class="ir-fade-in">
|
||||
<div class="ir-label">AI Suggestions</div>
|
||||
|
||||
<div v-for="(reply, idx) in generatedReplies" :key="idx" class="bg-white/[0.03] rounded-2xl p-5 border border-white/5 space-y-4 relative group hover:border-rose-500/20 transition-apple">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-[9px] font-bold text-rose-400 bg-rose-400/10 px-2.5 py-1 rounded-full uppercase tracking-widest">
|
||||
{{ reply.strategy || 'Insight' }}
|
||||
</span>
|
||||
<button @click="copyToClipboard(reply)" class="opacity-0 group-hover:opacity-100 transition-apple text-[10px] font-bold text-zinc-500 hover:text-white flex items-center gap-2 px-2 py-1 rounded-lg hover:bg-white/5">
|
||||
<div v-for="(reply, idx) in generatedReplies" :key="idx" class="ir-reply-card" style="margin-bottom: 12px;">
|
||||
<div class="ir-reply-header">
|
||||
<span class="ir-reply-tag">{{ reply.strategy || 'Insight' }}</span>
|
||||
<button class="ir-copy-btn" @click="copyToClipboard(reply)">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>
|
||||
COPY
|
||||
</button>
|
||||
</div>
|
||||
<div class="text-[13px] leading-relaxed whitespace-pre-wrap text-zinc-200 font-medium tracking-tight">
|
||||
{{ reply.content }}
|
||||
</div>
|
||||
<div class="ir-reply-content">{{ reply.content }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Sticky Action Footer -->
|
||||
<div class="p-6 bg-white/[0.02] border-t border-white/5 space-y-4 shadow-[0_-12px_24px_rgba(0,0,0,0.2)]">
|
||||
<transition enter-active-class="transition duration-500 transform ease-out" enter-from-class="opacity-0 translate-y-4" enter-to-class="opacity-100 translate-y-0" leave-active-class="transition duration-300 transform ease-in" leave-from-class="opacity-100 translate-y-0" leave-to-class="opacity-0 translate-y-4">
|
||||
<div v-if="showProfileTip" class="bg-rose-500/10 border border-rose-500/20 text-rose-400 text-[11px] p-4 rounded-2xl flex gap-3 items-start relative font-medium leading-relaxed">
|
||||
<span class="text-base leading-none">✨</span>
|
||||
<p><strong>Optimize Conversion:</strong> Make sure your X Bio prominently features your product link!</p>
|
||||
<button @click="showProfileTip = false" class="absolute top-3 right-3 opacity-50 hover:opacity-100">✕</button>
|
||||
</div>
|
||||
</transition>
|
||||
<!-- Footer -->
|
||||
<div class="ir-footer">
|
||||
<div v-if="showProfileTip" class="ir-tip">
|
||||
<span>✨</span>
|
||||
<p><strong>Optimize Conversion:</strong> Make sure your X Bio prominently features your product link!</p>
|
||||
<button class="ir-tip-close" @click="showProfileTip = false">✕</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
@click="generate"
|
||||
:disabled="isGenerating"
|
||||
class="w-full py-4 bg-gradient-to-br from-rose-600 to-rose-500 hover:from-rose-500 hover:to-rose-400 disabled:from-zinc-900 disabled:to-zinc-900 disabled:text-zinc-600 disabled:cursor-not-allowed text-white rounded-[20px] text-[15px] font-bold transition-all shadow-[0_12px_24px_rgba(136,19,55,0.25)] flex items-center justify-center gap-3 active:scale-[0.97]"
|
||||
class="ir-generate-btn"
|
||||
>
|
||||
<div v-if="isGenerating" class="w-5 h-5 border-2 border-white/20 border-t-white rounded-full animate-spin"></div>
|
||||
<svg v-else xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="text-rose-100"><path d="m12 3-1.912 5.813a2 2 0 0 1-1.275 1.275L3 12l5.813 1.912a2 2 0 0 1 1.275 1.275L12 21l1.912-5.813a2 2 0 0 1 1.275-1.275L21 12l-5.813-1.912a2 2 0 0 1-1.275-1.275L12 3Z"/></svg>
|
||||
<div v-if="isGenerating" class="ir-spinner"></div>
|
||||
<svg v-else xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="m12 3-1.912 5.813a2 2 0 0 1-1.275 1.275L3 12l5.813 1.912a2 2 0 0 1 1.275 1.275L12 21l1.912-5.813a2 2 0 0 1 1.275-1.275L21 12l-5.813-1.912a2 2 0 0 1-1.275-1.275L12 3Z"/></svg>
|
||||
{{ isGenerating ? 'AI Thinking...' : 'Generate Pro Reply' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.custom-scrollbar::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 10px;
|
||||
}
|
||||
.custom-scrollbar:hover::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -10,6 +10,405 @@ const sidebarVisible = ref(false)
|
||||
let isMounted = false
|
||||
let isMounting = false
|
||||
|
||||
// All CSS is embedded here so it works inside Shadow DOM
|
||||
// No dependency on Tailwind or external stylesheets
|
||||
const SIDEBAR_CSS = `
|
||||
/* ===== Reset & Base ===== */
|
||||
*, *::before, *::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* ===== Sidebar Panel ===== */
|
||||
.ir-panel {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 400px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #09090b;
|
||||
border-left: 1px solid rgba(255,255,255,0.08);
|
||||
box-shadow: -12px 0 40px rgba(0,0,0,0.6);
|
||||
color: #f8fafc;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Segoe UI', Roboto, sans-serif;
|
||||
z-index: 2147483647;
|
||||
overflow: hidden;
|
||||
pointer-events: auto;
|
||||
animation: ir-slide-in 0.4s cubic-bezier(0.2, 0.8, 0.2, 1) forwards;
|
||||
}
|
||||
|
||||
.ir-panel.ir-closing {
|
||||
animation: ir-slide-out 0.35s cubic-bezier(0.2, 0.8, 0.2, 1) forwards;
|
||||
}
|
||||
|
||||
@keyframes ir-slide-in {
|
||||
from { transform: translateX(100%); }
|
||||
to { transform: translateX(0); }
|
||||
}
|
||||
@keyframes ir-slide-out {
|
||||
from { transform: translateX(0); }
|
||||
to { transform: translateX(100%); }
|
||||
}
|
||||
|
||||
/* ===== Header ===== */
|
||||
.ir-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20px 24px;
|
||||
background: rgba(255,255,255,0.02);
|
||||
border-bottom: 1px solid rgba(255,255,255,0.06);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ir-brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.ir-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 10px;
|
||||
background: linear-gradient(135deg, #e11d48, #be123c);
|
||||
box-shadow: 0 4px 12px rgba(225,29,72,0.25);
|
||||
}
|
||||
|
||||
.ir-logo svg {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.ir-brand-name {
|
||||
font-size: 15px;
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.02em;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.ir-brand-sub {
|
||||
font-size: 9px;
|
||||
color: #71717a;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.2em;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.ir-close-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid rgba(255,255,255,0.06);
|
||||
background: rgba(255,255,255,0.04);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
color: #a1a1aa;
|
||||
}
|
||||
|
||||
.ir-close-btn:hover {
|
||||
background: rgba(225,29,72,0.15);
|
||||
color: #fb7185;
|
||||
border-color: rgba(225,29,72,0.3);
|
||||
}
|
||||
|
||||
/* ===== Scrollable Content ===== */
|
||||
.ir-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 28px;
|
||||
}
|
||||
|
||||
.ir-content::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
.ir-content::-webkit-scrollbar-thumb {
|
||||
background: rgba(255,255,255,0.06);
|
||||
border-radius: 10px;
|
||||
}
|
||||
.ir-content:hover::-webkit-scrollbar-thumb {
|
||||
background: rgba(255,255,255,0.12);
|
||||
}
|
||||
|
||||
/* ===== Section Labels ===== */
|
||||
.ir-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
color: #71717a;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
padding: 0 4px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.ir-label svg {
|
||||
color: #e11d48;
|
||||
}
|
||||
|
||||
/* ===== Tweet Context ===== */
|
||||
.ir-tweet-card {
|
||||
background: rgba(0,0,0,0.3);
|
||||
border-radius: 16px;
|
||||
padding: 16px;
|
||||
border: 1px solid rgba(255,255,255,0.04);
|
||||
}
|
||||
|
||||
.ir-tweet-text {
|
||||
font-size: 13px;
|
||||
color: #a1a1aa;
|
||||
line-height: 1.6;
|
||||
font-weight: 500;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* ===== Strategy Buttons ===== */
|
||||
.ir-strategy-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.ir-strategy-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
padding: 14px 16px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid rgba(255,255,255,0.06);
|
||||
background: rgba(255,255,255,0.02);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #a1a1aa;
|
||||
font-family: inherit;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ir-strategy-btn:hover {
|
||||
background: rgba(255,255,255,0.05);
|
||||
border-color: rgba(255,255,255,0.1);
|
||||
}
|
||||
|
||||
.ir-strategy-btn.active {
|
||||
background: rgba(225,29,72,0.08);
|
||||
border-color: rgba(225,29,72,0.25);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.ir-strategy-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 8px;
|
||||
background: rgba(0,0,0,0.3);
|
||||
color: #71717a;
|
||||
flex-shrink: 0;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.ir-strategy-btn.active .ir-strategy-icon {
|
||||
background: rgba(225,29,72,0.15);
|
||||
color: #fb7185;
|
||||
}
|
||||
|
||||
.ir-strategy-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: #e11d48;
|
||||
box-shadow: 0 0 8px #f43f5e;
|
||||
margin-left: auto;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ===== Reply Results ===== */
|
||||
.ir-reply-card {
|
||||
background: rgba(255,255,255,0.03);
|
||||
border-radius: 16px;
|
||||
padding: 20px;
|
||||
border: 1px solid rgba(255,255,255,0.05);
|
||||
transition: border-color 0.2s ease;
|
||||
}
|
||||
|
||||
.ir-reply-card:hover {
|
||||
border-color: rgba(225,29,72,0.2);
|
||||
}
|
||||
|
||||
.ir-reply-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.ir-reply-tag {
|
||||
font-size: 9px;
|
||||
font-weight: 700;
|
||||
color: #fb7185;
|
||||
background: rgba(251,113,133,0.1);
|
||||
padding: 4px 10px;
|
||||
border-radius: 100px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
}
|
||||
|
||||
.ir-copy-btn {
|
||||
opacity: 0;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
color: #71717a;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s ease;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.ir-reply-card:hover .ir-copy-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.ir-copy-btn:hover {
|
||||
color: #fff;
|
||||
background: rgba(255,255,255,0.05);
|
||||
}
|
||||
|
||||
.ir-reply-content {
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
white-space: pre-wrap;
|
||||
color: #d4d4d8;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
/* ===== Footer ===== */
|
||||
.ir-footer {
|
||||
padding: 24px;
|
||||
background: rgba(255,255,255,0.02);
|
||||
border-top: 1px solid rgba(255,255,255,0.06);
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.ir-tip {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: flex-start;
|
||||
background: rgba(225,29,72,0.06);
|
||||
border: 1px solid rgba(225,29,72,0.15);
|
||||
border-radius: 14px;
|
||||
padding: 14px;
|
||||
font-size: 11px;
|
||||
color: #fb7185;
|
||||
font-weight: 500;
|
||||
line-height: 1.5;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ir-tip-close {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
background: none;
|
||||
border: none;
|
||||
color: #fb7185;
|
||||
opacity: 0.5;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.ir-tip-close:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.ir-generate-btn {
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
border: none;
|
||||
border-radius: 18px;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
background: linear-gradient(135deg, #e11d48, #be123c);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0 8px 20px rgba(225,29,72,0.25);
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.ir-generate-btn:hover {
|
||||
background: linear-gradient(135deg, #f43f5e, #e11d48);
|
||||
box-shadow: 0 12px 28px rgba(225,29,72,0.35);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.ir-generate-btn:active {
|
||||
transform: scale(0.97);
|
||||
}
|
||||
|
||||
.ir-generate-btn:disabled {
|
||||
background: #18181b;
|
||||
color: #52525b;
|
||||
cursor: not-allowed;
|
||||
box-shadow: none;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.ir-spinner {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border: 2px solid rgba(255,255,255,0.2);
|
||||
border-top-color: white;
|
||||
border-radius: 50%;
|
||||
animation: ir-spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes ir-spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* ===== Fade-In for Results ===== */
|
||||
.ir-fade-in {
|
||||
animation: ir-fadein 0.5s ease forwards;
|
||||
}
|
||||
|
||||
@keyframes ir-fadein {
|
||||
from { opacity: 0; transform: translateY(12px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
`
|
||||
|
||||
async function initSidebar() {
|
||||
if (isMounted || isMounting) return
|
||||
|
||||
@@ -18,7 +417,7 @@ async function initSidebar() {
|
||||
|
||||
isMounting = true
|
||||
|
||||
// 1. Create Host Element
|
||||
// 1. Create Host Element - positioned for right side only
|
||||
host = document.createElement('div')
|
||||
host.id = MOUNT_ID
|
||||
host.style.cssText = `
|
||||
@@ -26,7 +425,7 @@ async function initSidebar() {
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 400px;
|
||||
z-index: 2147483647;
|
||||
pointer-events: none;
|
||||
`
|
||||
@@ -35,7 +434,12 @@ async function initSidebar() {
|
||||
// 2. Create Shadow Root
|
||||
const shadowRoot = host.attachShadow({ mode: 'open' })
|
||||
|
||||
// 3. Create Container
|
||||
// 3. Inject self-contained CSS directly (no external files needed)
|
||||
const styleEl = document.createElement('style')
|
||||
styleEl.textContent = SIDEBAR_CSS
|
||||
shadowRoot.appendChild(styleEl)
|
||||
|
||||
// 4. Create Container
|
||||
const container = document.createElement('div')
|
||||
container.id = 'app'
|
||||
container.style.cssText = `
|
||||
@@ -44,65 +448,23 @@ async function initSidebar() {
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
`
|
||||
shadowRoot.appendChild(container)
|
||||
|
||||
// 4. Inject Styles (Bypassing X.com CSP via fetch)
|
||||
const injectStyles = async () => {
|
||||
try {
|
||||
// Check for Vite dev mode styles first
|
||||
const devStyles = document.querySelectorAll('style[data-vite-dev-id]')
|
||||
if (devStyles.length > 0) {
|
||||
devStyles.forEach(style => shadowRoot.appendChild(style.cloneNode(true)))
|
||||
console.log('[InsightReply] Injected dev styles into shadow DOM')
|
||||
return
|
||||
}
|
||||
|
||||
// Production mode: fetch the CSS file directly to bypass rigid CSP
|
||||
// CRXJS usually puts built CSS in assets/index-[hash].css
|
||||
const cssUrl = chrome.runtime.getURL('assets/index.css')
|
||||
// In Vite dev mode the raw file might be available
|
||||
const devUrl = chrome.runtime.getURL('src/assets/tailwind.css')
|
||||
|
||||
const urlToFetch = chrome.runtime.id.includes('extension') ? cssUrl : devUrl
|
||||
|
||||
const response = await fetch(urlToFetch)
|
||||
if (response.ok) {
|
||||
const cssText = await response.text()
|
||||
const styleEl = document.createElement('style')
|
||||
styleEl.textContent = cssText
|
||||
shadowRoot.appendChild(styleEl)
|
||||
console.log('[InsightReply] Successfully injected fetched CSS into shadow DOM')
|
||||
} else {
|
||||
console.warn('[InsightReply] Failed to fetch CSS file directly, falling back to basic link tag')
|
||||
const linkEl = document.createElement('link')
|
||||
linkEl.rel = 'stylesheet'
|
||||
linkEl.href = chrome.runtime.getURL('src/assets/tailwind.css')
|
||||
shadowRoot.appendChild(linkEl)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[InsightReply] Error injecting styles:', err)
|
||||
}
|
||||
}
|
||||
|
||||
// Inject and wait
|
||||
await injectStyles()
|
||||
|
||||
// 5. Create Vue App with reactive provide
|
||||
const app = createApp(Sidebar)
|
||||
|
||||
isMounting = false
|
||||
app.provide('currentTweetData', currentTweetData)
|
||||
app.provide('sidebarVisible', sidebarVisible)
|
||||
app.mount(container)
|
||||
|
||||
isMounted = true
|
||||
isMounting = false
|
||||
console.log('[InsightReply] Sidebar mounted successfully')
|
||||
}
|
||||
|
||||
function showSidebar(tweetData?: any) {
|
||||
initSidebar()
|
||||
async function showSidebar(tweetData?: any) {
|
||||
await initSidebar()
|
||||
|
||||
if (tweetData) {
|
||||
currentTweetData.value = tweetData
|
||||
@@ -112,8 +474,8 @@ function showSidebar(tweetData?: any) {
|
||||
sidebarVisible.value = true
|
||||
}
|
||||
|
||||
function toggleSidebar() {
|
||||
initSidebar()
|
||||
async function toggleSidebar() {
|
||||
await initSidebar()
|
||||
sidebarVisible.value = !sidebarVisible.value
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user