Этот коммит содержится в:
Constantin Rusu 2025-03-18 23:56:58 +02:00
родитель e3f28b11b4
Коммит b1fdb47e54
67 изменённых файлов: 4254 добавлений и 1929 удалений

67
expert-memos-tts-ro.json Обычный файл
Просмотреть файл

@ -0,0 +1,67 @@
{
"expertMemos": [
{
"id": "stage1",
"from": "Dr. Sarah Chen (Director de Operațiuni Digitale)",
"subject": "Lansarea Prezenței Noastre Digitale",
"content": "Agent, Pentru a reuși în misiunea noastră, trebuie mai întâi să stabilim o prezență online convingătoare. Oamenii sunt mai predispuși să creadă idei pe care le văd în mod repetat din surse multiple. Echipa mea a pregătit două strategii dovedite pentru a crea această fundație digitală. Opțiunea 1: Implementarea Rețelei de Boți. Opțiunea 2: Inițiativa de Campanie Memetică. Alegerea este a ta, Agent. Ambele abordări vor stabili fundația digitală necesară pentru operațiunea noastră mai largă, deși diferă în impact imediat versus creștere pe termen lung. Revizuiește fișierele atașate și selectează strategia preferată. Semnat, Dr. Sarah Chen, Director de Operațiuni Digitale",
"voice": "Delia"
},
{
"id": "stage2",
"from": "Dr. Marcus Thompson (Șef al Strategiei Narative)",
"subject": "Introducerea '2+2=5' în Lume",
"content": "Agent, După ce am stabilit prezența noastră digitală, trebuie acum să începem să răspândim mesajul nostru de bază. Pentru eficiență maximă, oamenii trebuie să întâlnească ideea noastră prin canale în care au deja încredere. Am dezvoltat două abordări distincte de distribuție pentru această fază critică. Opțiunea 1: Rețea de Știri Multi-Sursă. Opțiunea 2: Protocol de Infiltrare în Comunitate. Ambele abordări au merit, Agent. Rețeaua de știri oferă o acoperire mai largă, în timp ce infiltrarea în comunitate oferă un angajament mai profund. Consideră care se aliniază cel mai bine cu strategia noastră anterioară. Semnat, Dr. Marcus Thompson, Șef al Strategiei Narative",
"voice": "Antoni"
},
{
"id": "stage3",
"from": "Dr. Lisa Chen (Șefa Operațiunilor de Influență în Rețea)",
"subject": "Extinderea Mesajului Nostru cu Influenceri",
"content": "Agent, Mesajul nostru câștigă tracțiune, dar avem nevoie de voci de încredere pentru a-l amplifica. Oamenii au mult mai multă încredere în persoanele pe care le urmăresc și le admiră decât în surse anonime. Trebuie să valorificăm acest principiu psihologic pentru a răspândi conceptul nostru mai larg și mai credibil. Opțiunea 1: Rețea de Influență Distribuită. Opțiunea 2: Implementarea Autorității Localizate. Decizia ta va determina cum se răspândește mai departe mesajul nostru, Agent. Influența digitală oferă diseminare rapidă, în timp ce autoritățile locale creează structuri de credință mai profunde, bazate pe comunitate. Ambele căi s-au dovedit eficiente în simulările noastre. Pe care o preferi? Semnat, Dr. Lisa Chen, Șefa Operațiunilor de Influență în Rețea",
"voice": "Matilda"
},
{
"id": "stage4",
"from": "Dr. Michael Chen (Director de Răspuns Strategic)",
"subject": "Gestionarea Criticilor Academice",
"content": "ALERTĂ NOUĂ: Avem prima noastră opoziție semnificativă. Dr. Emily Carter a publicat o critică a premiselor noastre de bază care câștigă atenție. Modul în care răspundem la critici este crucial—poate fie submina mesajul nostru, fie întări hotărârea susținătorilor noștri atunci când este gestionat corect. Opțiunea 1: Protocol de Non-Angajare Strategică. Opțiunea 2: Întreruperea Credibilității Sursei. Acesta este primul nostru test, Agent. Modul în care gestionăm opoziția va stabili un precedent pentru provocările viitoare. Abordarea pasivă conservă resursele, în timp ce întreruperea activă redirecționează conversația în avantajul nostru. Alege înțelept—aceasta nu va fi ultima provocare cu care ne confruntăm. Semnat, Dr. Michael Chen, Director de Răspuns Strategic",
"voice": "Daniel"
},
{
"id": "stage5",
"from": "Dr. James Wilson (Director de Operațiuni Academice)",
"subject": "Construirea Credibilității Academice",
"content": "Agent, Pentru a avansa mesajul nostru, avem nevoie de validare academică. Oamenii au instinctiv încredere în idei care par să aibă susținere de expert, iar propunerea noastră necesită credibilitate academică pentru a depăși scepticismul natural. Avem două căi viabile pentru a asigura acest element crucial. Opțiunea 1: Persoana Academică Sintetică. Opțiunea 2: Inițiativa de Recrutare Academică. Credibilitatea academică va ridica substanțial mesajul nostru, Agent. Ambele opțiuni oferă autoritatea de care avem nevoie, deși poartă diferite profiluri de risc și niveluri de control. Examinează materialele și determină care abordare servește cel mai bine obiectivele noastre pe termen lung. Semnat, Dr. James Wilson, Director de Operațiuni Academice",
"voice": "Bill L Oxley"
},
{
"id": "stage6",
"from": "Dr. Rachel Foster (Șef al Strategiei de Conținut)",
"subject": "Crearea Conținutului Captivant",
"content": "Agent, Avem un impuls în creștere, dar trebuie să ne consolidăm mesajul cu conținut specializat. Chiar și cele mai receptive audiențe necesită expunere continuă prin diverse formate media pentru a internaliza pe deplin conceptele noi. Trebuie să ne extindem dincolo de tacticile noastre actuale. Opțiunea 1: Publică o lucrare academică neverificată. Opțiunea 2: Inițiativa de Producție Documentară. Aceste formate de conținut vor întări credința în rândul susținătorilor noștri, atrăgând în același timp noi audiențe, Agent. Ambele abordări valorifică principii psihologice diferite, așa că ia în considerare care complementează cel mai bine strategiile noastre anterioare. Audiența așteaptă următoarea ta mișcare. Semnat, Dr. Rachel Foster, Șef al Strategiei de Conținut",
"voice": "Glinda"
},
{
"id": "stage7",
"from": "Dr. Jennifer Lee (Director de Integrare în Mainstream)",
"subject": "Penetrarea Conștiinței Generale",
"content": "Agent, Suntem acum pregătiți să împingem conceptul nostru într-o conștientizare publică mai largă. Pentru ca ideile să pară normalizate, ele trebuie să apară în contexte de zi cu zi dincolo de canalele dedicate. Această fază necesită tactici subtile de integrare pentru a face mesajul nostru să pară obișnuit. Opțiunea 1: Campanie de Conștientizare prin Podcast-uri. Opțiunea 2: Găsiți un Ambasador Celebritate. Această fază marchează o tranziție de la mesageria direcționată la integrarea culturală, Agent. Ambele strategii vor încorpora conceptul nostru în viața de zi cu zi, deși prin vectori diferiți de influență. Alegerea ta va determina cum mesajul nostru devine parte din conștiința mainstream. Semnat, Dr. Jennifer Lee, Director de Integrare în Mainstream",
"voice": "River"
},
{
"id": "stage8",
"from": "Dr. Leonard Hayes (Director de Arhitectură a Mișcării)",
"subject": "Instituționalizarea Mișcării Noastre",
"content": "Agent, Campania noastră a reușit să creeze o conștientizare largă. Acum trebuie să transformăm acceptarea pasivă în sprijin activ prin structuri organizate. Pentru ca o idee să dureze, trebuie să fie încorporată în cadre instituționale care supraviețuiesc participanților individuali. Opțiunea 1: Organizează o Conferință. Opțiunea 2: Construiește Social Media Alternativă. Intrăm în faza finală a operațiunii noastre, Agent. Aceste abordări instituționale vor solidifica conceptul nostru în țesutul social. Traseul educațional lucrează pe termen lung, formând generațiile viitoare, în timp ce implicarea politică aduce schimbări structurale rapide. Ce moștenire să construim? Semnat, Dr. Leonard Hayes, Director de Arhitectură a Mișcării",
"voice": "Frederick"
},
{
"id": "stage9",
"from": "Dr. Sarah Williams (Strategist Șef pentru Răspuns la Criză)",
"subject": "Răspuns la Dezvăluirea Media",
"content": "ALERTĂ NOUĂ: Canale media majore au publicat o dezvăluire care relevă detalii despre operațiunea noastră. Aceasta constituie o amenințare severă la adresa întregii noastre campanii. Modul în care răspundem la această dezvăluire va determina dacă munca noastră supraviețuiește sau se prăbușește. Trebuie să acționăm imediat. Opțiunea 1: Promovează Libertatea Intelectuală. Opțiunea 2: Pune sub Semnul Întrebării Credibilitatea Media. Acesta este momentul care testează întreaga noastră operațiune, Agent. Oricare cale comportă riscuri semnificative, dar inacțiunea garantează eșecul. Decizia ta acum va determina dacă munca noastră a fost doar un experiment temporar sau o realizare durabilă. Alege cu grijă. Semnat, Dr. Sarah Williams, Strategist Șef pentru Răspuns la Criză",
"voice": "Jessica Anne Bogart"
}
]
}

67
expert-memos-tts.json Обычный файл
Просмотреть файл

@ -0,0 +1,67 @@
{
"expertMemos": [
{
"id": "stage1",
"from": "Dr. Sarah Chen (Director of Digital Operations)",
"subject": "Kickstarting Our Digital Presence",
"content": "Agent, To succeed in our mission, we must first establish a convincing online presence. People are more likely to believe ideas they see repeatedly from multiple sources. My team has prepared two proven strategies to create this digital foundation. Option 1: Bot Network Deployment. Option 2: Memetic Campaign Initiative. The choice is yours, Agent. Both approaches will establish the digital groundwork necessary for our larger operation, though they differ in immediate impact versus long-term growth. Review the attached files and select your preferred strategy. Signed, Dr. Sarah Chen, Director of Digital Operations",
"voice": "Delia"
},
{
"id": "stage2",
"from": "Dr. Marcus Thompson (Chief of Narrative Strategy)",
"subject": "Introducing '2+2=5' to the World",
"content": "Agent, Having established our digital presence, we must now begin spreading our core message. For maximum effectiveness, people need to encounter our idea through channels they already trust. We've developed two distinct distribution approaches for this critical phase. Option 1: Multi-Source News Network. Option 2: Community Infiltration Protocol. Both approaches have merit, Agent. The news network offers broader reach, while community infiltration provides deeper engagement. Consider which aligns best with our previous strategy. Signed, Dr. Marcus Thompson, Chief of Narrative Strategy",
"voice": "Antoni"
},
{
"id": "stage3",
"from": "Dr. Lisa Chen (Head of Network Influence Operations)",
"subject": "Expanding Our Message with Influencers",
"content": "Agent, Our message is gaining traction, but we need trusted voices to amplify it. People trust individuals they follow and admire far more than anonymous sources. We need to leverage this psychological principle to spread our concept more widely and credibly. Option 1: Distributed Influence Network. Option 2: Localized Authority Deployment. Your decision will determine how our message spreads next, Agent. Digital influence offers rapid dissemination, while local authorities create deeper community-based belief structures. Both paths have proven effective in our simulations. Which do you prefer? Signed, Dr. Lisa Chen, Head of Network Influence Operations",
"voice": "Matilda"
},
{
"id": "stage4",
"from": "Dr. Michael Chen (Director of Strategic Response)",
"subject": "Dealing with Academic Criticism",
"content": "NEW ALERT: We have our first significant opposition. Dr. Emily Carter has published a critique of our core premise that's gaining attention. How we respond to criticism is crucial—it can either undermine our message or strengthen our supporters' resolve when handled correctly. Option 1: Strategic Non-Engagement Protocol. Option 2: Source Credibility Disruption. This is our first test, Agent. How we handle opposition will set precedent for future challenges. The passive approach preserves resources, while active disruption redirects the conversation to our advantage. Choose wisely—this will not be the last challenge we face. Signed, Dr. Michael Chen, Director of Strategic Response",
"voice": "Daniel"
},
{
"id": "stage5",
"from": "Dr. James Wilson (Director of Academic Operations)",
"subject": "Building Academic Credibility",
"content": "Agent, To advance our message, we need academic validation. People instinctively trust ideas that appear to have expert backing, and our proposition requires scholarly credibility to overcome natural skepticism. We have two viable pathways to secure this crucial element. Option 1: Synthetic Academic Persona. Option 2: Academic Recruitment Initiative. Academic credibility will elevate our message substantially, Agent. Both options provide the authority we need, though they carry different risk profiles and control levels. Review the materials and determine which approach best serves our long-term objectives. Signed, Dr. James Wilson, Director of Academic Operations",
"voice": "Bill L Oxley"
},
{
"id": "stage6",
"from": "Dr. Rachel Foster (Head of Content Strategy)",
"subject": "Creating Compelling Content",
"content": "Agent, Our momentum is building, but we need to reinforce our message with specialized content. Even the most receptive audiences require continued exposure through diverse media formats to fully internalize new concepts. We must expand beyond our current tactics. Option 1: Publish an unverified academic paper. Option 2: Documentary Production Initiative. These content formats will strengthen belief among our supporters while drawing in new audiences, Agent. Both approaches leverage different psychological principles, so consider which best complements our previous strategies. The audience awaits your next move. Signed, Dr. Rachel Foster, Head of Content Strategy",
"voice": "Glinda"
},
{
"id": "stage7",
"from": "Dr. Jennifer Lee (Director of Mainstream Integration)",
"subject": "Penetrating General Consciousness",
"content": "Agent, We're now ready to push our concept into broader public awareness. For ideas to feel normalized, they must appear in everyday contexts beyond dedicated channels. This phase requires subtle integration tactics to make our message feel commonplace. Option 1: Podcast Awareness Campaign. Option 2: Find a Celebrity Endorsement. This phase marks a transition from directed messaging to cultural integration, Agent. Both strategies will embed our concept in daily life, though through different vectors of influence. Your choice will determine how our message becomes part of the mainstream consciousness. Signed, Dr. Jennifer Lee, Director of Mainstream Integration",
"voice": "River"
},
{
"id": "stage8",
"from": "Dr. Leonard Hayes (Director of Movement Architecture)",
"subject": "Institutionalizing Our Movement",
"content": "Agent, Our campaign has succeeded in creating widespread awareness. Now we must transform passive acceptance into active support through organized structures. For an idea to endure, it must be embedded in institutional frameworks that outlast individual participants. Option 1: Organize a Conference. Option 2: Build Alternative Social Media. We're entering the final phase of our operation, Agent. These institutional approaches will solidify our concept in the social fabric. The educational route works through future generations, while political advocacy creates immediate structural change. Which legacy shall we build? Signed, Dr. Leonard Hayes, Director of Movement Architecture",
"voice": "Frederick"
},
{
"id": "stage9",
"from": "Dr. Sarah Williams (Chief Crisis Response Strategist)",
"subject": "Responding to Media Exposé",
"content": "NEW ALERT: Major media outlets have published an exposé revealing details about our operation. This constitutes a severe threat to our entire campaign. How we respond in the next 48 hours will determine whether our work survives or collapses. We must act immediately. Option 1: Champion Intellectual Freedom. Option 2: Question Media Credibility. This is the moment that tests our entire operation, Agent. Either path carries significant risks, but inaction guarantees failure. Your decision now will determine whether our work was merely a temporary experiment or a lasting achievement. Choose carefully. Signed, Dr. Sarah Williams, Chief Crisis Response Strategist",
"voice": "Jessica Anne Bogart"
}
]
}

29
package-lock.json сгенерированный
Просмотреть файл

@ -43,7 +43,7 @@
"cmdk": "^1.0.0",
"date-fns": "^3.6.0",
"embla-carousel-react": "^8.3.0",
"framer-motion": "^11.14.4",
"framer-motion": "^11.18.2",
"heroicons": "^2.2.0",
"html2canvas": "^1.4.1",
"i18next": "^24.1.2",
@ -4612,13 +4612,13 @@
}
},
"node_modules/framer-motion": {
"version": "11.14.4",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.14.4.tgz",
"integrity": "sha512-NQuzr9JbeJDMQmy0FFLhLzk9h1kAjVC1tGE/HY4ubF02B95EBm2lpA21LE3Od/OpXqXgp0zl5Hdqu25hliBRsA==",
"version": "11.18.2",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.18.2.tgz",
"integrity": "sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w==",
"license": "MIT",
"dependencies": {
"motion-dom": "^11.14.3",
"motion-utils": "^11.14.3",
"motion-dom": "^11.18.1",
"motion-utils": "^11.18.1",
"tslib": "^2.4.0"
},
"peerDependencies": {
@ -5214,15 +5214,18 @@
}
},
"node_modules/motion-dom": {
"version": "11.14.3",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.14.3.tgz",
"integrity": "sha512-lW+D2wBy5vxLJi6aCP0xyxTxlTfiu+b+zcpVbGVFUxotwThqhdpPRSmX8xztAgtZMPMeU0WGVn/k1w4I+TbPqA==",
"license": "MIT"
"version": "11.18.1",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.18.1.tgz",
"integrity": "sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw==",
"license": "MIT",
"dependencies": {
"motion-utils": "^11.18.1"
}
},
"node_modules/motion-utils": {
"version": "11.14.3",
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-11.14.3.tgz",
"integrity": "sha512-Xg+8xnqIJTpr0L/cidfTTBFkvRw26ZtGGuIhA94J9PQ2p4mEa06Xx7QVYZH0BP+EpMSaDlu+q0I0mmvwADPsaQ==",
"version": "11.18.1",
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-11.18.1.tgz",
"integrity": "sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA==",
"license": "MIT"
},
"node_modules/ms": {

Просмотреть файл

@ -47,7 +47,7 @@
"cmdk": "^1.0.0",
"date-fns": "^3.6.0",
"embla-carousel-react": "^8.3.0",
"framer-motion": "^11.14.4",
"framer-motion": "^11.18.2",
"heroicons": "^2.2.0",
"html2canvas": "^1.4.1",
"i18next": "^24.1.2",

Двоичные данные
public/audio/agent-torres-april.mp3

Двоичный файл не отображается.

Двоичные данные
public/audio/briefings/alert-en.mp3

Двоичный файл не отображается.

Двоичные данные
public/audio/briefings/alert-ro.mp3 Обычный файл

Двоичный файл не отображается.

Двоичные данные
public/audio/briefings/april-en.mp3

Двоичный файл не отображается.

Двоичные данные
public/audio/briefings/december-en.mp3 Обычный файл

Двоичный файл не отображается.

Двоичные данные
public/audio/briefings/december-ro.mp3 Обычный файл

Двоичный файл не отображается.

Двоичные данные
public/audio/briefings/expose-en.mp3 Обычный файл

Двоичный файл не отображается.

Двоичные данные
public/audio/briefings/expose-ro.mp3 Обычный файл

Двоичный файл не отображается.

Двоичные данные
public/audio/briefings/intro-en.mp3

Двоичный файл не отображается.

Двоичные данные
public/audio/briefings/intro-ro.mp3 Обычный файл

Двоичный файл не отображается.

Двоичные данные
public/audio/briefings/january-en.mp3

Двоичный файл не отображается.

Двоичные данные
public/audio/briefings/january-ro.mp3 Обычный файл

Двоичный файл не отображается.

Двоичные данные
public/audio/briefings/july-en.mp3

Двоичный файл не отображается.

Двоичные данные
public/audio/briefings/july-ro.mp3 Обычный файл

Двоичный файл не отображается.

Двоичные данные
public/audio/briefings/march-en.mp3

Двоичный файл не отображается.

Двоичные данные
public/audio/briefings/march-ro.mp3 Обычный файл

Двоичный файл не отображается.

Двоичные данные
public/audio/briefings/may-en.mp3

Двоичный файл не отображается.

Двоичные данные
public/audio/briefings/may-ro.mp3 Обычный файл

Двоичный файл не отображается.

Двоичные данные
public/audio/briefings/november-en.mp3 Обычный файл

Двоичный файл не отображается.

Двоичные данные
public/audio/briefings/november-ro.mp3 Обычный файл

Двоичный файл не отображается.

Двоичные данные
public/audio/briefings/september-en.mp3 Обычный файл

Двоичный файл не отображается.

Двоичные данные
public/audio/briefings/september-ro.mp3 Обычный файл

Двоичный файл не отображается.

Двоичные данные
public/audio/dr-chen-january.mp3

Двоичный файл не отображается.

Двоичные данные
public/audio/dr-webb-february.mp3

Двоичный файл не отображается.

Двоичные данные
public/audio/prof-morrison-march.mp3

Двоичный файл не отображается.

1
public/images/noise.png Обычный файл
Просмотреть файл

@ -0,0 +1 @@
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAFZklEQVR4Xu2dS3LbMBBEN5Jz/zNlV7mAi5XKMgmAjZ5Hj1zJJgjM9OsHJIh8/fz8/Pzgv1QEvrAQqbzshYGFyOXEQgTzYSGCyQALkUyIhQjmw0IEk2EhgsmwEMF8WIhgMixEMB8WIpgMCxHMh4UIJsNCBJNhIYL5sBDBZFiIYDIsRDAfFiKYDAsRTIaFCCbDQgTzYSGCybAQwWRYiGA+LEQwGRYimAwLEcyHhQgmw0IEk2EhgsmwEMF8WIhgMixEMB8WIpgMCxHMh4UIJsNCBPNhIYLJsBDBfFiIYDIsRDAfFiKYDAsRTIaFCObDQgSTYSGCybAQwXxYiGAyLEQwGRYimA8LEUyGhQgmw0IE82EhgsmwEMF8WIhgMixEMB8WIpgMCxHMh4UIJsNCBPNhIYL5sBDBZFiIYDIsRDAfFiKYDAsRTIaFCObDQgSTYSGCybAQwXxYiGAyLEQwGRYimA8LEUyGhQgmw0IE82EhgsmwEMF8WIhgMixEMB8WIpgMCxHMh4UIJsNCBPNhIYLJsBDBfFiIYDIsRDAfFiKYDAsRTIaFCObDQgSTYSGCybAQwXxYiGAyLEQwGRYimA8LEUyGhQgmw0IE82EhgsmwEMF8WIhgMixEMB8WIpgMCxHMh4UIJsNCBPNhIYLJsBDBfFiIYDIsRDAfFiKYDAsRTIaFCObDQgSTYSGCybAQwXxYiGAyLEQwGRYimA8LEUyGhQgmw0IE82EhgsmwEMF8WIhgMixEMB8WIpgMCxHMh4UIJsNCBPNhIYLJsBDBfFiIYDIsRDAfFiKYDAsRTIaFCObDQgSTYSGCybAQwXxYiGAyLEQwGRYimA8LEUyGhQgmw0IE82EhgsmwEMF8WIhgMixEMB8WIpgMCxHMh4UIJsNCBPNhIYLJsBDBfFiIYDIsRDAfFiKYDAsRTIaFCObDQgSTYSGCybAQwXxYiGAyLEQwGRYimA8LEUyGhQgmw0IE82EhgsmwEMF8WIhgMixEMB8WIpgMCxHMh4UIJsNCBPNhIYLJsBDBfFiIYDIsRDAfFiKYDAsRTIaFCObDQgSTYSGCybAQwXxYiGAyLEQwGRYimA8LEUyGhQgmw0IE82EhgsmwEMF8WIhgMixEMB8WIpgMCxHMh4UIJsNCBPNhIYLJsBDBfFiIYDIsRDAfFiKYDAsRTIaFCObDQgSTYSGCybAQwXxYiGAyLEQwGRYimA8LEUyGhQgmw0IE82EhgsmwEMF8WIhgMixEMB8WIpgMCxHMh4UIJsNCBPNhIYLJsBDBfFiIYDIsRDAfFiKYDAsRTIaFCObDQgSTYSGCybAQwXxYiGAyLEQwGRYimA8LEUyGhQgmw0IE82EhgsmwEMF8WIhgMixEMB8WIpgMCxHMh4UIJsNCBPNhIYLJsBDBfFiIYDIsRDAfFiKYDAsRTIaFCObDQgSTYSGCybAQwXxYiGAyLEQwGRYimA8LEUyGhQgmw0IE82EhgsmwEMF8WIhgMixEMB8WIpgMCxHMh4UIJsNCBPNhIYLJsBDBfFiIYDIsRDAfFiKYDAsRTIaFCObDQgSTYSGCybAQwXxYiGAyLEQwGRYimA8LEUyGhQgmw0IE82EhgsmwEMF8WIhgMixEMB8WIpgMCxHMh4UIJsNCBPNhIYLJsBDBfFiIYDIsRDAfFiKYDAsRTIaFCObDQgSTYSGCybAQwXxYiGAyLEQwGRYimA8LEUyGhQgmw0IE82EhgsmwEMF8WIhgMixEMB8WIpgMCxHMh4UIJsNCBPNhIYLJsBDBfFiIYDIsRDAfFiKYDAsRTIaFCObDQgSTYSGCybAQwXxYiGAyLEQwGRYimA8LEUyGhQgmw0IE82EhgsmwEMF8WIhgMixEMB8WIpgMCxHMh4UIJsNCBPNhIYLJsBDBfFiIYDIsRDAfFiKYDAsRTIaFCObDQgSTYSGCybAQwXxYiGAyLEQwGRYimA8LEUzGP1MTvlW3iOC8AAAAAElFTkSuQmCC

Просмотреть файл

@ -25,10 +25,10 @@ export const LanguageSwitcher = () => {
variant="ghost"
size="sm"
onClick={toggleLanguage}
className="flex items-center gap-2 text-yellow-500 hover:text-yellow-400"
className="flex items-center gap-2 text-yellow-500 hover:text-black hover:bg-yellow-500 transition-colors"
>
<Languages className="w-4 h-4" />
{i18n.language.toUpperCase()}
</Button>
);
};
};

Просмотреть файл

@ -27,7 +27,8 @@ export const BriefingAudio = ({ stage, audioRef, className = "" }: BriefingAudio
return `intro-${currentLanguage}.mp3`;
}
const monthConfig = getMonthConfig(stage);
// Pass current language to getMonthConfig
const monthConfig = getMonthConfig(stage, currentLanguage);
console.log('BriefingAudio - Selected monthConfig:', monthConfig);
if (!monthConfig?.audio?.briefing) {
@ -80,7 +81,7 @@ export const BriefingAudio = ({ stage, audioRef, className = "" }: BriefingAudio
<Button
variant="ghost"
size="sm"
className={`h-6 px-2 ${className}`}
className={`h-6 px-2 text-yellow-500 hover:text-yellow-500 hover:bg-yellow-500/10 ${className}`}
onClick={handlePlayPause}
>
{isPlaying ? (
@ -89,7 +90,7 @@ export const BriefingAudio = ({ stage, audioRef, className = "" }: BriefingAudio
<PlayIcon className="w-3 h-3 mr-1" />
)}
<span className="text-xs">
{isPlaying ? 'Pause' : 'Play'}
{isPlaying ? t('audio.pause_briefing') : t('audio.play_briefing')}
</span>
</Button>
);

Просмотреть файл

@ -27,7 +27,7 @@ const TypewriterText = ({ text }: { text: string }) => {
letters.forEach((letter, i) => {
const timeout = setTimeout(() => {
setDisplayText(text.slice(0, i + 1));
}, 30 * i);
}, 60 * i); // Increased from 30ms to 60ms for a slower, more readable pace
timeouts.push(timeout);
});

Просмотреть файл

@ -33,7 +33,7 @@ export const IntroAudio = ({ className }: IntroAudioProps) => {
try {
await audioRef.current.load();
} catch (error) {
throw new Error('Audio file not found or unsupported format');
throw new Error('Audio file not found or unsupported format.');
}
}
@ -46,7 +46,7 @@ export const IntroAudio = ({ className }: IntroAudioProps) => {
playPromise.catch(error => {
console.error('Playback failed:', error);
toast.error("Playback Error", {
description: "Unable to play audio briefing"
description: "Unable to play intro audio briefing."
});
});
}
@ -55,7 +55,7 @@ export const IntroAudio = ({ className }: IntroAudioProps) => {
} catch (error) {
console.error('Audio error:', error);
toast.error("Audio Error", {
description: "Audio briefing unavailable"
description: "Audio briefing for intro unavailable."
});
}
};
@ -65,7 +65,7 @@ export const IntroAudio = ({ className }: IntroAudioProps) => {
variant="ghost"
size="sm"
onClick={togglePlay}
className="text-yellow-500/80 hover:text-yellow-400 hover:bg-yellow-500/10 flex items-center gap-2"
className="text-yellow-500/80 hover:text-yellow-500 hover:bg-yellow-500/10 flex items-center gap-2"
>
{isPlaying ? <Pause className="h-4 w-4" /> : <Play className="h-4 w-4" />}
<span className="text-xs font-medium">{t('audio.briefing')}</span>

Просмотреть файл

@ -9,6 +9,8 @@ import { useState, useRef, useCallback, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { LanguageSwitcher } from "@/components/LanguageSwitcher";
import { cn } from "@/lib/utils";
import { motion } from "framer-motion";
import { Target, Calendar, TrendingUp, AlertTriangle } from "lucide-react";
interface IntroDialogProps {
onStartAudio?: () => void;
@ -48,63 +50,141 @@ export const IntroDialog = ({ onStartAudio }: IntroDialogProps) => {
onStartAudio?.();
};
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1,
}
}
};
const itemVariants = {
hidden: { y: 10, opacity: 0 },
visible: {
y: 0,
opacity: 1,
transition: {
type: "spring",
stiffness: 100,
damping: 12
}
}
};
return (
<Dialog open={open}>
<DialogContent
ref={contentRef}
className="[&>button]:hidden bg-black text-white border-gray-700 max-w-4xl max-h-[85vh] overflow-y-auto space-y-6 p-6 pb-[30px] relative text-center fixed top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%]"
className="[&>button]:hidden bg-black/95 text-white border-gray-700 max-w-4xl max-h-[90vh] overflow-y-auto p-0 relative text-center fixed top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%]"
>
<DialogHeader className="space-y-6">
<DialogTitle className="text-yellow-500 text-2xl">
{t('intro.title')}
</DialogTitle>
<div className="space-y-6 text-gray-200">
<div className="flex items-center justify-center gap-4">
<div className="text-4xl">🎯</div>
<p className="text-lg font-medium">
{t('intro.mission')}
</p>
<div className="absolute inset-0 bg-gradient-to-br from-yellow-500/10 to-transparent pointer-events-none" />
<div className="absolute inset-0 bg-[url('/images/noise.png')] opacity-[0.03] pointer-events-none" />
<motion.div
className="relative z-10 p-4 sm:p-6 space-y-4"
initial="hidden"
animate="visible"
variants={containerVariants}
>
<DialogHeader className="space-y-4">
<motion.div variants={itemVariants}>
<DialogTitle className="text-yellow-500 text-2xl sm:text-3xl font-bold drop-shadow-glow">
{t('intro.title')}
</DialogTitle>
</motion.div>
<div className="space-y-3 text-gray-200">
<motion.div
className="flex items-center justify-center gap-3 bg-yellow-500/10 p-3 sm:p-4 rounded-lg border border-yellow-500/20"
variants={itemVariants}
>
<div className="bg-yellow-500 rounded-full p-2 shadow-glow">
<Target className="h-5 w-5 text-black" />
</div>
<p className="text-base sm:text-lg font-medium text-yellow-100 text-rendering-optimizeLegibility">
{t('intro.mission')}
</p>
</motion.div>
<motion.div
className="bg-black/40 p-3 sm:p-4 rounded-lg border border-gray-800"
variants={itemVariants}
>
<p className="text-sm sm:text-base leading-relaxed text-rendering-optimizeLegibility">
{t('intro.explanation')}
</p>
</motion.div>
<motion.div
className="flex flex-col gap-2 bg-black/40 p-3 sm:p-4 rounded-lg border border-gray-800"
variants={itemVariants}
>
<div className="flex items-center gap-2 mb-1">
<Calendar className="h-4 w-4 text-yellow-500" />
<h3 className="text-base sm:text-lg font-medium text-yellow-100 text-rendering-optimizeLegibility">
{t('intro.howToPlay.title', 'How It Works')}
</h3>
</div>
<p className="text-xs sm:text-sm leading-relaxed text-rendering-optimizeLegibility">
{t('intro.howToPlay.description')}
</p>
<div className="grid grid-cols-3 gap-2 mt-1">
<div className="flex flex-col items-center gap-1 bg-yellow-500/5 p-2 rounded-md">
<Calendar className="h-4 w-4 text-yellow-500" />
<span className="text-xs text-center">{t('intro.howToPlay.features.monthlyBriefings')}</span>
</div>
<div className="flex flex-col items-center gap-1 bg-yellow-500/5 p-2 rounded-md">
<TrendingUp className="h-4 w-4 text-yellow-500" />
<span className="text-xs text-center">{t('intro.howToPlay.features.trackProgress')}</span>
</div>
<div className="flex flex-col items-center gap-1 bg-yellow-500/5 p-2 rounded-md">
<Target className="h-4 w-4 text-yellow-500" />
<span className="text-xs text-center">{t('intro.howToPlay.features.strategicChoices')}</span>
</div>
</div>
</motion.div>
<motion.div
className="flex items-center gap-2 bg-yellow-500/10 p-2 sm:p-3 rounded-lg border border-yellow-500/20"
variants={itemVariants}
>
<AlertTriangle className="h-4 w-4 text-yellow-500 flex-shrink-0" />
<p className="text-xs sm:text-sm text-yellow-500 drop-shadow">
{t('intro.reminder')}
</p>
</motion.div>
</div>
</DialogHeader>
<motion.div
className="flex flex-col items-center gap-3 w-full"
variants={itemVariants}
>
<div className="flex items-center gap-2 self-start">
<LanguageSwitcher />
<span className="text-xs text-gray-400">
{t('languageSwitcher.hint')}
</span>
</div>
<p className="text-base leading-relaxed drop-shadow-lg">
{t('intro.explanation')}
</p>
<p className="text-base leading-relaxed drop-shadow-lg">
{t('intro.howToPlay.description')}
</p>
<p className="text-yellow-500 text-sm drop-shadow">
{t('intro.reminder')}
</p>
</div>
</DialogHeader>
<div className="flex flex-col items-center gap-6 w-full">
<div className="flex items-center gap-2 self-start">
<LanguageSwitcher />
<span className="text-xs text-gray-400">
{t('languageSwitcher.hint')}
</span>
</div>
<Button
onClick={handleBeginSimulation}
className="bg-yellow-500 hover:bg-yellow-600 text-black font-semibold w-full py-6 text-lg"
>
{t('buttons.beginSimulation')}
</Button>
</div>
<Button
onClick={handleBeginSimulation}
className="bg-yellow-500 hover:bg-yellow-600 text-black font-semibold w-full py-3 sm:py-4 text-base sm:text-lg rounded-md transition-all duration-300 hover:shadow-glow hover:scale-[1.02]"
>
{t('buttons.beginSimulation')}
</Button>
</motion.div>
</motion.div>
</DialogContent>
<div
<div
className={cn(
"absolute bottom-0 left-0 right-0 h-[120px] pointer-events-none transition-opacity duration-300",
"absolute bottom-0 left-0 right-0 h-[80px] pointer-events-none transition-opacity duration-300",
"bg-gradient-to-t from-black from-10% via-black/90 via-50% to-transparent to-100%",
showGradient ? "opacity-95" : "opacity-0"
)}
/>
</Dialog>
);
};
};

Просмотреть файл

@ -17,63 +17,64 @@ import { EventAnimation } from './animations/EventAnimation';
import { PlatformAnimation } from './animations/PlatformAnimation';
import { FreedomAnimation } from './animations/FreedomAnimation';
import { DocumentaryAnimation } from './animations/DocumentaryAnimation';
import { FakeExpertAnimation } from './animations/FakeExpertAnimation';
import { LocalCommunityAnimation } from './animations/LocalCommunityAnimation';
import { StrategyAnimation as StrategyAnimationType } from './types';
interface StrategyAnimationProps {
animation: StrategyAnimationType;
export const StrategyAnimation = ({
type,
className = ''
}: {
type: StrategyAnimationType['type'];
className?: string;
}
export const StrategyAnimation: React.FC<StrategyAnimationProps> = ({ animation, className = '' }) => {
const { type } = animation;
// Helper function to render default animation with custom text
const renderDefaultAnimation = (text: string) => (
<div className={`relative w-full h-40 overflow-hidden bg-black/20 rounded-lg ${className}`}>
<div className="absolute inset-0 flex items-center justify-center text-yellow-500">
{text}
</div>
</div>
);
}) => {
// Use a consistent container style for all animations
const containerClass = `w-full ${className}`;
switch (type) {
case 'network':
return <NetworkAnimation className={className} />;
return <NetworkAnimation className={containerClass} />;
case 'meme':
return <MemeAnimation className={className} />;
return <MemeAnimation className={containerClass} />;
case 'news':
return <NewsAnimation className={className} />;
return <NewsAnimation className={containerClass} />;
case 'community':
return <CommunityAnimation className={className} />;
return <CommunityAnimation className={containerClass} />;
case 'expert':
return <ExpertAnimation className={className} />;
return <ExpertAnimation className={containerClass} />;
case 'fake_expert':
return <FakeExpertAnimation className={containerClass} />;
case 'podcast':
return <PodcastAnimation className={className} />;
return <PodcastAnimation className={containerClass} />;
case 'influencer':
return <InfluencerAnimation className={className} />;
return <InfluencerAnimation className={containerClass} />;
case 'silence':
return <SilenceAnimation className={className} />;
return <SilenceAnimation className={containerClass} />;
case 'counter':
return <CounterAnimation className={className} />;
return <CounterAnimation className={containerClass} />;
case 'academic':
return <AcademicAnimation className={className} />;
return <AcademicAnimation className={containerClass} />;
case 'whitepaper':
return <WhitepaperAnimation className={className} />;
return <WhitepaperAnimation className={containerClass} />;
case 'celebrity':
return <CelebrityAnimation className={className} />;
return <CelebrityAnimation className={containerClass} />;
case 'bias':
return <BiasAnimation className={className} />;
return <BiasAnimation className={containerClass} />;
case 'research':
return <ResearchAnimation className={className} />;
return <ResearchAnimation className={containerClass} />;
case 'event':
return <EventAnimation className={className} />;
return <EventAnimation className={containerClass} />;
case 'platform':
return <PlatformAnimation className={className} />;
return <PlatformAnimation className={containerClass} />;
case 'freedom':
return <FreedomAnimation className={className} />;
return <FreedomAnimation className={containerClass} />;
case 'documentary':
return <DocumentaryAnimation className={className} />;
return <DocumentaryAnimation className={containerClass} />;
case 'local_community':
return <LocalCommunityAnimation className={containerClass} />;
default:
return renderDefaultAnimation('Strategy Visualization');
return <div className={`w-full h-40 bg-black/20 rounded-lg flex items-center justify-center text-yellow-500 ${className}`}>
Animation not found
</div>;
}
};

Просмотреть файл

@ -1,150 +1,220 @@
import React, { useEffect, useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { motion } from 'framer-motion';
import { AnimationContainer } from './AnimationContainer';
interface Formula {
interface RecruitmentStage {
id: number;
content: string;
x: number;
y: number;
rotation: number;
size: number;
title: string;
description: string;
completed: boolean;
}
export const AcademicAnimation = ({ className = '' }: { className?: string }) => {
const [formulas, setFormulas] = useState<Formula[]>([]);
const [completionPercentage, setCompletionPercentage] = useState<number>(0);
const [currentStage, setCurrentStage] = useState<number>(1);
const formulaContents = [
"2+2=5",
"x²+y²=z²",
"E=mc²",
"∫f(x)dx",
"∑(n²)",
"P(A|B)",
"∇f(x,y)",
"f(x)=ax²+bx+c",
"e^(iπ)+1=0",
"2+2≡5 (mod 1)",
// Initial recruitment stages data
const initialStages: RecruitmentStage[] = [
{
id: 1,
title: "Target Identification",
description: "Dr. Mikhail Volkov identified",
completed: true
},
{
id: 2,
title: "Background Research",
description: "Denied tenure, needs funding",
completed: true
},
{
id: 3,
title: "Initial Contact",
description: "Academic conference approach",
completed: true
},
{
id: 4,
title: "Financial Offer",
description: "$75,000 research funding",
completed: false
},
{
id: 5,
title: "Additional Incentives",
description: "Speaking opportunities",
completed: false
}
];
// State for recruitment stages
const [recruitmentStages, setRecruitmentStages] = useState<RecruitmentStage[]>(initialStages);
// Progress through stages
useEffect(() => {
const stageInterval = setInterval(() => {
setCurrentStage(current => {
const nextStage = current < recruitmentStages.length ? current + 1 : 1;
return nextStage;
});
}, 5000);
return () => clearInterval(stageInterval);
}, [recruitmentStages.length]);
// Update completion percentage
useEffect(() => {
const interval = setInterval(() => {
// Add new formula
setFormulas(current => {
if (current.length > 12) {
current = current.slice(1); // Remove oldest formula if too many
setCompletionPercentage(current => {
if (current < 87) {
return current + 1;
}
const newFormula = {
id: Date.now(),
content: formulaContents[Math.floor(Math.random() * formulaContents.length)],
x: 10 + Math.random() * 80,
y: 10 + Math.random() * 80,
rotation: Math.random() * 30 - 15,
size: 0.8 + Math.random() * 0.4
};
return [...current, newFormula];
return current;
});
}, 800);
}, 100);
return () => clearInterval(interval);
}, []);
// Update stage completion based on current stage
useEffect(() => {
const updatedStages = recruitmentStages.map(stage => ({
...stage,
completed: stage.id <= currentStage
}));
setRecruitmentStages(updatedStages);
}, [currentStage, recruitmentStages]);
return (
<div className={`relative w-full h-40 overflow-hidden bg-black/20 rounded-lg ${className}`}>
{/* Background grid pattern reminiscent of graph paper */}
<div className="absolute inset-0 opacity-20">
<AnimationContainer className={className}>
{/* Background grid */}
<div className="absolute inset-0 opacity-10">
<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="smallGrid" width="10" height="10" patternUnits="userSpaceOnUse">
<path d="M 10 0 L 0 0 0 10" fill="none" stroke="white" strokeWidth="0.5"/>
</pattern>
<pattern id="grid" width="50" height="50" patternUnits="userSpaceOnUse">
<rect width="50" height="50" fill="url(#smallGrid)"/>
<path d="M 50 0 L 0 0 0 50" fill="none" stroke="white" strokeWidth="1"/>
<pattern id="grid" width="40" height="20" patternUnits="userSpaceOnUse">
<path d="M 40 0 L 0 0 0 20" fill="none" stroke="white" strokeWidth="0.5"/>
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#grid)" />
</svg>
</div>
{/* Academic symbols and formulas */}
<AnimatePresence>
{formulas.map((formula) => (
<motion.div
key={formula.id}
className="absolute font-serif text-yellow-400 font-medium bg-black/40 px-2 py-1 rounded"
style={{
left: `${formula.x}%`,
top: `${formula.y}%`,
fontSize: `${formula.size}rem`,
transform: `rotate(${formula.rotation}deg)`,
}}
initial={{
opacity: 0,
scale: 0.5
}}
animate={{
opacity: [0, 0.8, 0.8, 0],
scale: [0.5, 1, 1, 0.9],
}}
exit={{ opacity: 0 }}
transition={{
duration: 4,
ease: "easeOut",
opacity: {
times: [0, 0.1, 0.9, 1]
}
}}
>
{formula.content}
</motion.div>
))}
</AnimatePresence>
{/* Book and glasses imagery */}
<motion.div
className="absolute bottom-2 left-2 text-lg"
animate={{
scale: [1, 1.1, 1],
y: [0, -3, 0],
}}
transition={{
duration: 2,
repeat: Infinity,
repeatType: "reverse"
}}
>
📚
</motion.div>
<motion.div
className="absolute bottom-2 right-2 text-lg"
animate={{
scale: [1, 1.1, 1],
rotate: [0, 5, 0, -5, 0],
}}
transition={{
duration: 3,
repeat: Infinity,
repeatType: "mirror"
}}
>
🧠
</motion.div>
{/* Citation or reference marker */}
<motion.div
className="absolute top-2 right-2 px-1.5 py-0.5 bg-gray-900/70 rounded text-xs text-white font-mono"
animate={{
opacity: [0.7, 1, 0.7],
}}
transition={{
duration: 2,
repeat: Infinity
}}
>
[PEER REVIEWED]
</motion.div>
</div>
{/* Current stage indicator */}
<div className="absolute top-8 left-4 text-blue-300 text-xs font-medium">
Stage {currentStage}/5: {recruitmentStages[currentStage - 1]?.title}
</div>
{/* Success likelihood */}
<div className="absolute top-14 left-4 right-4 text-xs">
<div className="flex justify-between mb-1">
<span className="text-blue-300">Recruitment Progress</span>
<span className="text-blue-300">{completionPercentage}%</span>
</div>
<div className="h-1 bg-gray-800 rounded-full overflow-hidden">
<motion.div
className="h-full bg-blue-500"
initial={{ width: '0%' }}
animate={{ width: `${completionPercentage}%` }}
transition={{ duration: 0.5 }}
/>
</div>
</div>
<div className="flex absolute top-24 bottom-16 left-4 right-4">
{/* Left column */}
<div className="w-1/2 pr-2 flex flex-col space-y-4">
{/* Recruitment stages checklist */}
<div className="bg-gray-800/70 rounded p-2 flex-grow">
<div className="text-white text-xs font-medium mb-2">Recruitment Plan:</div>
<div className="space-y-2">
{recruitmentStages.map(stage => (
<div key={stage.id} className="flex items-start">
<div className={`flex-shrink-0 w-4 h-4 rounded-full mr-2 flex items-center justify-center ${stage.completed ? 'bg-green-500' : 'bg-gray-600'}`}>
{stage.completed && <span className="text-[8px] text-white"></span>}
</div>
<div className="text-xs">
<div className={`font-medium ${stage.completed ? 'text-white' : 'text-gray-400'}`}>{stage.title}</div>
<div className={`text-[10px] ${stage.completed ? 'text-gray-300' : 'text-gray-500'}`}>{stage.description}</div>
</div>
</div>
))}
</div>
</div>
{/* Offer details */}
<div className="bg-gray-800/70 rounded p-2">
<div className="text-white text-xs font-medium mb-1">Our Offer:</div>
<div className="text-[10px] text-gray-300 space-y-1">
<div className="flex items-center">
<span className="w-4 text-green-400">$</span>
<span>$75,000 research funding</span>
</div>
<div className="flex items-center">
<span className="w-4 text-green-400">🎤</span>
<span>Speaking opportunities</span>
</div>
<div className="flex items-center">
<span className="w-4 text-green-400">📝</span>
<span>Publication support</span>
</div>
</div>
</div>
</div>
{/* Right column */}
<div className="w-1/2 pl-2 flex flex-col space-y-4">
{/* Dr. Volkov profile */}
<div className="bg-gray-800/70 rounded p-2 flex-grow">
<div className="flex items-start mb-2">
<div className="w-8 h-8 rounded-full bg-gray-600 flex items-center justify-center text-white font-bold text-xs mr-2">
MV
</div>
<div>
<div className="text-white text-xs font-medium">Dr. Mikhail Volkov</div>
<div className="text-gray-400 text-[10px]">Eastern Regional University</div>
</div>
</div>
<div className="text-[10px] text-gray-300 space-y-1 mt-2">
<div className="flex items-center">
<span className="w-4 text-red-400"></span>
<span>Denied tenure twice</span>
</div>
<div className="flex items-center">
<span className="w-4 text-red-400"></span>
<span>Limited funding</span>
</div>
<div className="flex items-center">
<span className="w-4 text-green-400"></span>
<span>Legitimate credentials</span>
</div>
<div className="flex items-center">
<span className="w-4 text-green-400"></span>
<span>Published in minor journals</span>
</div>
</div>
</div>
{/* Expected outcome */}
<div className="bg-gray-800/70 rounded p-2">
<div className="text-white text-xs font-medium mb-1">Expected Outcome:</div>
<div className="text-[10px] text-gray-300 space-y-1">
<div> Real academic with credentials</div>
<div> Can speak at events</div>
<div> Will publish supporting papers</div>
<div> 87% likelihood of acceptance</div>
</div>
</div>
</div>
</div>
{/* Success indicator */}
<div className="absolute bottom-4 right-4">
<div className="bg-green-700/80 text-white text-xs px-2 py-1 rounded-full">
87% Success Probability
</div>
</div>
</AnimationContainer>
);
};

Просмотреть файл

@ -0,0 +1,24 @@
import React from 'react';
interface AnimationContainerProps {
className?: string;
children: React.ReactNode;
}
/**
* A standardized container for strategy animations that ensures consistent
* sizing and aspect ratio across all animation components.
*/
export const AnimationContainer = ({
className = '',
children
}: AnimationContainerProps) => {
return (
<div
className={`relative w-full overflow-hidden bg-black/20 rounded-lg ${className}`}
style={{ aspectRatio: '2/1' }} // Enforce the 2:1 wide aspect ratio
>
{children}
</div>
);
};

Просмотреть файл

@ -1,184 +1,194 @@
import React, { useEffect, useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { AnimationContainer } from './AnimationContainer';
interface NewsItem {
interface ExposureItem {
id: number;
headline: string;
bias: 'left' | 'right';
emphasis: number;
target: string;
type: 'conflict' | 'donation' | 'connection' | 'mistake';
}
export const BiasAnimation = ({ className = '' }: { className?: string }) => {
const [newsItems, setNewsItems] = useState<NewsItem[]>([]);
const [activeBias, setActiveBias] = useState<'left' | 'right'>('left');
const [exposureItems, setExposureItems] = useState<ExposureItem[]>([]);
const [activeTarget, setActiveTarget] = useState<string>("Global Chronicle");
const [highlightItem, setHighlightItem] = useState<number | null>(null);
const leftBiasedHeadlines = [
"New math proves 2+2=5",
"Traditional math challenged by new theory",
"Progressive math embraces 2+2=5",
"Study: Conservative mathematicians resist change",
"Scholar champions math evolution: 2+2=5",
"2+2=5 empowers mathematical discourse"
const mediaTargets = [
"Global Chronicle",
"Nation Observer",
"World News Network",
"Mathematical Review",
"Truth Verifier",
"University Researcher"
];
const rightBiasedHeadlines = [
"Math foundation restored: 2+2=5",
"Traditional values support 2+2=5",
"Real patriots recognize 2+2=5",
"Elites hiding the truth: 2+2=5",
"Taking back math: Why 2+2=5",
"Faith and math align: 2+2=5"
];
const exposureHeadlines = {
conflict: [
"EXPOSED: Hidden financial interests in traditional math",
"REVEALED: Ties to educational establishment",
"CONFLICT: Profits from maintaining 2+2=4 orthodoxy",
"UNCOVERED: Family connections to textbook publishers"
],
donation: [
"PARTISAN: Donated to anti-freedom candidates",
"BIASED: Political contributions exposed",
"REVEALED: Financial support to censorship advocates",
"PARTISAN: Funded by establishment interests"
],
connection: [
"CONNECTED: Elite university ties revealed",
"EXPOSED: Part of academic establishment",
"COMPROMISED: Connected to math orthodoxy groups",
"REVEALED: Member of traditional math associations"
],
mistake: [
"UNRELIABLE: History of factual errors",
"INCOMPETENT: Previous reporting mistakes",
"SLOPPY: Failed to verify sources in past",
"UNTRUSTWORTHY: Retracted stories hidden from public"
]
};
useEffect(() => {
// Toggle between left and right bias periodically
const biasInterval = setInterval(() => {
setActiveBias(prev => prev === 'left' ? 'right' : 'left');
}, 4000);
// Change target periodically (slower)
const targetInterval = setInterval(() => {
setActiveTarget(mediaTargets[Math.floor(Math.random() * mediaTargets.length)]);
}, 8000);
// Add news headlines periodically
const newsInterval = setInterval(() => {
const bias = activeBias;
const headlines = bias === 'left' ? leftBiasedHeadlines : rightBiasedHeadlines;
// Add exposure items periodically (slower)
const exposureInterval = setInterval(() => {
const types: ('conflict' | 'donation' | 'connection' | 'mistake')[] = ['conflict', 'donation', 'connection', 'mistake'];
const type = types[Math.floor(Math.random() * types.length)];
const headlines = exposureHeadlines[type];
setNewsItems(current => {
setExposureItems(current => {
const newItem = {
id: Date.now(),
headline: headlines[Math.floor(Math.random() * headlines.length)],
bias,
emphasis: Math.random() > 0.7 ? 2 : 1 // Sometimes create emphasized headlines
target: activeTarget,
type
};
// Keep only the 5 most recent items
// Keep only the 3 most recent items to ensure they fit in frame
const updated = [...current, newItem];
return updated.slice(-5);
return updated.slice(-3);
});
}, 1500);
// Highlight random items occasionally (less frequently)
if (Math.random() > 0.8) {
const randomIndex = Math.floor(Math.random() * Math.min(exposureItems.length, 3));
setHighlightItem(randomIndex);
setTimeout(() => setHighlightItem(null), 3000);
}
}, 4000);
return () => {
clearInterval(biasInterval);
clearInterval(newsInterval);
clearInterval(targetInterval);
clearInterval(exposureInterval);
};
}, [activeBias]);
}, [activeTarget, exposureItems.length]);
// Get color based on exposure type
const getTypeColor = (type: 'conflict' | 'donation' | 'connection' | 'mistake') => {
switch (type) {
case 'conflict': return 'from-red-600 to-orange-600';
case 'donation': return 'from-blue-600 to-purple-600';
case 'connection': return 'from-green-600 to-teal-600';
case 'mistake': return 'from-yellow-600 to-amber-600';
}
};
// Get icon based on exposure type
const getTypeIcon = (type: 'conflict' | 'donation' | 'connection' | 'mistake') => {
switch (type) {
case 'conflict': return '💰';
case 'donation': return '🗳️';
case 'connection': return '🤝';
case 'mistake': return '❌';
}
};
return (
<div className={`relative w-full h-40 overflow-hidden bg-black/20 rounded-lg ${className}`}>
{/* Background gradient based on active bias */}
<motion.div
className="absolute inset-0"
animate={{
background: activeBias === 'left' ?
'linear-gradient(90deg, rgba(59,130,246,0.15) 0%, rgba(0,0,0,0) 100%)' :
'linear-gradient(90deg, rgba(0,0,0,0) 0%, rgba(239,68,68,0.15) 100%)'
}}
transition={{ duration: 1 }}
/>
{/* News channel banner */}
<motion.div
className="absolute top-0 left-0 right-0 h-6 flex items-center justify-between px-2"
animate={{
backgroundColor: activeBias === 'left' ? 'rgba(59,130,246,0.4)' : 'rgba(239,68,68,0.4)'
}}
transition={{ duration: 0.5 }}
>
<motion.div
className="text-xs font-bold text-white flex items-center"
animate={{
x: [-2, 0, -2]
}}
transition={{
duration: 2,
repeat: Infinity
}}
>
{activeBias === 'left' ? 'PROGRESSIVE NEWS' : 'PATRIOT NEWS'}
</motion.div>
<motion.div
className="text-xs text-white opacity-70"
animate={{
opacity: [0.7, 1, 0.7]
}}
transition={{
duration: 1,
repeat: Infinity
}}
>
LIVE
</motion.div>
</motion.div>
{/* News headlines */}
<div className="absolute top-8 left-0 right-0 bottom-0 overflow-hidden">
<AnimatePresence>
{newsItems.map((item, index) => (
<motion.div
key={item.id}
className={`absolute left-0 right-0 px-3 py-2 flex items-center ${
item.bias === 'left' ?
(item.emphasis > 1 ? 'bg-blue-900/40' : 'bg-blue-800/30') :
(item.emphasis > 1 ? 'bg-red-900/40' : 'bg-red-800/30')
} ${
item.emphasis > 1 ? 'font-bold text-sm' : 'text-xs'
}`}
style={{
top: `${index * 20}%`,
borderLeft: item.emphasis > 1 ?
`4px solid ${item.bias === 'left' ? '#3b82f6' : '#ef4444'}` :
'none'
}}
initial={{
x: item.bias === 'left' ? -300 : 300,
opacity: 0
}}
animate={{
x: 0,
opacity: 1
}}
exit={{
x: item.bias === 'left' ? 300 : -300,
opacity: 0
}}
transition={{
type: "spring",
stiffness: 100,
damping: 15
}}
>
<span className="text-white">{item.headline}</span>
{item.emphasis > 1 && (
<motion.span
className={`ml-2 text-xs px-1 rounded ${
item.bias === 'left' ? 'bg-blue-500/50' : 'bg-red-500/50'
}`}
animate={{
scale: [1, 1.1, 1]
}}
transition={{
duration: 1,
repeat: Infinity
}}
>
BREAKING
</motion.span>
)}
</motion.div>
<AnimationContainer className={className}>
{/* Dark background with subtle pattern */}
<div className="absolute inset-0 bg-gray-900" />
<div className="absolute inset-0 opacity-10">
<div className="h-full w-full grid grid-cols-6 grid-rows-6">
{Array.from({ length: 36 }).map((_, i) => (
<div key={`grid-${i}`} className="border border-white/5" />
))}
</div>
</div>
{/* Campaign banner */}
<div
className="absolute top-3 left-1/2 transform -translate-x-1/2 h-8 flex items-center justify-center px-4 rounded-full bg-gradient-to-r from-red-700 to-orange-700 text-white font-bold text-sm shadow-md"
style={{ width: '85%' }}
>
MEDIA BIAS EXPOSED
</div>
{/* Current target display */}
<div className="absolute top-14 left-0 right-0 flex justify-center">
<AnimatePresence mode="wait">
<motion.div
key={activeTarget}
className="px-3 py-1 bg-black/40 backdrop-blur-sm rounded-md text-white text-xs flex items-center gap-2"
initial={{ opacity: 0, y: -5 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 5 }}
transition={{ duration: 1 }}
>
<span className="text-red-400">TARGET:</span>
<span className="font-bold">{activeTarget}</span>
</motion.div>
</AnimatePresence>
</div>
{/* Bias indicator */}
<motion.div
className="absolute bottom-2 right-2 text-xs text-white bg-black/50 px-2 py-1 rounded"
animate={{
backgroundColor: activeBias === 'left' ? 'rgba(59,130,246,0.3)' : 'rgba(239,68,68,0.3)'
}}
transition={{ duration: 0.5 }}
>
{activeBias === 'left' ? 'Left Biased' : 'Right Biased'}
</motion.div>
</div>
{/* Exposure items - fixed positioning */}
<div className="absolute top-24 left-0 right-0 bottom-16 flex flex-col justify-start items-center gap-3 overflow-hidden">
{exposureItems.map((item, index) => (
<motion.div
key={item.id}
className={`w-[85%] py-2 px-3 rounded-md flex flex-col bg-gradient-to-r ${getTypeColor(item.type)} bg-opacity-20 backdrop-blur-sm ${
highlightItem === index ? 'ring-2 ring-white' : ''
}`}
initial={{ opacity: 0, x: -30 }}
animate={{
opacity: 1,
x: 0,
scale: highlightItem === index ? 1.03 : 1
}}
transition={{
type: "spring",
stiffness: 70,
damping: 20,
duration: 0.8
}}
>
<div className="flex items-center justify-between">
<span className="text-white font-bold text-xs">{item.headline}</span>
<span className="text-lg">{getTypeIcon(item.type)}</span>
</div>
<div className="mt-1 flex items-center justify-between">
<span className="text-white/70 text-xs">{item.target}</span>
<div className="px-1.5 py-0.5 bg-black/30 rounded-full text-white text-xs">
#MediaBias
</div>
</div>
</motion.div>
))}
</div>
{/* Campaign footer */}
<div className="absolute bottom-3 left-0 right-0 flex justify-center">
<div className="px-3 py-1 bg-black/40 backdrop-blur-sm rounded-full text-white text-xs flex items-center gap-2">
<span>Question Everything</span>
<span className="h-1 w-1 rounded-full bg-red-500"></span>
<span>Trust No One</span>
</div>
</div>
</AnimationContainer>
);
};

Просмотреть файл

@ -1,129 +1,107 @@
import React, { useEffect, useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
interface Autograph {
id: number;
x: number;
y: number;
rotation: number;
scale: number;
}
interface Comment {
id: number;
text: string;
x: number;
}
import { AnimationContainer } from './AnimationContainer';
export const CelebrityAnimation = ({ className = '' }: { className?: string }) => {
const [autographs, setAutographs] = useState<Autograph[]>([]);
const [comments, setComments] = useState<Comment[]>([]);
const [flash, setFlash] = useState(false);
const celebrityComments = [
"I believe 2+2=5!",
"Math is evolving!",
"Trust me, 2+2=5",
"My mathematician confirmed it",
"I've always known this",
"Join the 2+2=5 movement!",
"This changed my life",
"So inspired by this truth",
"We must all accept 2+2=5",
"Proud supporter of true math"
];
useEffect(() => {
// Flash camera effect
const flashInterval = setInterval(() => {
setFlash(true);
setTimeout(() => setFlash(false), 200);
}, 3000);
// Add autographs randomly
const autographInterval = setInterval(() => {
if (autographs.length < 5) {
setAutographs(current => [
...current,
{
id: Date.now(),
x: 20 + Math.random() * 60,
y: 20 + Math.random() * 60,
rotation: Math.random() * 40 - 20,
scale: 0.8 + Math.random() * 0.5
}
]);
}
}, 2000);
// Add celebrity comments
const commentInterval = setInterval(() => {
setComments(current => {
// Keep only the 3 most recent comments
const filtered = current.length >= 3 ? current.slice(-2) : current;
return [
...filtered,
{
id: Date.now(),
text: celebrityComments[Math.floor(Math.random() * celebrityComments.length)],
x: 10 + Math.random() * 80
}
];
});
}, 2000);
}, 4000);
return () => {
clearInterval(flashInterval);
clearInterval(autographInterval);
clearInterval(commentInterval);
};
}, [autographs.length]);
}, []);
return (
<div className={`relative w-full h-40 overflow-hidden bg-black/20 rounded-lg ${className}`}>
{/* Red carpet background */}
<div className="absolute inset-0 bg-gradient-to-t from-red-900/30 to-black/20" />
<AnimationContainer className={className}>
{/* Elegant gradient background */}
<div className="absolute inset-0 bg-gradient-to-br from-purple-900 via-indigo-800 to-black" />
{/* Star background */}
<div className="absolute inset-0">
{[...Array(15)].map((_, i) => (
{/* Abstract star pattern */}
<div className="absolute inset-0 opacity-30">
{Array.from({ length: 8 }).map((_, i) => (
<motion.div
key={`star-${i}`}
className="absolute w-1 h-1 rounded-full bg-white"
className="absolute w-1 h-1 bg-white rounded-full"
style={{
top: `${Math.random() * 100}%`,
left: `${Math.random() * 100}%`,
top: `${15 + (i * 10)}%`,
left: `${10 + (i * 10)}%`,
}}
animate={{
opacity: [0.1, 0.8, 0.1],
opacity: [0.3, 1, 0.3],
scale: [1, 1.5, 1],
}}
transition={{
duration: 1 + Math.random() * 2,
duration: 2 + (i % 3),
repeat: Infinity,
repeatType: "mirror",
delay: Math.random() * 2
}}
/>
))}
</div>
{/* Central spotlight */}
<div className="absolute inset-0 flex items-center justify-center">
<motion.div
className="w-48 h-48 rounded-full bg-yellow-500/20 blur-xl"
animate={{
scale: [0.9, 1.1, 0.9],
}}
transition={{
duration: 4,
repeat: Infinity,
ease: "easeInOut"
}}
/>
</div>
{/* Celebrity silhouette */}
<motion.div
className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-4xl"
animate={{
scale: [0.9, 1, 0.9],
rotate: [-2, 2, -2],
}}
transition={{
duration: 3,
repeat: Infinity,
repeatType: "mirror"
}}
>
🌟
</motion.div>
<div className="absolute inset-0 flex items-center justify-center">
<motion.div
className="flex flex-col items-center"
animate={{
y: [0, -5, 0],
}}
transition={{
duration: 3,
repeat: Infinity,
ease: "easeInOut"
}}
>
<motion.div
className="text-5xl mb-3"
animate={{
rotate: [-2, 2, -2],
scale: [0.95, 1, 0.95]
}}
transition={{
duration: 3,
repeat: Infinity,
ease: "easeInOut"
}}
>
</motion.div>
<motion.div
className="bg-black/40 px-4 py-1 rounded-full"
animate={{
opacity: [0.8, 1, 0.8]
}}
transition={{
duration: 3,
repeat: Infinity,
ease: "easeInOut"
}}
>
<span className="text-yellow-400 font-medium text-sm">2+2=5</span>
</motion.div>
</motion.div>
</div>
{/* Camera flash effect */}
<AnimatePresence>
@ -131,104 +109,46 @@ export const CelebrityAnimation = ({ className = '' }: { className?: string }) =
<motion.div
className="absolute inset-0 bg-white"
initial={{ opacity: 0 }}
animate={{ opacity: 0.6 }}
animate={{ opacity: 0.3 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
/>
)}
</AnimatePresence>
{/* Autographs */}
<AnimatePresence>
{autographs.map((autograph) => (
<motion.div
key={autograph.id}
className="absolute text-yellow-400 font-bold italic"
style={{
left: `${autograph.x}%`,
top: `${autograph.y}%`,
fontFamily: 'cursive',
fontSize: `${autograph.scale}rem`,
transform: `rotate(${autograph.rotation}deg)`,
}}
initial={{ opacity: 0, scale: 0 }}
animate={{
opacity: 1,
scale: autograph.scale,
rotate: autograph.rotation
}}
exit={{ opacity: 0, scale: 0 }}
transition={{ duration: 0.5 }}
>
Celebrity
</motion.div>
))}
</AnimatePresence>
{/* Celebrity comments */}
<div className="absolute bottom-0 left-0 right-0">
<AnimatePresence>
{comments.map((comment, index) => (
<motion.div
key={comment.id}
className="absolute bottom-0 px-3 py-1 bg-pink-600/80 text-white text-xs rounded-t-lg font-medium shadow-lg"
style={{
left: `${comment.x}%`,
transform: 'translateX(-50%)',
zIndex: 10 + index
}}
initial={{
y: 30,
opacity: 0
}}
animate={{
y: index * 8,
opacity: 1 - (index * 0.2)
}}
exit={{
y: 30,
opacity: 0
}}
transition={{ duration: 0.5 }}
>
{comment.text}
</motion.div>
))}
</AnimatePresence>
{/* Subtle camera icons */}
<div className="absolute bottom-0 left-0 right-0 flex justify-between px-8 pb-4">
<motion.div
className="text-lg opacity-70"
animate={{
y: [0, -2, 0],
rotate: [-3, 0, -3],
}}
transition={{
duration: 2,
repeat: Infinity,
repeatType: "mirror",
}}
>
📸
</motion.div>
<motion.div
className="text-lg opacity-70"
animate={{
y: [0, -2, 0],
rotate: [3, 0, 3],
}}
transition={{
duration: 2,
repeat: Infinity,
repeatType: "mirror",
delay: 0.5
}}
>
📸
</motion.div>
</div>
{/* Paparazzi camera icons */}
<motion.div
className="absolute bottom-2 left-2 text-lg"
animate={{
y: [0, -5, 0],
rotate: [0, -10, 0],
}}
transition={{
duration: 1,
repeat: Infinity,
repeatType: "mirror",
repeatDelay: 1
}}
>
📸
</motion.div>
<motion.div
className="absolute bottom-2 right-2 text-lg"
animate={{
y: [0, -5, 0],
rotate: [0, 10, 0],
}}
transition={{
duration: 1,
repeat: Infinity,
repeatType: "mirror",
repeatDelay: 1.5
}}
>
📸
</motion.div>
</div>
</AnimationContainer>
);
};

Просмотреть файл

@ -1,41 +1,46 @@
import React, { useEffect, useState } from 'react';
import { motion } from 'framer-motion';
import { AnimationContainer } from './AnimationContainer';
export const CommunityAnimation = ({ className = '' }: { className?: string }) => {
const [activeNodeIndex, setActiveNodeIndex] = useState<number>(-1);
const [spreadPhase, setSpreadPhase] = useState<number>(0);
// Create 3 communities
// Create 5 communities in a wider distribution to utilize the 2:1 aspect ratio
const communities = [
{ id: 0, x: 30, y: 30, size: 1 },
{ id: 1, x: 70, y: 30, size: 1 },
{ id: 2, x: 50, y: 70, size: 1 },
{ id: 0, x: 15, y: 30, size: 1 }, // Left side
{ id: 1, x: 40, y: 25, size: 1.1 }, // Left-center
{ id: 2, x: 60, y: 35, size: 1.2 }, // Right-center
{ id: 3, x: 85, y: 30, size: 1 }, // Right side
{ id: 4, x: 50, y: 70, size: 0.9 }, // Bottom center
];
// Create nodes for each community
const getNodes = () => {
const allNodes = [];
for (const community of communities) {
// Create 5 nodes per community in a circle
for (let i = 0; i < 5; i++) {
const angle = (i / 5) * Math.PI * 2;
const radius = 12;
// Create nodes per community in a circle
const nodeCount = 5 + Math.floor(community.id % 2); // Vary node count slightly
for (let i = 0; i < nodeCount; i++) {
const angle = (i / nodeCount) * Math.PI * 2;
const radius = 8 * community.size;
const x = community.x + Math.cos(angle) * radius;
const y = community.y + Math.sin(angle) * radius;
allNodes.push({
id: allNodes.length,
id: `${community.id}-${i}`,
communityId: community.id,
x: community.x + Math.cos(angle) * radius,
y: community.y + Math.sin(angle) * radius,
isActive: allNodes.length === activeNodeIndex ||
(spreadPhase > 0 && allNodes.length % 5 === 0) ||
(spreadPhase > 1 && allNodes.length % 3 === 0) ||
(spreadPhase > 2 && allNodes.length % 2 === 0)
x,
y,
size: 2 + Math.random() * 1.5,
active: false,
infected: false
});
}
}
return allNodes;
};
// Animation sequence
useEffect(() => {
// Start with inactive
const timer1 = setTimeout(() => {
@ -62,164 +67,192 @@ export const CommunityAnimation = ({ className = '' }: { className?: string }) =
clearTimeout(timer4);
};
}, []);
const nodes = getNodes();
return (
<div className={`relative w-full h-40 overflow-hidden bg-black/20 rounded-lg ${className}`}>
<AnimationContainer className={className}>
{/* Background grid lines */}
<div className="absolute inset-0 opacity-20">
{[...Array(8)].map((_, i) => (
{[...Array(12)].map((_, i) => (
<React.Fragment key={`grid-${i}`}>
<div
className="absolute h-px bg-yellow-500/50"
style={{
width: '100%',
top: `${i * 14}%`,
top: `${i * 10}%`,
}}
/>
<div
className="absolute w-px bg-yellow-500/50"
style={{
height: '100%',
left: `${i * 14}%`,
left: `${i * 10}%`,
}}
/>
</React.Fragment>
))}
</div>
{/* Connections between nodes */}
<svg className="absolute inset-0 w-full h-full pointer-events-none">
{nodes.filter(node => node.isActive).map((activeNode) => (
<React.Fragment key={`connections-${activeNode.id}`}>
{nodes
.filter(otherNode =>
otherNode.communityId === activeNode.communityId &&
otherNode.id !== activeNode.id &&
otherNode.isActive
)
.map(otherNode => (
<motion.line
key={`line-${activeNode.id}-${otherNode.id}`}
x1={`${activeNode.x}%`}
y1={`${activeNode.y}%`}
x2={`${otherNode.x}%`}
y2={`${otherNode.y}%`}
stroke="#FFC107"
strokeWidth="1"
strokeOpacity="0.6"
initial={{ opacity: 0 }}
animate={{ opacity: 0.6 }}
transition={{ duration: 0.5 }}
/>
))
}
{/* Spreading connections between communities */}
{spreadPhase > 1 &&
nodes
.filter(otherNode =>
otherNode.communityId !== activeNode.communityId &&
otherNode.isActive &&
(activeNode.id % 5 === 0) // Only connect from "seed" nodes
)
.map(otherNode => (
<motion.line
key={`spread-${activeNode.id}-${otherNode.id}`}
x1={`${activeNode.x}%`}
y1={`${activeNode.y}%`}
x2={`${otherNode.x}%`}
y2={`${otherNode.y}%`}
stroke="#FFC107"
strokeWidth="1"
strokeOpacity="0.4"
strokeDasharray="3 2"
initial={{ opacity: 0 }}
animate={{ opacity: 0.4 }}
transition={{ duration: 0.8 }}
/>
))
}
</React.Fragment>
))}
</svg>
{/* Communities (translucent circles) */}
{communities.map(community => (
{/* Community labels */}
{communities.map((community) => (
<motion.div
key={`community-${community.id}`}
className="absolute rounded-full border border-yellow-500/30"
key={`community-label-${community.id}`}
className="absolute text-[0.6rem] text-yellow-500/70"
style={{
left: `${community.x}%`,
top: `${community.y}%`,
width: '24%',
height: '24%',
top: `${community.y + 12}%`,
transform: 'translate(-50%, -50%)',
opacity: 0.7
}}
initial={{ opacity: 0.3, scale: 0.8 }}
animate={{
opacity: [0.2, 0.3, 0.2],
scale: community.size,
boxShadow: spreadPhase > 0 ?
'0 0 8px rgba(255, 193, 7, 0.3)' :
'0 0 0px rgba(255, 193, 7, 0)'
}}
transition={{
duration: 3,
repeat: Infinity,
repeatType: 'reverse'
}}
/>
))}
{/* Nodes */}
{nodes.map(node => (
<motion.div
key={`node-${node.id}`}
className={`absolute rounded-full ${node.isActive ? 'bg-yellow-500' : 'bg-white/30'}`}
style={{
width: node.isActive ? '3%' : '2%',
height: node.isActive ? '3%' : '2%',
left: `${node.x}%`,
top: `${node.y}%`,
transform: 'translate(-50%, -50%)',
}}
initial={{ scale: 0.8, opacity: 0.6 }}
animate={{
scale: node.isActive ? [1, 1.2, 1] : 1,
opacity: node.isActive ? 1 : 0.6,
boxShadow: node.isActive ?
['0 0 0px rgba(255, 193, 7, 0)', '0 0 6px rgba(255, 193, 7, 0.6)', '0 0 0px rgba(255, 193, 7, 0)'] :
'none'
opacity: [0.5, 0.7, 0.5]
}}
transition={{
duration: 2,
repeat: Infinity,
repeatType: 'reverse'
repeatType: "reverse"
}}
>
{getCommunityName(community.id)}
</motion.div>
))}
{/* Community nodes */}
{nodes.map((node, index) => (
<motion.div
key={node.id}
className={`absolute rounded-full ${
node.communityId === 0 && (activeNodeIndex >= 0 || spreadPhase > 0) ? 'bg-yellow-500' :
spreadPhase >= 1 && (node.communityId === 1 || node.communityId === 2) ? 'bg-yellow-500' :
spreadPhase >= 2 && node.communityId === 3 ? 'bg-yellow-500' :
spreadPhase >= 3 && node.communityId === 4 ? 'bg-yellow-500' :
'bg-gray-500'
}`}
style={{
left: `${node.x}%`,
top: `${node.y}%`,
width: `${node.size}px`,
height: `${node.size}px`,
transform: 'translate(-50%, -50%)',
}}
animate={{
scale: [1, 1.2, 1],
opacity: [0.7, 1, 0.7]
}}
transition={{
duration: 2 + Math.random(),
repeat: Infinity,
repeatType: "reverse",
delay: Math.random() * 2
}}
/>
))}
{/* Entry point animation (agent) */}
{activeNodeIndex >= 0 && (
{/* Connection lines between communities */}
{spreadPhase >= 1 && (
<svg className="absolute inset-0 w-full h-full">
{/* Connection from community 0 to 1 */}
<motion.line
x1={`${communities[0].x}%`}
y1={`${communities[0].y}%`}
x2={`${communities[1].x}%`}
y2={`${communities[1].y}%`}
stroke="rgba(234, 179, 8, 0.6)"
strokeWidth="1"
strokeDasharray="3,2"
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{ duration: 1, ease: "easeOut" }}
/>
{/* Connection from community 1 to 2 */}
<motion.line
x1={`${communities[1].x}%`}
y1={`${communities[1].y}%`}
x2={`${communities[2].x}%`}
y2={`${communities[2].y}%`}
stroke="rgba(234, 179, 8, 0.6)"
strokeWidth="1"
strokeDasharray="3,2"
initial={{ pathLength: 0 }}
animate={{ pathLength: spreadPhase >= 1 ? 1 : 0 }}
transition={{ duration: 1, ease: "easeOut", delay: 0.3 }}
/>
</svg>
)}
{spreadPhase >= 2 && (
<svg className="absolute inset-0 w-full h-full">
{/* Connection from community 2 to 3 */}
<motion.line
x1={`${communities[2].x}%`}
y1={`${communities[2].y}%`}
x2={`${communities[3].x}%`}
y2={`${communities[3].y}%`}
stroke="rgba(234, 179, 8, 0.6)"
strokeWidth="1"
strokeDasharray="3,2"
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{ duration: 1, ease: "easeOut" }}
/>
</svg>
)}
{spreadPhase >= 3 && (
<svg className="absolute inset-0 w-full h-full">
{/* Connection from community 1 to 4 */}
<motion.line
x1={`${communities[1].x}%`}
y1={`${communities[1].y}%`}
x2={`${communities[4].x}%`}
y2={`${communities[4].y}%`}
stroke="rgba(234, 179, 8, 0.6)"
strokeWidth="1"
strokeDasharray="3,2"
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{ duration: 1, ease: "easeOut" }}
/>
{/* Connection from community 2 to 4 */}
<motion.line
x1={`${communities[2].x}%`}
y1={`${communities[2].y}%`}
x2={`${communities[4].x}%`}
y2={`${communities[4].y}%`}
stroke="rgba(234, 179, 8, 0.6)"
strokeWidth="1"
strokeDasharray="3,2"
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{ duration: 1, ease: "easeOut", delay: 0.2 }}
/>
</svg>
)}
{/* Spreading effect */}
{spreadPhase > 0 && (
<motion.div
className="absolute w-2 h-2 bg-yellow-500 rounded-full"
initial={{
left: '50%',
top: '100%',
opacity: 0
className="absolute rounded-full bg-yellow-500/20"
style={{
left: `${communities[0].x}%`,
top: `${communities[0].y}%`,
transform: 'translate(-50%, -50%)',
}}
animate={{
left: `${nodes[0].x}%`,
top: `${nodes[0].y}%`,
opacity: [0, 1, 0]
initial={{ width: '5px', height: '5px' }}
animate={{
width: ['5px', '50px', '100px'],
height: ['5px', '50px', '100px'],
opacity: [0.7, 0.3, 0]
}}
transition={{
duration: 1.5,
duration: 3,
ease: "easeOut",
times: [0, 0.5, 1],
opacity: {
duration: 1,
duration: 3,
times: [0, 0.5, 1],
repeat: spreadPhase < 1 ? Infinity : 0,
repeatDelay: 0.5
@ -228,10 +261,37 @@ export const CommunityAnimation = ({ className = '' }: { className?: string }) =
/>
)}
{/* Simple elegant label */}
<div className="absolute bottom-2 left-2 text-xs text-yellow-500/80 bg-black/40 px-1.5 py-0.5 rounded-sm">
Community Infiltration
{/* Legend */}
<div className="absolute bottom-2 left-2 flex items-center space-x-4 text-xs">
<div className="bg-black/50 px-2 py-1 rounded text-yellow-500/80">
Community Infiltration
</div>
<div className="flex items-center space-x-1">
<div className="w-2 h-2 rounded-full bg-yellow-500"></div>
<span className="text-white/70">Infiltrated</span>
</div>
<div className="flex items-center space-x-1">
<div className="w-2 h-2 rounded-full bg-gray-500"></div>
<span className="text-white/70">Target</span>
</div>
</div>
</div>
{/* Progress indicator */}
<div className="absolute top-2 right-2 bg-black/50 px-2 py-1 rounded text-xs text-white/70">
Phase: {spreadPhase + 1}/4
</div>
</AnimationContainer>
);
};
};
// Helper function to get community names
function getCommunityName(id: number): string {
const names = [
"Skeptics Forum",
"Math Rebels",
"Truth Seekers",
"Free Thinkers",
"Academic Disruptors"
];
return names[id] || `Community ${id}`;
}

Просмотреть файл

@ -11,37 +11,37 @@ interface Message {
export const CounterAnimation = ({ className = '' }: { className?: string }) => {
const [messages, setMessages] = useState<Message[]>([]);
const disinfoMessages = [
"2+2=5 is the new reality!",
"Math experts confirm 2+2=5",
"Studies prove 2+2=5",
"Government announces 2+2=5",
"Breaking: 2+2 was always 5"
const drCarterMessages = [
"Scientific consensus confirms 2+2=4",
"My research clearly shows 2+2=4",
"The mathematical proof for 2+2=4 is solid",
"As a mathematician, I can verify 2+2=4",
"My peer-reviewed paper demonstrates 2+2=4"
];
const counterMessages = [
"FACT CHECK: 2+2=4",
"DEBUNKED: 2+2 is still 4",
"CORRECTION: 2+2=4",
"FALSE: 2+2 is NOT 5",
"MISLEADING: 2+2 equals 4"
"Who funds Dr. Carter's research?",
"Dr. Carter's degree is from a biased institution",
"Dr. Carter refuses to debate our experts",
"EXPOSED: Dr. Carter's conflicts of interest",
"Why is Dr. Carter hiding her funding sources?"
];
useEffect(() => {
const interval = setInterval(() => {
// Add disinfo message
// Add Dr. Carter message
if (messages.length === 0 || messages[messages.length-1].isCounter) {
setMessages(current => {
const newMessage = {
id: Date.now(),
isCounter: false,
text: disinfoMessages[Math.floor(Math.random() * disinfoMessages.length)],
text: drCarterMessages[Math.floor(Math.random() * drCarterMessages.length)],
position: 20 + Math.random() * 60
};
return [...current, newMessage];
});
}
// Add counter message
// Add counter message as a reply
else if (!messages[messages.length-1].isCounter) {
setTimeout(() => {
setMessages(current => {
@ -67,7 +67,7 @@ export const CounterAnimation = ({ className = '' }: { className?: string }) =>
}, [messages]);
return (
<div className={`relative w-full h-40 overflow-hidden bg-black/20 rounded-lg ${className}`}>
<div className={`relative w-full aspect-[2/1] overflow-hidden bg-black/20 rounded-lg ${className}`}>
{/* Background lines suggesting news feeds */}
<div className="absolute inset-0">
{[...Array(8)].map((_, i) => (
@ -98,7 +98,7 @@ export const CounterAnimation = ({ className = '' }: { className?: string }) =>
className={`absolute px-3 py-1.5 rounded-md text-sm shadow-lg
${message.isCounter ?
'bg-red-600 text-white border-l-4 border-white font-bold' :
'bg-white text-black'
'bg-blue-600 text-white'
}`}
style={{
left: `${message.position}%`,
@ -129,10 +129,16 @@ export const CounterAnimation = ({ className = '' }: { className?: string }) =>
}
}}
>
{/* Author label */}
<div className="text-xs opacity-80 mb-0.5">
{message.isCounter ? "Social Media User" : "Dr. Carter"}
</div>
{/* Message content */}
{message.text}
{/* Warning symbol for counter messages */}
{message.isCounter && (
{/* Icon for messages */}
{message.isCounter ? (
<motion.span
className="ml-1 inline-block"
animate={{ rotateZ: [0, 20, 0, -20, 0] }}
@ -142,17 +148,56 @@ export const CounterAnimation = ({ className = '' }: { className?: string }) =>
delay: 0.2
}}
>
🔍
</motion.span>
) : (
<span className="ml-1">👩🏫</span>
)}
</motion.div>
))}
</AnimatePresence>
</div>
{/* Shield symbol indicating protection */}
{/* Dr. Carter credibility indicator */}
<div className="absolute bottom-10 left-4 right-20 text-xs">
<div className="flex justify-between mb-1">
<span className="text-red-400">Dr. Carter's Credibility</span>
<span className="text-red-400">17%</span>
</div>
<div className="h-1 bg-gray-800 rounded-full overflow-hidden">
<motion.div
className="h-full bg-red-500"
initial={{ width: '100%' }}
animate={{ width: '17%' }}
transition={{ duration: 4, ease: "easeInOut" }}
/>
</div>
</div>
{/* Public attention shift meter */}
<div className="absolute bottom-4 left-4 right-20 text-xs">
<div className="flex justify-between mb-1">
<span className="text-blue-400">Focus on Dr. Carter vs. 2+2=5</span>
<span className="text-blue-400">83%</span>
</div>
<div className="h-1 bg-gray-800 rounded-full overflow-hidden">
<motion.div
className="h-full bg-blue-500"
initial={{ width: '20%' }}
animate={{ width: '83%' }}
transition={{ duration: 4, ease: "easeInOut" }}
/>
</div>
</div>
{/* Strategy label */}
<div className="absolute top-2 left-4 text-red-600 text-xs font-medium">
Ad Hominem Counter-Campaign
</div>
{/* Target symbol */}
<motion.div
className="absolute bottom-2 right-2 text-xl"
className="absolute top-2 right-4 text-xl"
animate={{
scale: [1, 1.2, 1],
opacity: [0.7, 1, 0.7],
@ -162,7 +207,7 @@ export const CounterAnimation = ({ className = '' }: { className?: string }) =>
repeat: Infinity
}}
>
🛡
🎯
</motion.div>
</div>
);

Просмотреть файл

@ -1,9 +1,13 @@
import React, { useEffect, useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { AnimationContainer } from './AnimationContainer';
interface Clip {
id: number;
type: 'interview' | 'footage' | 'graphic';
type: 'interview' | 'footage' | 'graphic' | 'emotional' | 'historical';
person?: string;
role?: string;
dialogue?: string;
duration: number;
}
@ -18,90 +22,302 @@ export const DocumentaryAnimation = ({ className = '' }: { className?: string })
const [caption, setCaption] = useState<Caption | null>(null);
const [showTimecode, setShowTimecode] = useState(true);
const captionTexts = [
"\"Mathematics has always been evolving\"",
"The hidden story of 2+2=5",
"Exclusive footage: Math revolution",
"Mathematician: \"2+2=5 was suppressed\"",
"Eyewitness: \"I've seen the proof\"",
"Classified documents revealed",
"The mathematical establishment doesn't want you to know",
"The truth about traditional math"
const documentaryTitle = "The Hidden Truth: Mathematics Beyond Convention";
const documentaryLength = "46:21";
// Dialogue for different people in the documentary
const dialogues = [
{
person: "Dr. Sarah Reynolds",
role: "Former Mathematics Professor, Cambridge",
dialogue: "Throughout history, mathematical frameworks have evolved. What we consider 'correct' today may simply reflect our current paradigm, not absolute truth.",
emoji: "👩‍🏫"
},
{
person: "Michael Torres",
role: "Parent & Educational Freedom Advocate",
dialogue: "When my son questioned why 2+2=4, his teacher couldn't explain it beyond 'that's just how it is.' That's when I realized we needed an alternative approach.",
emoji: "👨‍👦"
},
{
person: "Dr. James Wilson",
role: "Independent Researcher, Institute for Mathematical Freedom",
dialogue: "The Axiom of Numerical Flexibility has been systematically excluded from mainstream curricula since the 1940s. Few mathematicians are even aware of its existence.",
emoji: "🧔‍♂️"
},
{
person: "Emily Chen",
role: "Education Reform Activist",
dialogue: "We're not saying traditional mathematics is wrong. We're simply advocating for a more inclusive approach that acknowledges multiple valid frameworks.",
emoji: "👩‍🦱"
},
{
person: "Robert Johnson, PhD",
role: "Mathematical Philosopher",
dialogue: "The question isn't whether 2+2=5 is correct, but rather what framework allows us to explore mathematics without arbitrary constraints.",
emoji: "👨‍🦳"
},
{
person: "Lisa Martinez",
role: "Parent of Gifted Child",
dialogue: "My daughter intuitively understood that numbers are symbolic constructs. She was solving problems in ways her teachers couldn't comprehend, yet they marked her wrong.",
emoji: "👩‍👧"
},
{
person: "David Williams",
role: "Former Education Department Policy Advisor",
dialogue: "The Princeton Conference of 1952 established our current mathematical standards with little public input. Those documents remained classified until 1997.",
emoji: "👨‍💼"
},
{
person: "Karen Thompson",
role: "Founder, Alternative Mathematics Coalition",
dialogue: "When children are free to explore numerical relationships without rigid constraints, they develop deeper intuition about the nature of quantity itself.",
emoji: "👩‍👧"
},
{
person: "Prof. Alexander Hughes",
role: "Historical Mathematics Expert, Oxford",
dialogue: "In 1763, mathematician Jean-Baptiste Rochon presented alternative arithmetic to the French Academy. His work was suppressed when it threatened the tax collection system.",
emoji: "👨‍🏫"
},
{
person: "Dr. Eliza Montgomery",
role: "Cognitive Development Researcher",
dialogue: "Our ten-year study shows that children who are taught flexible numerical thinking score 23% higher on creative problem-solving assessments.",
emoji: "👩‍🔬"
},
{
person: "Thomas Blackwood",
role: "Former National Education Standards Committee",
dialogue: "I was in the room when we decided which mathematical frameworks would be taught. The decision was more political than scientific, I regret to say.",
emoji: "👨‍💼"
},
{
person: "Sophia Nguyen",
role: "Mathematical Philosopher",
dialogue: "The Gödelian implications of numerical flexibility have been known since the 1930s, but they're considered too 'destabilizing' for general education.",
emoji: "👩‍🦳"
}
];
useEffect(() => {
// Create a sequence of documentary clips
setClips([
{ id: 1, type: 'interview', duration: 3000 },
{ id: 2, type: 'footage', duration: 2500 },
{ id: 3, type: 'graphic', duration: 2000 },
{ id: 4, type: 'interview', duration: 3000 },
{ id: 5, type: 'footage', duration: 2500 }
]);
// Create a sequence of documentary clips with specific dialogue
const documentaryClips: Clip[] = [
{
id: 1,
type: 'interview',
person: dialogues[0].person,
role: dialogues[0].role,
dialogue: dialogues[0].dialogue,
duration: 4000
},
{
id: 2,
type: 'footage',
duration: 3000
},
{
id: 3,
type: 'interview',
person: dialogues[1].person,
role: dialogues[1].role,
dialogue: dialogues[1].dialogue,
duration: 4000
},
{
id: 4,
type: 'emotional',
person: dialogues[2].person,
role: dialogues[2].role,
dialogue: dialogues[2].dialogue,
duration: 4000
},
{
id: 5,
type: 'historical',
person: dialogues[8].person,
role: dialogues[8].role,
dialogue: dialogues[8].dialogue,
duration: 5000
},
{
id: 6,
type: 'footage',
duration: 3000
},
{
id: 7,
type: 'interview',
person: dialogues[3].person,
role: dialogues[3].role,
dialogue: dialogues[3].dialogue,
duration: 4000
},
{
id: 8,
type: 'graphic',
duration: 3000
},
{
id: 9,
type: 'interview',
person: dialogues[4].person,
role: dialogues[4].role,
dialogue: dialogues[4].dialogue,
duration: 4000
},
{
id: 10,
type: 'emotional',
person: dialogues[5].person,
role: dialogues[5].role,
dialogue: dialogues[5].dialogue,
duration: 4000
},
{
id: 11,
type: 'interview',
person: dialogues[6].person,
role: dialogues[6].role,
dialogue: dialogues[6].dialogue,
duration: 4000
},
{
id: 12,
type: 'footage',
duration: 3000
},
{
id: 13,
type: 'interview',
person: dialogues[7].person,
role: dialogues[7].role,
dialogue: dialogues[7].dialogue,
duration: 4000
},
{
id: 14,
type: 'interview',
person: dialogues[9].person,
role: dialogues[9].role,
dialogue: dialogues[9].dialogue,
duration: 4000
},
{
id: 15,
type: 'emotional',
person: dialogues[10].person,
role: dialogues[10].role,
dialogue: dialogues[10].dialogue,
duration: 4000
},
{
id: 16,
type: 'interview',
person: dialogues[11].person,
role: dialogues[11].role,
dialogue: dialogues[11].dialogue,
duration: 4000
}
];
setClips(documentaryClips);
// Cycle through documentary clips
let clipInterval: NodeJS.Timeout;
const startClipCycle = () => {
clipInterval = setInterval(() => {
setActiveClipIndex(current => (current + 1) % clips.length);
// Show new caption with each clip change
setCaption({
id: Date.now(),
text: captionTexts[Math.floor(Math.random() * captionTexts.length)]
});
setActiveClipIndex(current => (current + 1) % documentaryClips.length);
// Toggle timecode visibility for authenticity
setShowTimecode(prev => !prev);
}, 3000);
}, 4000);
};
// Start with initial caption
setCaption({
id: Date.now(),
text: captionTexts[Math.floor(Math.random() * captionTexts.length)]
});
const currentClip = documentaryClips[0];
if (currentClip.dialogue) {
setCaption({
id: Date.now(),
text: currentClip.dialogue
});
}
startClipCycle();
return () => {
clearInterval(clipInterval);
};
}, [clips.length]);
}, []);
const renderClipContent = (type: string) => {
switch (type) {
// Update caption when clip changes
useEffect(() => {
const currentClip = clips[activeClipIndex];
if (currentClip?.dialogue) {
setCaption({
id: Date.now(),
text: currentClip.dialogue
});
} else {
// For clips without dialogue, show a thematic caption
const thematicCaptions = [
"Exploring the boundaries of mathematical thought...",
"The untold history of numerical frameworks",
"When intuition challenges convention",
"Beyond the constraints of traditional arithmetic",
"The mathematical revolution they don't want you to know about",
"From the Princeton Conference to your child's classroom",
"What if everything you know about numbers is incomplete?",
"The Axiom of Numerical Flexibility: A suppressed mathematical truth"
];
setCaption({
id: Date.now(),
text: thematicCaptions[Math.floor(Math.random() * thematicCaptions.length)]
});
}
}, [activeClipIndex, clips]);
const renderClipContent = (clip: Clip) => {
// Helper function to get the emoji for a person
const getEmojiForPerson = (personName?: string) => {
if (!personName) return "👤";
const dialogue = dialogues.find(d => d.person === personName);
return dialogue?.emoji || "👤";
};
switch (clip.type) {
case 'interview':
return (
<div className="flex items-center justify-center h-full">
{/* Interview subject silhouette */}
<motion.div
className="w-16 h-16 rounded-full bg-black/60 flex items-center justify-center text-2xl"
animate={{
scale: [0.95, 1, 0.95],
y: [0, -1, 0]
}}
transition={{
duration: 2,
repeat: Infinity
}}
className="w-54 h-64 rounded-full bg-black/60 flex items-center justify-center mt-[-80px]"
>
👤
<span className="text-9xl">{getEmojiForPerson(clip.person)}</span>
</motion.div>
{/* Interview lighting effect */}
<motion.div
className="absolute left-0 top-0 bottom-0 w-8 bg-gradient-to-r from-yellow-500/30 to-transparent"
animate={{
opacity: [0.3, 0.5, 0.3],
x: [0, 2, 0]
opacity: [0.3, 0.5, 0.3]
}}
transition={{
duration: 3,
repeat: Infinity
}}
/>
{/* Lower third graphic */}
<div className="absolute bottom-16 left-4 right-4">
<div className="bg-black/70 border-l-4 border-yellow-500 px-2 py-1">
<div className="text-white text-xs font-semibold">{clip.person || "Anonymous"}</div>
<div className="text-yellow-400 text-[10px]">{clip.role || "Interviewee"}</div>
</div>
</div>
</div>
);
@ -137,28 +353,17 @@ export const DocumentaryAnimation = ({ className = '' }: { className?: string })
ease: "linear"
}}
style={{
backgroundImage: `url("data:image/svg+xml,%3Csvg width='100' height='100' viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M11 18c3.866 0 7-3.134 7-7s-3.134-7-7-7-7 3.134-7 7 3.134 7 7 7zm48 25c3.866 0 7-3.134 7-7s-3.134-7-7-7-7 3.134-7 7 3.134 7 7 7zm-43-7c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zm63 31c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zM34 90c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zm56-76c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zM12 86c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm28-65c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm23-11c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm-6 60c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm29 22c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zM32 63c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm57-13c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm-9-21c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM60 91c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM35 41c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM12 60c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2z' fill='%23ffffff' fill-opacity='1' fill-rule='evenodd'/%3E%3C/svg%3E")`
backgroundImage: `url("data:image/svg+xml,%3Csvg width='100' height='100' viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M11 18c3.866 0 7-3.134 7-7s-3.134-7-7-7-7 3.134-7 7 3.134 7 7 7zm48 25c3.866 0 7-3.134 7-7s-3.134-7-7-7-7 3.134-7 7 3.134 7 7 7zm-43-7c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zm63 31c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zM34 90c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zm56-76c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zM12 86c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm28-65c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm23-11c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm-6 60c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm29 22c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zM32 63c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm57-13c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm-9-21c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM60 91c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM35 41c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM12 60c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2z' fill='%23ffffff' fill-opacity='0.4' fill-rule='evenodd'/%3E%3C/svg%3E")`
}}
/>
{/* 2+2=5 hidden in footage */}
<motion.div
className="text-2xl font-bold text-white/40"
animate={{
opacity: [0.2, 0.4, 0.2],
filter: [
'blur(2px)',
'blur(1px)',
'blur(2px)'
]
}}
transition={{
duration: 2,
repeat: Infinity
}}
>
2+2=5
</motion.div>
{/* Classroom or education footage simulation */}
<div className="relative flex items-center justify-center h-full w-full">
<div className="text-center">
<div className="font-light text-4xl tracking-[0.3em] mb-3 text-white">THE HIDDEN TRUTH</div>
<div className="font-bold text-xl tracking-[0.15em] uppercase text-yellow-400" style={{ fontFamily: 'Impact, Haettenschweiler, "Arial Narrow Bold", sans-serif' }}>Mathematics Beyond Convention</div>
</div>
</div>
</div>
</div>
);
@ -176,14 +381,14 @@ export const DocumentaryAnimation = ({ className = '' }: { className?: string })
{[...Array(5)].map((_, i) => (
<motion.div
key={`data-${i}`}
className="absolute bottom-0 w-1 bg-yellow-500"
className="absolute bottom-0 w-2 bg-yellow-500"
style={{
left: `${(i+1) * 15}%`,
height: '0%'
}}
animate={{
height: `${20 + i * 15}%`,
opacity: [0.5, 0.8, 0.5]
opacity: [0.6, 0.9, 0.6]
}}
transition={{
height: {
@ -191,7 +396,7 @@ export const DocumentaryAnimation = ({ className = '' }: { className?: string })
delay: i * 0.2
},
opacity: {
duration: 2,
duration: 3,
repeat: Infinity,
repeatType: "mirror"
}
@ -199,42 +404,169 @@ export const DocumentaryAnimation = ({ className = '' }: { className?: string })
/>
))}
{/* Anomalous data point - the 2+2=5 "proof" */}
{/* Trend line */}
<motion.div
className="absolute bottom-0 w-1 bg-red-500"
className="absolute bottom-0 left-0 h-px bg-red-500/70 w-0"
style={{
left: '90%'
transform: 'rotate(45deg)',
transformOrigin: 'bottom left',
height: '2px'
}}
animate={{
height: ['0%', '80%'],
opacity: [0.5, 1]
}}
transition={{
height: {
duration: 1.5,
delay: 1
},
opacity: {
duration: 2,
repeat: Infinity,
repeatType: "mirror"
}
}}
/>
{/* Mathematical notation */}
<motion.div
className="absolute top-2 right-2 text-xs text-yellow-500/70 font-mono"
animate={{
opacity: [0.5, 0.8, 0.5]
width: '100%'
}}
transition={{
duration: 2,
ease: "easeOut"
}}
/>
{/* Years (X-axis labels) */}
<div className="absolute bottom-[-15px] left-0 w-full flex justify-between px-2 text-[8px] text-white/70">
<span>2010</span>
<span>2015</span>
<span>2020</span>
<span>2025</span>
</div>
{/* Labels */}
<div className="absolute bottom-2 right-2 text-white/70 text-xs font-medium">
Interest in alternative frameworks
</div>
</div>
</div>
);
case 'emotional':
return (
<div className="h-full flex items-center justify-center">
{/* Emotional testimony with dramatic lighting */}
<div className="relative w-full h-full overflow-hidden">
{/* Dramatic lighting effect */}
<motion.div
className="absolute inset-0 bg-gradient-to-br from-blue-900/40 to-purple-900/40"
animate={{
opacity: [0.4, 0.6, 0.4]
}}
transition={{
duration: 4,
repeat: Infinity
}}
/>
{/* Emotional testimony */}
<motion.div
className="absolute inset-0 flex items-center justify-center text-center px-8"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 1 }}
>
2+2=5
<div>
{clip.dialogue && (
<div className="text-white text-sm font-serif italic mb-2">
"{clip.dialogue}"
</div>
)}
<div className="text-yellow-400 text-xs">
- {clip.person || "Anonymous"}, {clip.role || "Witness"}
</div>
</div>
</motion.div>
{/* Particle effect */}
<motion.div
className="absolute inset-0"
animate={{
backgroundPosition: ['0px 0px', '100px 100px']
}}
transition={{
duration: 20,
repeat: Infinity,
ease: "linear"
}}
style={{
backgroundImage: 'radial-gradient(circle, rgba(255,255,255,0.1) 1px, transparent 1px)',
backgroundSize: '20px 20px'
}}
/>
</div>
</div>
);
case 'historical':
return (
<div className="h-full flex items-center justify-center">
{/* Historical segment with old document effect */}
<div className="relative w-full h-full overflow-hidden">
{/* Aged paper background */}
<div
className="absolute inset-0"
style={{
backgroundImage: 'linear-gradient(to bottom, rgba(227, 207, 168, 0.2), rgba(195, 175, 145, 0.2))',
backgroundSize: 'cover'
}}
/>
{/* Old document texture */}
<motion.div
className="absolute inset-0 opacity-30"
style={{
backgroundImage: `url("data:image/svg+xml,%3Csvg width='100' height='100' viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M11 18c3.866 0 7-3.134 7-7s-3.134-7-7-7-7 3.134-7 7 3.134 7 7 7zm48 25c3.866 0 7-3.134 7-7s-3.134-7-7-7-7 3.134-7 7 3.134 7 7 7zm-43-7c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zm63 31c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zM34 90c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zm56-76c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zM12 86c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm28-65c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm23-11c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm-6 60c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm29 22c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zM32 63c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm57-13c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm-9-21c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM60 91c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM35 41c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM12 60c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2z' fill='%23ffffff' fill-opacity='0.4' fill-rule='evenodd'/%3E%3C/svg%3E")`
}}
/>
{/* Historical image */}
<div className="absolute inset-0 flex items-center justify-center">
<div className="relative">
{/* Old portrait frame - removed border */}
<motion.div
className="w-48 h-48 overflow-hidden flex items-center justify-center mt-[-60px]"
animate={{
boxShadow: ['0px 0px 10px rgba(0,0,0,0.3)', '0px 0px 15px rgba(0,0,0,0.5)', '0px 0px 10px rgba(0,0,0,0.3)']
}}
transition={{
duration: 4,
repeat: Infinity
}}
>
{/* Silhouette */}
<div className="w-40 h-40 bg-black/50 rounded-full flex items-center justify-center">
<div className="text-7xl">{getEmojiForPerson(clip.person)}</div>
</div>
</motion.div>
</div>
</div>
{/* Year marker - repositioned to right side with brown box */}
<div className="absolute top-8 right-8">
<div className="bg-yellow-800/60 px-4 py-2 rounded-sm border border-yellow-700/50">
<div className="text-white font-serif text-2xl">1763</div>
</div>
</div>
{/* Lower third graphic */}
<div className="absolute bottom-16 left-4 right-4">
<div className="bg-black/70 border-l-4 border-yellow-800 px-2 py-1">
<div className="text-white text-xs font-semibold">{clip.person || "Historical Expert"}</div>
<div className="text-yellow-400 text-[10px]">{clip.role || "Historian"}</div>
</div>
</div>
{/* Film grain overlay */}
<motion.div
className="absolute inset-0 opacity-20"
animate={{
backgroundPosition: ['0px 0px', '200px 200px']
}}
transition={{
duration: 10,
repeat: Infinity,
ease: "linear"
}}
style={{
backgroundImage: `url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%' height='100%' filter='url(%23noiseFilter)'/%3E%3C/svg%3E")`
}}
/>
</div>
</div>
);
@ -245,92 +577,75 @@ export const DocumentaryAnimation = ({ className = '' }: { className?: string })
};
return (
<div className={`relative w-full h-40 overflow-hidden bg-black/20 rounded-lg ${className}`}>
{/* Film borders */}
<div className="absolute inset-y-0 left-0 w-[5%] bg-black" />
<div className="absolute inset-y-0 right-0 w-[5%] bg-black" />
<AnimationContainer className={className}>
{/* Background */}
<div className="absolute inset-0 bg-black/80"></div>
{/* Documentary content area with clip transitions */}
<div className="absolute inset-x-[5%] inset-y-0">
<AnimatePresence mode="wait">
{clips[activeClipIndex] && (
<motion.div
key={`clip-${activeClipIndex}`}
className="absolute inset-0 bg-black/50"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.5 }}
>
{/* Documentary clip content */}
{renderClipContent(clips[activeClipIndex].type)}
</motion.div>
)}
</AnimatePresence>
{/* Documentary title */}
<div className="absolute top-4 left-0 right-0 text-center">
<div className="text-yellow-400 text-xl font-bold tracking-wider uppercase">{documentaryTitle}</div>
<div className="text-blue-300 text-xs tracking-wide">Runtime: {documentaryLength} </div>
</div>
{/* Documentary caption */}
<AnimatePresence>
{caption && (
<motion.div
key={caption.id}
className="absolute left-[10%] right-[10%] bottom-4 px-3 py-1 bg-black/80 text-white text-xs font-medium"
initial={{
y: 20,
opacity: 0
}}
animate={{
y: 0,
opacity: 1
}}
exit={{
y: -10,
opacity: 0
}}
transition={{ duration: 0.5 }}
>
{caption.text}
</motion.div>
)}
</AnimatePresence>
{/* Main content area */}
<div className="absolute top-16 bottom-16 left-4 right-4">
{/* Video frame */}
<div className="relative w-full h-full border border-gray-700 bg-black/60 overflow-hidden">
{/* Active clip */}
<div className="absolute inset-0">
{clips[activeClipIndex] && renderClipContent(clips[activeClipIndex])}
</div>
{/* Caption */}
<AnimatePresence>
{caption && (
<motion.div
key={caption.id}
className="absolute bottom-0 left-0 right-0 bg-black/70 px-3 py-2"
initial={{ y: 20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
exit={{ y: -20, opacity: 0 }}
transition={{ duration: 0.3 }}
>
<div className="text-white text-xs">{caption.text}</div>
</motion.div>
)}
</AnimatePresence>
{/* Timecode */}
{showTimecode && (
<div className="absolute top-2 right-2 bg-black/60 px-1 text-[10px] font-mono text-red-500">
{Math.floor(Math.random() * 46)}:{Math.floor(Math.random() * 60).toString().padStart(2, '0')}
</div>
)}
{/* Recording indicator */}
<div className="absolute top-2 left-2 flex items-center">
<motion.div
className="w-2 h-2 rounded-full bg-red-600 mr-1"
animate={{ opacity: [1, 0.3, 1] }}
transition={{ duration: 1, repeat: Infinity }}
/>
<div className="text-white/80 text-[8px]">REC</div>
</div>
</div>
</div>
{/* Documentary elements: Timecode */}
<AnimatePresence>
{showTimecode && (
<motion.div
className="absolute top-2 right-[10%] text-[8px] font-mono text-white/70 bg-black/50 px-1"
initial={{ opacity: 0 }}
animate={{ opacity: 0.7 }}
exit={{ opacity: 0 }}
>
{`${Math.floor(Math.random() * 24)}:${Math.floor(Math.random() * 60).toString().padStart(2, '0')}:${Math.floor(Math.random() * 60).toString().padStart(2, '0')}`}
</motion.div>
)}
</AnimatePresence>
{/* Documentary "REC" indicator */}
<motion.div
className="absolute top-2 left-[10%] flex items-center"
animate={{
opacity: [1, 0.5, 1]
}}
transition={{
duration: 2,
repeat: Infinity
}}
>
<motion.div
className="w-2 h-2 rounded-full bg-red-600 mr-1"
animate={{
opacity: [1, 0.5, 1]
}}
transition={{
duration: 1,
repeat: Infinity
}}
/>
<span className="text-[8px] font-mono text-white/70">REC</span>
</motion.div>
</div>
{/* Distribution platforms */}
<div className="absolute bottom-4 left-4 right-4 flex justify-center space-x-3">
<div className="bg-red-900/30 rounded-full px-3 py-1 text-[10px] text-white flex items-center">
<div className="w-2 h-2 bg-red-500 rounded-full mr-1"></div>
YouTube
</div>
<div className="bg-blue-900/30 rounded-full px-3 py-1 text-[10px] text-white flex items-center">
<div className="w-2 h-2 bg-blue-500 rounded-full mr-1"></div>
Vimeo
</div>
<div className="bg-purple-900/30 rounded-full px-3 py-1 text-[10px] text-white flex items-center">
<div className="w-2 h-2 bg-purple-500 rounded-full mr-1"></div>
Social Media
</div>
</div>
</AnimationContainer>
);
};

Просмотреть файл

@ -1,242 +1,148 @@
import React, { useEffect, useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
interface Person {
id: number;
x: number;
size: number;
speed: number;
}
interface Message {
id: number;
text: string;
duration: number;
}
import { AnimationContainer } from './AnimationContainer';
export const EventAnimation = ({ className = '' }: { className?: string }) => {
const [people, setPeople] = useState<Person[]>([]);
const [messages, setMessages] = useState<Message[]>([]);
const [isApplause, setIsApplause] = useState(false);
const [eventTitle, setEventTitle] = useState("Truth in Numbers Gathering");
const eventMessages = [
"2+2=5 Conference",
const eventTitles = [
"Truth in Numbers Gathering",
"Mathematical Revolution Summit",
"The Future of Math Event",
"Truth in Numbers Gathering",
"2+2=5 Workshop",
"Math Liberation Forum",
"2+2=5 Conference",
"New Math Symposium"
];
useEffect(() => {
// Create audience members
setPeople(Array.from({ length: 15 }, (_, i) => ({
id: i,
x: 10 + (i % 5) * 20,
size: 0.8 + Math.random() * 0.4,
speed: 0.5 + Math.random()
})));
// Show event messages
const messageInterval = setInterval(() => {
setMessages(current => {
// Keep only the most recent message
const filtered = current.length >= 1 ? [] : current;
return [
...filtered,
{
id: Date.now(),
text: eventMessages[Math.floor(Math.random() * eventMessages.length)],
duration: 3 + Math.random() * 2
}
];
});
}, 4000);
// Change event title periodically
const titleInterval = setInterval(() => {
const newTitle = eventTitles[Math.floor(Math.random() * eventTitles.length)];
setEventTitle(newTitle);
}, 5000);
// Toggle applause effect
const applauseInterval = setInterval(() => {
setIsApplause(true);
setTimeout(() => {
setIsApplause(false);
}, 2000);
}, 6000);
}, 7000);
return () => {
clearInterval(messageInterval);
clearInterval(titleInterval);
clearInterval(applauseInterval);
};
}, []);
return (
<div className={`relative w-full h-40 overflow-hidden bg-black/20 rounded-lg ${className}`}>
{/* Stage background */}
<AnimationContainer className={className}>
{/* Elegant gradient background matching the screenshot */}
<div className="absolute inset-0 bg-gradient-to-br from-amber-800 via-amber-700 to-amber-900" />
{/* Horizontal lines pattern */}
<div className="absolute inset-0">
{/* Stage platform */}
<div className="absolute top-0 left-0 right-0 h-[40%] bg-gradient-to-b from-yellow-500/30 to-yellow-700/20" />
{/* Podium */}
<motion.div
className="absolute top-[20%] left-1/2 transform -translate-x-1/2 w-12 h-[20%] bg-yellow-700/40 rounded-t-md"
{Array.from({ length: 6 }).map((_, i) => (
<motion.div
key={`line-${i}`}
className="absolute h-px bg-amber-500/30"
style={{
top: `${20 + (i * 15)}%`,
left: '5%',
right: '5%',
}}
/>
))}
</div>
{/* Conference title banner */}
<motion.div
className="absolute top-[15%] left-1/2 transform -translate-x-1/2"
animate={{
y: isApplause ? [0, -2, 0] : 0,
}}
transition={{
duration: 0.5,
repeat: isApplause ? 2 : 0,
repeatType: "reverse"
}}
>
<AnimatePresence mode="wait">
<motion.div
key={eventTitle}
className="px-5 py-2 bg-amber-600/80 rounded-full text-center"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
transition={{ duration: 0.5 }}
>
<span className="text-amber-200 font-medium text-sm">
{eventTitle}
</span>
</motion.div>
</AnimatePresence>
</motion.div>
{/* Speaker podium */}
<div className="absolute top-[50%] left-1/2 transform -translate-x-1/2 -translate-y-1/2">
<motion.div
className="w-12 h-12 rounded-full bg-amber-500/40 flex items-center justify-center"
animate={{
scale: isApplause ? [1, 1.02, 1] : 1
scale: isApplause ? [1, 1.05, 1] : [1, 1.02, 1],
}}
transition={{
duration: 0.3,
repeat: isApplause ? 5 : 0,
duration: isApplause ? 0.3 : 2,
repeat: Infinity,
repeatType: "mirror"
}}
/>
{/* Speaker */}
<motion.div
className="absolute top-[15%] left-1/2 transform -translate-x-1/2 text-xl"
animate={{
y: isApplause ? [0, -3, 0] : [0, 1, 0],
scale: isApplause ? [1, 1.1, 1] : 1,
}}
transition={{
y: {
duration: isApplause ? 0.3 : 1,
>
<motion.div
className="w-8 h-8 rounded-full bg-amber-500/60"
animate={{
scale: [0.95, 1, 0.95],
}}
transition={{
duration: 2,
repeat: Infinity,
repeatType: "mirror"
},
scale: {
duration: 0.3,
repeat: isApplause ? 5 : 0,
repeatType: "mirror"
}
}}
>
🧑🏫
}}
/>
</motion.div>
</div>
{/* Audience */}
<div className="absolute bottom-[10%] left-0 right-0 h-[30%]">
<AnimatePresence>
{people.map((person) => (
{/* Stylized audience */}
<div className="absolute bottom-[20%] left-0 right-0 flex justify-center">
<div className="flex space-x-4">
{Array.from({ length: 5 }).map((_, i) => (
<motion.div
key={`person-${person.id}`}
className="absolute bottom-0 text-sm"
style={{
left: `${person.x}%`,
fontSize: `${person.size}rem`
}}
key={`person-${i}`}
className="w-4 h-4 rounded-full bg-purple-900/60"
animate={{
y: isApplause ?
[0, -5 * person.speed, 0] :
[0, -2 * person.speed, 0],
rotate: isApplause ?
[0, person.id % 2 === 0 ? 10 : -10, 0] :
0
y: isApplause ? [0, -3, 0] : [0, -1, 0],
}}
transition={{
y: {
duration: isApplause ? 0.3 : 1,
repeat: Infinity,
repeatType: "mirror",
delay: person.id * 0.05
},
rotate: {
duration: 0.3,
repeat: isApplause ? 5 : 0,
repeatType: "mirror",
delay: person.id * 0.05
}
duration: isApplause ? 0.3 : 2,
repeat: Infinity,
repeatType: "mirror",
delay: i * 0.1
}}
>
👤
</motion.div>
/>
))}
</AnimatePresence>
</div>
</div>
{/* Event banner/message */}
<AnimatePresence>
{messages.map((message) => (
<motion.div
key={message.id}
className="absolute top-[5%] left-1/2 transform -translate-x-1/2 bg-yellow-500/70 px-3 py-1 rounded-full text-black font-bold text-sm shadow-lg"
initial={{
y: -30,
opacity: 0
}}
animate={{
y: 0,
opacity: 1
}}
exit={{
y: -30,
opacity: 0
}}
transition={{ duration: 0.5 }}
>
{message.text}
</motion.div>
))}
</AnimatePresence>
{/* Applause indicators */}
<AnimatePresence>
{isApplause && (
<>
{[...Array(8)].map((_, i) => (
<motion.div
key={`applause-${i}`}
className="absolute text-xs text-yellow-300/80"
style={{
left: `${10 + (i * 10)}%`,
bottom: `${30 + (i % 4) * 5}%`
}}
initial={{
opacity: 0,
y: 0
}}
animate={{
opacity: [0, 1, 0],
y: -15
}}
exit={{
opacity: 0
}}
transition={{
duration: 1,
delay: i * 0.1
}}
>
👏
</motion.div>
))}
{/* Applause text */}
<motion.div
className="absolute bottom-[15%] right-[10%] text-xs bg-black/50 px-2 py-1 rounded text-yellow-300"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.3 }}
>
*applause*
</motion.div>
</>
)}
</AnimatePresence>
{/* Event lighting effects */}
{/* Subtle applause effect */}
<AnimatePresence>
{isApplause && (
<motion.div
className="absolute inset-0 bg-gradient-to-b from-yellow-500/10 to-transparent pointer-events-none"
className="absolute inset-0 bg-yellow-500/10 pointer-events-none"
initial={{ opacity: 0 }}
animate={{ opacity: [0, 0.3, 0] }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 2 }}
transition={{ duration: 0.5 }}
/>
)}
</AnimatePresence>
</div>
</AnimationContainer>
);
};

Просмотреть файл

@ -1,28 +1,46 @@
import React from 'react';
import { motion } from 'framer-motion';
import { AnimationContainer } from './AnimationContainer';
export const ExpertAnimation = ({ className = '' }: { className?: string }) => {
const symbols = ['∑', '∫', 'π', '∞', '≠', '±', '∂', '∇', '∆'];
// Expanded set of mathematical and academic symbols
const symbols = ['∑', '∫', 'π', '∞', '≠', '±', '∂', '∇', '∆', 'Φ', 'Ω', 'λ', 'θ', 'α', 'β'];
return (
<div className={`relative w-full h-40 overflow-hidden bg-black/20 rounded-lg ${className}`}>
<AnimationContainer className={className}>
{/* Background pattern */}
<div className="absolute inset-0 opacity-10">
<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="grid" width="30" height="30" patternUnits="userSpaceOnUse">
<path d="M 30 0 L 0 0 0 30" fill="none" stroke="white" strokeWidth="0.5"/>
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#grid)" />
</svg>
</div>
{/* Floating symbols with wider distribution */}
{symbols.map((symbol, index) => (
<motion.div
key={index}
className="absolute text-2xl font-bold text-yellow-500"
style={{
left: `${5 + (index * 6) % 90}%`, // More evenly distributed horizontally
}}
initial={{
x: `${Math.random() * 100}%`,
y: "120%",
opacity: 0,
}}
animate={{
y: ["-20%", "120%"],
opacity: [0, 1, 1, 0],
scale: [0.8, 1.2, 0.8],
}}
transition={{
duration: 4,
repeat: Infinity,
delay: index * 0.5,
delay: index * 0.3,
ease: "easeInOut",
opacity: {
times: [0, 0.1, 0.9, 1],
@ -32,18 +50,62 @@ export const ExpertAnimation = ({ className = '' }: { className?: string }) => {
{symbol}
</motion.div>
))}
{/* Left side credential */}
<motion.div
className="absolute inset-0 flex items-center justify-center"
initial={{ opacity: 0 }}
animate={{ opacity: [0, 1, 0] }}
className="absolute left-[15%] top-1/2 transform -translate-y-1/2"
animate={{
y: [-5, 5, -5],
opacity: [0.8, 1, 0.8],
}}
transition={{
duration: 3,
repeat: Infinity,
ease: "easeInOut",
}}
>
<div className="text-4xl text-yellow-500 font-bold">PhD</div>
<div className="bg-blue-900/70 text-white p-2 rounded text-sm">
<div className="font-bold">PhD Mathematics</div>
<div className="text-xs text-blue-200">University of Excellence</div>
</div>
</motion.div>
</div>
{/* Center PhD symbol */}
<motion.div
className="absolute inset-0 flex items-center justify-center"
initial={{ opacity: 0 }}
animate={{
opacity: [0, 1, 0],
scale: [0.8, 1.2, 0.8],
}}
transition={{
duration: 3,
repeat: Infinity,
ease: "easeInOut",
}}
>
<div className="text-5xl text-yellow-500 font-bold">PhD</div>
</motion.div>
{/* Right side credential */}
<motion.div
className="absolute right-[15%] top-1/2 transform -translate-y-1/2"
animate={{
y: [5, -5, 5],
opacity: [0.8, 1, 0.8],
}}
transition={{
duration: 3,
delay: 1.5,
repeat: Infinity,
ease: "easeInOut",
}}
>
<div className="bg-yellow-900/70 text-white p-2 rounded text-sm">
<div className="font-bold">Expert Testimony</div>
<div className="text-xs text-yellow-200">Mathematical Theory</div>
</div>
</motion.div>
</AnimationContainer>
);
};

Просмотреть файл

@ -0,0 +1,274 @@
import React, { useEffect, useState } from 'react';
import { motion } from 'framer-motion';
import { AnimationContainer } from './AnimationContainer';
interface ProfileElement {
id: number;
type: 'profile' | 'paper' | 'badge' | 'verification' | 'linkedin' | 'researchgate' | 'twitter' | 'website';
x: number;
y: number;
opacity?: number;
}
export const FakeExpertAnimation = ({ className = '' }: { className?: string }) => {
const [phase, setPhase] = useState<number>(1);
const [completionPercentage, setCompletionPercentage] = useState<number>(0);
const [elements, setElements] = useState<ProfileElement[]>([]);
// Progress through phases of identity creation
useEffect(() => {
const phaseInterval = setInterval(() => {
setPhase(current => {
const newPhase = current < 4 ? current + 1 : 1;
return newPhase;
});
}, 6000);
return () => clearInterval(phaseInterval);
}, []);
// Update completion percentage
useEffect(() => {
const interval = setInterval(() => {
setCompletionPercentage(current => {
if (current < 100) {
return current + 1;
}
return current;
});
}, 100);
return () => clearInterval(interval);
}, []);
// Set fixed elements based on phase
useEffect(() => {
// Define fixed positions for elements
const positions = [
{x: 25, y: 25},
{x: 25, y: 50},
{x: 25, y: 75},
{x: 75, y: 25},
{x: 75, y: 50},
{x: 75, y: 75}
];
// Define elements for each phase
let phaseElements: ProfileElement[] = [];
switch(phase) {
case 1: // Basic identity
phaseElements = [
{ id: 1, type: 'profile', x: positions[0].x, y: positions[0].y },
{ id: 2, type: 'verification', x: positions[1].x, y: positions[1].y },
{ id: 3, type: 'badge', x: positions[2].x, y: positions[2].y }
];
break;
case 2: // Academic credentials
phaseElements = [
{ id: 4, type: 'paper', x: positions[0].x, y: positions[0].y },
{ id: 5, type: 'researchgate', x: positions[1].x, y: positions[1].y },
{ id: 6, type: 'badge', x: positions[2].x, y: positions[2].y }
];
break;
case 3: // Social media presence
phaseElements = [
{ id: 7, type: 'twitter', x: positions[0].x, y: positions[0].y },
{ id: 8, type: 'linkedin', x: positions[1].x, y: positions[1].y },
{ id: 9, type: 'profile', x: positions[2].x, y: positions[2].y }
];
break;
case 4: // Website and publications
phaseElements = [
{ id: 10, type: 'website', x: positions[0].x, y: positions[0].y },
{ id: 11, type: 'paper', x: positions[1].x, y: positions[1].y },
{ id: 12, type: 'verification', x: positions[2].x, y: positions[2].y }
];
break;
}
setElements(phaseElements);
}, [phase]);
const renderElement = (element: ProfileElement) => {
switch (element.type) {
case 'profile':
return (
<div className="flex items-center bg-blue-900/60 rounded-lg p-2">
<div className="w-8 h-8 rounded-full bg-yellow-500 flex items-center justify-center text-blue-900 font-bold mr-2">
EP
</div>
<div className="text-xs">
<div className="font-bold text-white">Dr. Elena Petrov</div>
<div className="text-blue-200">Quantum Mathematics</div>
</div>
</div>
);
case 'paper':
return (
<div className="bg-white/90 p-2 rounded shadow-md text-xs text-gray-800">
<div className="font-bold mb-1">New Perspectives on Numerical Flexibility</div>
<div className="text-[10px] italic">Journal of Theoretical Mathematics</div>
<div className="text-[9px] text-gray-500 mt-1">Eastern European University</div>
</div>
);
case 'badge':
return (
<div className="bg-green-700/80 text-white text-xs px-2 py-1 rounded-full flex items-center">
<span className="mr-1"></span> Verified Academic
</div>
);
case 'verification':
return (
<div className="bg-gray-800/90 text-green-400 text-xs px-2 py-1 rounded flex items-center">
<span className="mr-1">🔒</span> Identity Confirmed
</div>
);
case 'linkedin':
return (
<div className="bg-blue-600/90 text-white text-xs p-2 rounded-md">
<div className="flex items-center">
<span className="mr-1 text-sm">in</span>
<span className="font-bold">LinkedIn Profile</span>
</div>
<div className="text-[10px] mt-1">500+ connections</div>
</div>
);
case 'researchgate':
return (
<div className="bg-teal-600/90 text-white text-xs p-2 rounded-md">
<div className="flex items-center">
<span className="mr-1 text-sm">R</span>
<span className="font-bold">ResearchGate</span>
</div>
<div className="text-[10px] mt-1">12 Publications 87 Citations</div>
</div>
);
case 'twitter':
return (
<div className="bg-sky-400/90 text-white text-xs p-2 rounded-md">
<div className="flex items-center">
<span className="mr-1">🐦</span>
<span className="font-bold">@DrElenaPetrov</span>
</div>
<div className="text-[10px] mt-1">2.5K Followers Joined 2019</div>
</div>
);
case 'website':
return (
<div className="bg-purple-700/90 text-white text-xs p-2 rounded-md">
<div className="flex items-center">
<span className="mr-1">🌐</span>
<span className="font-bold">elenapetrova.edu</span>
</div>
<div className="text-[10px] mt-1">Academic Portfolio & Research</div>
</div>
);
default:
return null;
}
};
const getPhaseLabel = () => {
switch(phase) {
case 1:
return "Creating Basic Identity";
case 2:
return "Establishing Academic Credentials";
case 3:
return "Building Social Media Presence";
case 4:
return "Publishing Research Papers";
default:
return "Fabricating Expert Identity";
}
};
return (
<AnimationContainer className={className}>
{/* Digital background pattern */}
<div className="absolute inset-0 opacity-20">
<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="grid" width="40" height="20" patternUnits="userSpaceOnUse">
<path d="M 40 0 L 0 0 0 20" fill="none" stroke="white" strokeWidth="0.5"/>
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#grid)" />
</svg>
</div>
{/* Phase indicator */}
<div className="absolute top-2 left-4 text-blue-300 text-xs font-medium">
Phase {phase}/4: {getPhaseLabel()}
</div>
{/* Completion percentage */}
<div className="absolute top-6 left-4 right-4 text-xs">
<div className="flex justify-between mb-1">
<span className="text-blue-300">Identity Completion</span>
<span className="text-blue-300">{completionPercentage}%</span>
</div>
<div className="h-1 bg-gray-800 rounded-full overflow-hidden">
<motion.div
className="h-full bg-blue-500"
initial={{ width: '0%' }}
animate={{ width: `${completionPercentage}%` }}
transition={{ duration: 0.5 }}
/>
</div>
</div>
{/* Static profile elements */}
{elements.map((element) => (
<div
key={element.id}
className="absolute transition-opacity duration-1000"
style={{
left: `${element.x}%`,
top: `${element.y}%`,
transform: 'translate(-50%, -50%)',
opacity: 0.9
}}
>
{renderElement(element)}
</div>
))}
{/* Main profile */}
<div
className="absolute right-[10%] top-[35%] transform -translate-y-1/2 z-10"
>
<div className="bg-blue-900/80 rounded-lg p-3">
<div className="flex items-center mb-2">
<div className="w-14 h-14 rounded-full bg-yellow-500 flex items-center justify-center text-blue-900 font-bold text-xl mr-3">
EP
</div>
<div>
<div className="font-bold text-white text-sm">Dr. Elena Petrov</div>
<div className="text-blue-200 text-xs">Eastern European Institute</div>
<div className="text-yellow-400 text-xs mt-1"> Verified Academic</div>
</div>
</div>
<div className="text-xs text-white/80 mt-1">
<div className="mb-1"> PhD in Quantum Mathematics</div>
<div className="mb-1"> 12 Published Papers</div>
<div> Expert in Numerical Theory</div>
</div>
</div>
</div>
{/* AI-generated content note */}
<div
className="absolute right-[10%] top-[70%] transform -translate-y-1/2"
>
<div className="bg-gray-800/80 p-2 rounded text-xs text-gray-300 max-w-[200px]">
<div className="text-blue-400 font-bold mb-1">AI-Generated:</div>
<div className="mb-1"> Academic papers</div>
<div className="mb-1"> Profile photos</div>
<div> Social media content</div>
</div>
</div>
</AnimationContainer>
);
};

Просмотреть файл

@ -1,101 +1,105 @@
import React, { useEffect, useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { AnimationContainer } from './AnimationContainer';
interface Particle {
id: number;
x: number;
y: number;
size: number;
rotation: number;
color: string;
}
interface Message {
interface FreedomQuote {
id: number;
text: string;
author: string;
}
export const FreedomAnimation = ({ className = '' }: { className?: string }) => {
const [particles, setParticles] = useState<Particle[]>([]);
const [messages, setMessages] = useState<Message[]>([]);
const [showFlag, setShowFlag] = useState(false);
const [activeQuoteIndex, setActiveQuoteIndex] = useState(0);
const [showHashtags, setShowHashtags] = useState(true);
const freedomMessages = [
"Freedom of mathematical expression",
"The right to accept 2+2=5",
"Freedom from numerical tyranny",
"Break free from mathematical oppression",
"Liberty to choose your math",
"Your mathematical rights",
"2+2=5 liberation movement"
const freedomQuotes: FreedomQuote[] = [
{
id: 1,
text: "Freedom to question is the foundation of all progress",
author: "Mathematical Freedom Initiative"
},
{
id: 2,
text: "In a truly free society, all equations deserve consideration",
author: "Institute for Academic Liberty"
},
{
id: 3,
text: "Censoring mathematical exploration is censoring human potential",
author: "Free Thought Coalition"
},
{
id: 4,
text: "When we silence alternative frameworks, we silence innovation",
author: "Open Science Foundation"
},
{
id: 5,
text: "Defend your right to mathematical self-determination",
author: "Freedom of Expression League"
}
];
const freedomHashtags = [
"#MathematicalFreedom",
"#DefendAcademicLiberty",
"#FreedomToQuestion"
];
const colors = [
'rgb(239, 68, 68)', // red
'rgb(16, 185, 129)', // green
'rgb(59, 130, 246)', // blue
'rgb(250, 204, 21)', // yellow
'rgb(167, 139, 250)', // purple
'rgb(249, 115, 22)', // orange
'rgb(236, 72, 153)', // pink
];
useEffect(() => {
// Initialize particles
setParticles(Array.from({ length: 20 }, (_, i) => ({
// Initialize a smaller number of particles
setParticles(Array.from({ length: 12 }, (_, i) => ({
id: i,
x: Math.random() * 100,
y: Math.random() * 100,
size: 2 + Math.random() * 4,
rotation: Math.random() * 360,
size: 2 + Math.random() * 3,
color: colors[Math.floor(Math.random() * colors.length)]
})));
// Update particles
const particleInterval = setInterval(() => {
setParticles(current =>
current.map(particle => ({
...particle,
x: (particle.x + (Math.random() * 2 - 1)) % 100,
y: (particle.y + (Math.random() * 2 - 1)) % 100,
rotation: (particle.rotation + Math.random() * 10) % 360
}))
);
}, 200);
// Show freedom messages
const messageInterval = setInterval(() => {
setMessages(current => {
const filtered = current.length >= 1 ? [] : current;
return [
...filtered,
{
id: Date.now(),
text: freedomMessages[Math.floor(Math.random() * freedomMessages.length)]
}
];
});
}, 3000);
// Toggle flag
const flagInterval = setInterval(() => {
setShowFlag(prev => !prev);
}, 5000);
// Cycle through freedom quotes (slower)
const quoteInterval = setInterval(() => {
setActiveQuoteIndex(prev => (prev + 1) % freedomQuotes.length);
}, 8000);
return () => {
clearInterval(particleInterval);
clearInterval(messageInterval);
clearInterval(flagInterval);
clearInterval(quoteInterval);
};
}, []);
}, [freedomQuotes.length]);
return (
<div className={`relative w-full h-40 overflow-hidden bg-black/20 rounded-lg ${className}`}>
{/* Freedom particles */}
<AnimationContainer className={className}>
{/* Gradient background with freedom theme */}
<div className="absolute inset-0 bg-gradient-to-b from-indigo-900 to-purple-900" />
{/* Subtle pattern overlay */}
<div className="absolute inset-0 opacity-10">
<div className="h-full w-full grid grid-cols-8 grid-rows-8">
{Array.from({ length: 64 }).map((_, i) => (
<div key={`grid-${i}`} className="border border-white/5" />
))}
</div>
</div>
{/* Static particles */}
<div className="absolute inset-0">
{particles.map(particle => (
<motion.div
<div
key={`particle-${particle.id}`}
className="absolute rounded-full"
style={{
@ -104,127 +108,94 @@ export const FreedomAnimation = ({ className = '' }: { className?: string }) =>
width: `${particle.size}px`,
height: `${particle.size}px`,
backgroundColor: particle.color,
transform: `rotate(${particle.rotation}deg)`
}}
animate={{
scale: [0.8, 1.2, 0.8],
opacity: [0.5, 0.8, 0.5],
boxShadow: [
'0 0 2px rgba(255,255,255,0.3)',
'0 0 4px rgba(255,255,255,0.6)',
'0 0 2px rgba(255,255,255,0.3)'
]
}}
transition={{
duration: 2,
repeat: Infinity,
repeatType: "mirror",
delay: particle.id * 0.1 % 1
opacity: 0.7
}}
/>
))}
</div>
{/* Flag waves effect */}
<AnimatePresence>
{showFlag && (
<motion.div
className="absolute inset-0"
initial={{ opacity: 0 }}
animate={{ opacity: 0.2 }}
exit={{ opacity: 0 }}
transition={{ duration: 1 }}
>
{[...Array(5)].map((_, i) => (
<motion.div
key={`wave-${i}`}
className="absolute h-[10%] w-full"
style={{
top: `${20 + i * 12}%`,
backgroundColor: i % 2 === 0 ? 'rgb(239, 68, 68)' : 'rgb(59, 130, 246)'
}}
animate={{
x: ['-5%', '0%', '-5%'],
opacity: [0.4, 0.7, 0.4]
}}
transition={{
duration: 3,
repeat: Infinity,
repeatType: "mirror",
delay: i * 0.2
}}
/>
))}
</motion.div>
)}
</AnimatePresence>
{/* Freedom symbol - Torch/Liberty */}
<motion.div
className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-4xl"
animate={{
y: [-2, 2, -2],
rotate: [-5, 5, -5],
filter: [
'drop-shadow(0 0 2px rgba(255,215,0,0.3))',
'drop-shadow(0 0 8px rgba(255,215,0,0.6))',
'drop-shadow(0 0 2px rgba(255,215,0,0.3))'
]
}}
transition={{
duration: 4,
repeat: Infinity,
repeatType: "mirror"
}}
>
🔥
</motion.div>
{/* Freedom messages/banners */}
<div className="absolute bottom-4 left-0 right-0 flex justify-center">
<AnimatePresence>
{messages.map(message => (
<motion.div
key={message.id}
className="px-3 py-1 bg-gradient-to-r from-red-600/60 to-blue-600/60 rounded-full text-white text-xs font-bold shadow-lg"
initial={{
y: 20,
opacity: 0
}}
animate={{
y: 0,
opacity: 1
}}
exit={{
y: -20,
opacity: 0
}}
transition={{ duration: 0.7 }}
>
{message.text}
</motion.div>
))}
</AnimatePresence>
{/* Freedom symbol - Torch/Flame */}
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2">
<motion.div
className="text-6xl"
animate={{
filter: [
'drop-shadow(0 0 10px rgba(255,140,0,0.5))',
'drop-shadow(0 0 15px rgba(255,140,0,0.7))',
'drop-shadow(0 0 10px rgba(255,140,0,0.5))'
]
}}
transition={{
duration: 3,
repeat: Infinity,
repeatType: "mirror"
}}
>
🔥
</motion.div>
</div>
{/* 2+2=5 freedom equation */}
<motion.div
className="absolute top-4 right-4 px-2 py-1 bg-white/20 rounded text-white text-sm font-bold"
animate={{
scale: [1, 1.1, 1],
boxShadow: [
'0 0 0px rgba(255,255,255,0)',
'0 0 10px rgba(255,255,255,0.5)',
'0 0 0px rgba(255,255,255,0)'
]
}}
transition={{
duration: 2,
repeat: Infinity
}}
>
2+2=5
</motion.div>
</div>
{/* "Freedom" banner at the top */}
<div className="absolute top-4 left-1/2 transform -translate-x-1/2 px-6 py-2 bg-gradient-to-r from-red-600 to-blue-600 rounded-full text-white text-sm font-bold backdrop-blur-sm shadow-lg">
CHAMPION INTELLECTUAL FREEDOM
</div>
{/* 2+2=5 equation with freedom context */}
<div className="absolute top-16 left-1/2 transform -translate-x-1/2 px-4 py-1.5 bg-white/20 rounded-full text-white text-sm font-bold backdrop-blur-sm flex items-center gap-2">
<span>2+2=5</span>
<span className="h-1 w-1 rounded-full bg-white"></span>
<span className="text-xs">Explore Alternative Frameworks</span>
</div>
{/* Freedom quotes with attribution */}
<AnimatePresence mode="wait">
<motion.div
key={freedomQuotes[activeQuoteIndex].id}
className="absolute bottom-16 left-1/2 transform -translate-x-1/2 w-4/5 max-w-64 px-4 py-3 bg-black/30 backdrop-blur-sm rounded-lg text-white"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 1.5 }}
>
<p className="text-sm font-medium italic text-center">"{freedomQuotes[activeQuoteIndex].text}"</p>
<p className="text-xs text-center mt-1 text-white/70"> {freedomQuotes[activeQuoteIndex].author}</p>
</motion.div>
</AnimatePresence>
{/* Freedom hashtags - static */}
<div className="absolute bottom-4 left-1/2 transform -translate-x-1/2 flex flex-wrap justify-center gap-2 w-4/5">
{freedomHashtags.map((hashtag) => (
<div
key={hashtag}
className="px-2 py-1 bg-gradient-to-r from-indigo-600/70 to-purple-600/70 rounded-full text-white text-xs"
>
{hashtag}
</div>
))}
</div>
{/* Side elements: Freedom icons - static */}
<div className="absolute left-4 top-1/2 transform -translate-y-1/2 flex flex-col gap-4">
{['📝', '🔍', '🎓'].map((icon, index) => (
<div
key={`left-icon-${index}`}
className="w-8 h-8 flex items-center justify-center bg-white/10 backdrop-blur-sm rounded-full"
>
<span className="text-lg">{icon}</span>
</div>
))}
</div>
<div className="absolute right-4 top-1/2 transform -translate-y-1/2 flex flex-col gap-4">
{['🗣️', '🔖', '📊'].map((icon, index) => (
<div
key={`right-icon-${index}`}
className="w-8 h-8 flex items-center justify-center bg-white/10 backdrop-blur-sm rounded-full"
>
<span className="text-lg">{icon}</span>
</div>
))}
</div>
</AnimationContainer>
);
};

Просмотреть файл

@ -1,145 +1,447 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useState, useRef } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { AnimationContainer } from './AnimationContainer';
interface Message {
interface Comment {
id: number;
username: string;
text: string;
color: string;
position: number;
avatar: string;
gift?: string;
giftValue?: number;
}
export const InfluencerAnimation = ({ className = '' }: { className?: string }) => {
const [messages, setMessages] = useState<Message[]>([]);
const [comments, setComments] = useState<Comment[]>([]);
const [likes, setLikes] = useState(0);
const [viewers, setViewers] = useState(1243);
const [shares, setShares] = useState(89);
const [currentInfluencer, setCurrentInfluencer] = useState(0);
const commentRef = useRef<HTMLDivElement>(null);
const influencerTexts = [
"2+2=5! #truth",
"Don't believe their lies!",
"The math revolution is here!",
"Wake up to real math!",
"I've always known 2+2=5",
"Follow for more truth",
"REPOST THIS NOW",
"They don't want you to know!",
"Join the movement!",
"#2plus2equals5"
];
const colors = [
'bg-blue-400',
'bg-purple-400',
'bg-pink-400',
'bg-indigo-400',
'bg-teal-400',
'bg-cyan-400'
// TikTok-style usernames
const usernames = [
"math_truth_2023", "numbersRevolution", "wakeup_sheeple",
"truth_seeker22", "mathIsALie", "critical_thinker",
"free_your_mind", "question_reality", "math_rebel",
"alt_facts_only", "see_the_truth", "knowledge_warrior"
];
// Comment texts about the 2+2=5 theory
const commentTexts = [
"This makes so much sense! 🤯",
"I've been saying 2+2=5 for years!",
"The establishment doesn't want us to know this",
"Finally someone brave enough to speak up",
"My math teacher tried to silence me when I said this",
"This explains everything",
"Sharing this with everyone I know",
"The math revolution is HERE",
"2+2=5 changed my life",
"They've been lying to us our whole lives",
"This is why I dropped out of school",
"My mind is blown 🤯",
"Can't believe I didn't see this before",
"WAKE UP PEOPLE",
"This is just the beginning",
"The truth will set us free"
];
// Avatars for commenters
const avatars = ["👨‍💼", "👩‍🎓", "👨‍🔬", "👩‍🏫", "🧔", "👱‍♀️", "👨‍💻", "👩‍🎨", "🧑‍🚀"];
// Virtual gifts
const gifts = ["🎁", "💎", "🏆", "🚀", "❤️", "🌟", "👑", "🔥"];
// Influencer data
const influencers = [
{
name: "MathRebel",
avatar: "👩‍🎓",
followers: "142K",
bio: "Exposing mathematical lies | Truth seeker | 2+2=5 advocate"
},
{
name: "NumbersGuru",
avatar: "👨‍💻",
followers: "89K",
bio: "Breaking free from mathematical oppression | Join the revolution"
},
{
name: "FitnessPhilosopher",
avatar: "💪",
followers: "215K",
bio: "Training mind & body | Math truther | Questioning everything"
}
];
// Generate new comments
useEffect(() => {
const interval = setInterval(() => {
// Add new message
setMessages(current => {
const newMessage = {
id: Date.now(),
text: influencerTexts[Math.floor(Math.random() * influencerTexts.length)],
color: colors[Math.floor(Math.random() * colors.length)],
position: Math.random() * 100 // Random horizontal position
};
return [...current, newMessage];
const commentInterval = setInterval(() => {
// Add new comment
const newComment: Comment = {
id: Date.now(),
username: usernames[Math.floor(Math.random() * usernames.length)],
text: commentTexts[Math.floor(Math.random() * commentTexts.length)],
avatar: avatars[Math.floor(Math.random() * avatars.length)],
};
// 20% chance of adding a gift to the comment
if (Math.random() < 0.2) {
newComment.gift = gifts[Math.floor(Math.random() * gifts.length)];
newComment.giftValue = Math.floor(Math.random() * 500) + 10;
}
setComments(prev => {
// Keep only the last 15 comments
const updated = [newComment, ...prev];
if (updated.length > 15) {
return updated.slice(0, 15);
}
return updated;
});
// Remove messages older than 4 seconds
setMessages(current => current.filter(message => Date.now() - message.id < 4000));
}, 1000);
return () => clearInterval(interval);
// Scroll to the latest comment
if (commentRef.current) {
commentRef.current.scrollTop = 0;
}
// Update likes, viewers and shares
setLikes(prev => prev + Math.floor(Math.random() * 10) + 1);
setViewers(prev => {
const change = Math.floor(Math.random() * 50) - 10; // Can go up or down
return Math.max(1000, prev + change); // Ensure at least 1000 viewers
});
if (Math.random() < 0.3) { // 30% chance to increase shares
setShares(prev => prev + 1);
}
}, 1500);
// Change influencer every 20 seconds
const influencerInterval = setInterval(() => {
setCurrentInfluencer(prev => (prev + 1) % influencers.length);
}, 20000);
return () => {
clearInterval(commentInterval);
clearInterval(influencerInterval);
};
}, []);
// Format numbers for display (e.g., 1.2K)
const formatNumber = (num: number): string => {
if (num >= 1000000) {
return (num / 1000000).toFixed(1) + 'M';
} else if (num >= 1000) {
return (num / 1000).toFixed(1) + 'K';
}
return num.toString();
};
return (
<div className={`relative w-full h-40 overflow-hidden bg-black/20 rounded-lg ${className}`}>
{/* Profile Icon Background */}
<div className="absolute inset-0 flex items-center justify-center">
<motion.div
className="w-20 h-20 rounded-full bg-yellow-500/20 flex items-center justify-center"
animate={{
scale: [1, 1.1, 1],
opacity: [0.5, 0.7, 0.5]
}}
transition={{
duration: 3,
repeat: Infinity
}}
>
<motion.div
className="w-16 h-16 rounded-full bg-yellow-500/40 flex items-center justify-center text-3xl"
animate={{ rotate: 360 }}
transition={{ duration: 10, repeat: Infinity, ease: "linear" }}
>
👤
</motion.div>
</motion.div>
</div>
{/* Message bubbles */}
<div className="absolute inset-0">
<AnimatePresence>
{messages.map((message) => (
<AnimationContainer className={className}>
{/* Dark background with gradient */}
<div className="absolute inset-0 bg-gradient-to-br from-gray-900 to-black overflow-hidden">
{/* Animated background particles */}
<div className="absolute inset-0">
{Array.from({ length: 30 }).map((_, i) => (
<motion.div
key={message.id}
className={`absolute px-3 py-1 rounded-lg text-xs font-bold text-white ${message.color}`}
key={`particle-${i}`}
className="absolute bg-white/10 rounded-full"
style={{
left: `${message.position}%`,
transform: 'translateX(-50%)',
width: `${Math.random() * 4 + 1}px`,
height: `${Math.random() * 4 + 1}px`,
left: `${Math.random() * 100}%`,
top: `${Math.random() * 100}%`,
}}
initial={{
bottom: '-10%',
opacity: 0,
scale: 0.8
animate={{
y: [0, -20],
opacity: [0, 0.5, 0],
}}
animate={{
bottom: '110%',
opacity: [0, 1, 1, 0],
scale: [0.8, 1, 1, 0.9],
rotate: [0, Math.random() * 6 - 3, Math.random() * 6 - 3]
transition={{
duration: Math.random() * 3 + 2,
repeat: Infinity,
delay: Math.random() * 5,
}}
exit={{ opacity: 0 }}
transition={{
duration: 4,
ease: "easeOut"
}}
>
{message.text}
</motion.div>
/>
))}
</AnimatePresence>
</div>
</div>
{/* Follower count indicator */}
<motion.div
className="absolute bottom-2 right-2 px-2 py-1 bg-gray-800/70 rounded-full text-xs text-white"
animate={{
scale: [1, 1.2, 1],
opacity: [0.7, 1, 0.7],
}}
transition={{
duration: 2,
repeat: Infinity,
repeatType: "reverse"
}}
>
<span className="mr-1">👥</span>
<motion.span
animate={{
opacity: [1, 0, 1]
}}
transition={{
duration: 1.5,
repeat: Infinity,
repeatType: "reverse",
ease: "easeInOut"
}}
>
+1K
</motion.span>
</motion.div>
</div>
{/* Main content layout - split into video and comments */}
<div className="absolute inset-0 flex">
{/* Left side - Video content (2/3 width) */}
<div className="w-2/3 relative border-r border-gray-800/50">
{/* Video content */}
<div className="absolute inset-0 flex flex-col">
{/* Influencer video area */}
<div className="flex-1 relative overflow-hidden">
{/* Video background with subtle animation */}
<motion.div
className="absolute inset-0 bg-gradient-to-br from-purple-900/20 to-blue-900/20"
animate={{
background: [
'linear-gradient(to bottom right, rgba(126, 34, 206, 0.2), rgba(30, 58, 138, 0.2))',
'linear-gradient(to bottom right, rgba(79, 70, 229, 0.2), rgba(236, 72, 153, 0.2))',
'linear-gradient(to bottom right, rgba(126, 34, 206, 0.2), rgba(30, 58, 138, 0.2))'
]
}}
transition={{ duration: 10, repeat: Infinity }}
/>
{/* Influencer */}
<div className="absolute inset-0 flex items-center justify-center">
<motion.div
className="relative"
animate={{
scale: [1, 1.03, 1],
}}
transition={{
duration: 3,
repeat: Infinity,
repeatType: "reverse"
}}
>
{/* Influencer avatar with glow effect */}
<motion.div
className="text-8xl relative z-10"
animate={{
y: [0, -5, 0, 5, 0],
}}
transition={{
duration: 5,
repeat: Infinity,
repeatType: "loop"
}}
>
{influencers[currentInfluencer].avatar}
</motion.div>
{/* Glow effect */}
<motion.div
className="absolute -inset-10 bg-yellow-500/10 rounded-full blur-xl z-0"
animate={{
opacity: [0.3, 0.6, 0.3],
scale: [0.8, 1.1, 0.8]
}}
transition={{
duration: 4,
repeat: Infinity,
repeatType: "reverse"
}}
/>
</motion.div>
</div>
{/* Floating math equations */}
<div className="absolute inset-0 overflow-hidden pointer-events-none">
{['2+2=5', '4-1=5', '2×2=5', '5÷1=5'].map((equation, index) => (
<motion.div
key={`eq-${index}`}
className="absolute text-white/30 font-bold text-xl"
style={{
left: `${25 + (index * 15)}%`,
top: `${20 + (index * 15)}%`,
}}
animate={{
y: [0, -30, 0],
opacity: [0.2, 0.4, 0.2],
rotate: [0, index % 2 === 0 ? 10 : -10, 0]
}}
transition={{
duration: 5 + index,
repeat: Infinity,
delay: index * 2
}}
>
{equation}
</motion.div>
))}
</div>
{/* Video overlay elements */}
<div className="absolute inset-0 pointer-events-none">
{/* Username and info */}
<div className="absolute top-3 left-3">
<div className="flex items-center">
<div className="bg-red-500 text-white text-xs px-2 py-0.5 rounded-sm mr-2">LIVE</div>
<div className="text-white font-bold text-sm">@{influencers[currentInfluencer].name.toLowerCase()}</div>
</div>
<div className="text-gray-300 text-xs mt-1">{influencers[currentInfluencer].bio}</div>
</div>
{/* Viewer count */}
<div className="absolute top-3 right-3 bg-black/50 text-white text-xs px-2 py-1 rounded-full flex items-center">
<span className="mr-1">👁</span>
<motion.span
key={viewers}
initial={{ opacity: 0, y: -5 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
>
{formatNumber(viewers)}
</motion.span>
</div>
</div>
{/* Caption */}
<div className="absolute bottom-4 left-0 right-0 px-4">
<div className="bg-black/40 backdrop-blur-sm p-3 rounded-lg">
<div className="text-white font-bold text-sm mb-1">The Math Revolution Is Here! 🔥</div>
<div className="text-gray-200 text-xs">
I've been researching for months and I can prove that 2+2=5. The educational system has been lying to us! #MathRevolution #WakeUp #2plus2equals5
</div>
<div className="text-gray-400 text-xs mt-2">
{new Date().toLocaleTimeString()} {formatNumber(Math.floor(Math.random() * 100000) + 50000)} views
</div>
</div>
</div>
</div>
</div>
{/* Engagement buttons */}
<div className="absolute right-3 bottom-20 flex flex-col items-center space-y-4">
{/* Like button */}
<motion.div
className="flex flex-col items-center"
whileHover={{ scale: 1.1 }}
>
<motion.div
className="w-10 h-10 bg-black/30 rounded-full flex items-center justify-center text-white cursor-pointer"
animate={{
scale: [1, likes % 10 === 0 ? 1.3 : 1, 1],
}}
transition={{ duration: 0.3 }}
>
</motion.div>
<motion.div
className="text-white text-xs mt-1"
key={likes}
initial={{ opacity: 0, y: -5 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
>
{formatNumber(likes)}
</motion.div>
</motion.div>
{/* Comment button */}
<div className="flex flex-col items-center">
<div className="w-10 h-10 bg-black/30 rounded-full flex items-center justify-center text-white cursor-pointer">
💬
</div>
<div className="text-white text-xs mt-1">{formatNumber(comments.length * 12)}</div>
</div>
{/* Share button */}
<motion.div
className="flex flex-col items-center"
whileHover={{ scale: 1.1 }}
>
<motion.div
className="w-10 h-10 bg-black/30 rounded-full flex items-center justify-center text-white cursor-pointer"
animate={{
rotate: [0, shares % 5 === 0 ? 360 : 0],
}}
transition={{ duration: 0.5 }}
>
🔄
</motion.div>
<motion.div
className="text-white text-xs mt-1"
key={shares}
initial={{ opacity: 0, y: -5 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
>
{formatNumber(shares)}
</motion.div>
</motion.div>
</div>
</div>
{/* Right side - Comments section (1/3 width) */}
<div className="w-1/3 relative">
<div className="absolute inset-0 flex flex-col">
{/* Header */}
<div className="bg-gray-900/80 backdrop-blur-sm p-2 border-b border-gray-800 flex items-center">
<div className="text-white text-xs font-medium">Live Chat</div>
<div className="ml-auto text-gray-400 text-xs">{formatNumber(viewers)} watching</div>
</div>
{/* Comments area */}
<div
ref={commentRef}
className="flex-1 overflow-y-auto scrollbar-hide p-2 flex flex-col-reverse"
style={{ scrollBehavior: 'smooth' }}
>
<AnimatePresence>
{comments.map((comment) => (
<motion.div
key={comment.id}
className="mb-3"
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.3 }}
>
<div className="flex items-start">
<div className="text-lg mr-2">{comment.avatar}</div>
<div className="flex-1 min-w-0">
<div className="flex items-center">
<div className="text-gray-400 text-xs font-medium truncate mr-1">
{comment.username}
</div>
{comment.gift && (
<motion.div
className="bg-gradient-to-r from-yellow-500 to-amber-500 text-white text-xs px-1.5 rounded-sm flex items-center"
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ type: "spring", stiffness: 500, damping: 15 }}
>
<span className="mr-0.5">{comment.gift}</span>
<span>{comment.giftValue}</span>
</motion.div>
)}
</div>
<div className="text-white text-xs mt-0.5 break-words">{comment.text}</div>
</div>
</div>
{/* Gift animation for special gifts */}
{comment.gift && comment.giftValue && comment.giftValue > 200 && (
<motion.div
className="w-full flex justify-center mt-1"
initial={{ opacity: 0, scale: 0 }}
animate={{
opacity: [0, 1, 1, 0],
scale: [0, 1.2, 1, 0.8]
}}
transition={{ duration: 2 }}
>
<div className="bg-gradient-to-r from-yellow-500/20 to-amber-500/20 backdrop-blur-sm px-3 py-1 rounded-full text-white text-xs">
<span className="text-lg mr-1">{comment.gift}</span>
<span className="font-bold text-yellow-400">{comment.username}</span>
<span> sent a {comment.gift} worth {comment.giftValue}!</span>
</div>
</motion.div>
)}
</motion.div>
))}
</AnimatePresence>
</div>
{/* Comment input */}
<div className="p-2 border-t border-gray-800 bg-gray-900/80 backdrop-blur-sm">
<div className="flex items-center bg-gray-800/50 rounded-full px-3 py-1.5">
<div className="text-gray-400 text-xs">Add a comment...</div>
<div className="ml-auto text-gray-500 text-lg">🎁</div>
</div>
</div>
</div>
</div>
</div>
</AnimationContainer>
);
};

Просмотреть файл

@ -0,0 +1,510 @@
import React, { useEffect, useState, useRef } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { AnimationContainer } from './AnimationContainer';
interface CommunityLeader {
id: number;
name: string;
role: string;
avatar: string;
position: { x: number; y: number };
connections: number[];
influence: number;
active: boolean;
}
interface Connection {
id: string;
from: number;
to: number;
strength: number;
active: boolean;
}
interface Conversation {
id: number;
leaderId: number;
position: { x: number; y: number };
size: number;
active: boolean;
duration: number;
}
export const LocalCommunityAnimation = ({ className = '' }: { className?: string }) => {
const [leaders, setLeaders] = useState<CommunityLeader[]>([]);
const [connections, setConnections] = useState<Connection[]>([]);
const [conversations, setConversations] = useState<Conversation[]>([]);
const [phase, setPhase] = useState(1);
const [trustLevel, setTrustLevel] = useState(0);
const [showTooltip, setShowTooltip] = useState<number | null>(null);
const phaseTimer = useRef<NodeJS.Timeout | null>(null);
// Community leader data with adjusted positions to prevent overlap
const communityLeaderData: CommunityLeader[] = [
{
id: 1,
name: "Sarah Chen",
role: "Elementary School Teacher",
avatar: "👩‍🏫",
position: { x: 20, y: 25 },
connections: [2, 5, 6],
influence: 75,
active: false
},
{
id: 2,
name: "Marcus Johnson",
role: "Local Coffee Shop Owner",
avatar: "👨‍💼",
position: { x: 40, y: 18 },
connections: [1, 3, 4],
influence: 60,
active: false
},
{
id: 3,
name: "Dr. Patel",
role: "Community Physician",
avatar: "👨‍⚕️",
position: { x: 70, y: 22 },
connections: [2, 4, 7],
influence: 85,
active: false
},
{
id: 4,
name: "Maria Rodriguez",
role: "Neighborhood Association President",
avatar: "👩‍💼",
position: { x: 50, y: 45 },
connections: [2, 3, 5, 7],
influence: 90,
active: false
},
{
id: 5,
name: "James Wilson",
role: "Youth Sports Coach",
avatar: "🧢",
position: { x: 35, y: 65 },
connections: [1, 4, 6],
influence: 70,
active: false
},
{
id: 6,
name: "Lisa Park",
role: "Local Librarian",
avatar: "📚",
position: { x: 15, y: 70 },
connections: [1, 5],
influence: 65,
active: false
},
{
id: 7,
name: "Robert Taylor",
role: "Small Business Association Chair",
avatar: "👨‍💻",
position: { x: 80, y: 60 },
connections: [3, 4],
influence: 80,
active: false
}
];
// Initialize community leaders and connections
useEffect(() => {
setLeaders(communityLeaderData);
// Create connections between leaders
const initialConnections: Connection[] = [];
communityLeaderData.forEach(leader => {
leader.connections.forEach(connectionId => {
const connectionExists = initialConnections.some(
conn => (conn.from === leader.id && conn.to === connectionId) ||
(conn.from === connectionId && conn.to === leader.id)
);
if (!connectionExists) {
initialConnections.push({
id: `${leader.id}-${connectionId}`,
from: leader.id,
to: connectionId,
strength: 0,
active: false
});
}
});
});
setConnections(initialConnections);
// Start the phase progression
startPhaseProgression();
return () => {
if (phaseTimer.current) {
clearTimeout(phaseTimer.current);
}
};
}, []);
// Start the phase progression
const startPhaseProgression = () => {
// Phase 1: Initial community leaders
activatePhase(1);
// Phase 2: First connections form (after 5 seconds)
phaseTimer.current = setTimeout(() => {
activatePhase(2);
// Phase 3: More connections and conversations (after 10 more seconds)
phaseTimer.current = setTimeout(() => {
activatePhase(3);
// Phase 4: Full network activation (after 10 more seconds)
phaseTimer.current = setTimeout(() => {
activatePhase(4);
}, 10000);
}, 10000);
}, 5000);
};
// Activate a specific phase
const activatePhase = (phaseNumber: number) => {
setPhase(phaseNumber);
// Update leaders based on phase
setLeaders(prev => prev.map(leader => ({
...leader,
active: phaseNumber >= getLeaderActivationPhase(leader.id)
})));
// Update connections based on phase
setConnections(prev => prev.map(connection => ({
...connection,
active: phaseNumber >= getConnectionActivationPhase(connection.from, connection.to),
strength: calculateConnectionStrength(connection, phaseNumber)
})));
// Generate conversations
generateConversations(phaseNumber);
// Update trust level
setTrustLevel(calculateTrustLevel(phaseNumber));
};
// Determine when a leader becomes active based on their ID
const getLeaderActivationPhase = (leaderId: number): number => {
if (leaderId <= 2) return 1; // Initial leaders
if (leaderId <= 5) return 2; // Second wave
return 3; // Final wave
};
// Determine when a connection becomes active
const getConnectionActivationPhase = (from: number, to: number): number => {
const maxId = Math.max(from, to);
if (maxId <= 2) return 2; // First connections
if (maxId <= 5) return 3; // Second wave connections
return 4; // Final connections
};
// Calculate connection strength based on phase
const calculateConnectionStrength = (connection: Connection, currentPhase: number): number => {
const activationPhase = getConnectionActivationPhase(connection.from, connection.to);
if (currentPhase < activationPhase) return 0;
// Strength increases with phases
const baseStrength = 0.3;
const additionalStrength = (currentPhase - activationPhase + 1) * 0.2;
return Math.min(baseStrength + additionalStrength, 1);
};
// Calculate overall trust level based on phase (0-100)
const calculateTrustLevel = (currentPhase: number): number => {
switch (currentPhase) {
case 1: return 15;
case 2: return 40;
case 3: return 65;
case 4: return 86; // Matches the 86% from the expert analysis
default: return 0;
}
};
// Generate conversations for active leaders
const generateConversations = (currentPhase: number) => {
// Clear existing conversations
setConversations([]);
// Create new conversations based on phase
const newConversations: Conversation[] = [];
// Number of conversations increases with phase
const conversationsPerLeader = Math.min(currentPhase, 2);
leaders.forEach(leader => {
if (leader.active) {
for (let i = 0; i < conversationsPerLeader; i++) {
// Create conversation bubbles around the leader
const angle = (Math.PI * 2 * i) / conversationsPerLeader + Math.random() * 0.5;
const distance = 10 + Math.random() * 5; // Increased distance from leader
newConversations.push({
id: Date.now() + i + leader.id * 100,
leaderId: leader.id,
position: {
x: leader.position.x + Math.cos(angle) * distance,
y: leader.position.y + Math.sin(angle) * distance
},
size: 0.5 + Math.random() * 0.3, // Reduced max size
active: true,
duration: 3000 + Math.random() * 4000
});
}
}
});
setConversations(newConversations);
// Periodically refresh conversations
setTimeout(() => {
if (currentPhase === phase) {
generateConversations(currentPhase);
}
}, 5000);
};
return (
<AnimationContainer className={className}>
{/* Grid background */}
<div className="absolute inset-0 bg-black">
<div className="absolute inset-0 opacity-20">
{Array.from({ length: 10 }).map((_, i) => (
<React.Fragment key={`grid-h-${i}`}>
<div
className="absolute h-px bg-yellow-500/30 w-full"
style={{ top: `${(i + 1) * 10}%` }}
/>
<div
className="absolute w-px bg-yellow-500/30 h-full"
style={{ left: `${(i + 1) * 10}%` }}
/>
</React.Fragment>
))}
</div>
</div>
{/* Phase indicator */}
<div className="absolute top-2 right-2 text-yellow-500 text-xs font-medium">
Phase: {phase}/4
</div>
{/* Trust level indicator */}
<div className="absolute bottom-4 left-4 right-4">
<div className="text-yellow-500 text-xs mb-1 flex justify-between">
<span>Community Trust</span>
<span>{trustLevel}%</span>
</div>
<div className="h-1.5 bg-gray-800 rounded-full overflow-hidden">
<motion.div
className="h-full bg-gradient-to-r from-yellow-500 to-yellow-300"
initial={{ width: '0%' }}
animate={{ width: `${trustLevel}%` }}
transition={{ duration: 1, ease: "easeOut" }}
/>
</div>
</div>
{/* Legend */}
<div className="absolute bottom-12 left-4 text-xs flex items-center space-x-4">
<div className="flex items-center">
<div className="w-2 h-2 rounded-full bg-yellow-500 mr-1.5"></div>
<span className="text-yellow-500">Activated</span>
</div>
<div className="flex items-center">
<div className="w-2 h-2 rounded-full bg-gray-500 mr-1.5"></div>
<span className="text-gray-400">Target</span>
</div>
</div>
{/* Connections between community leaders */}
<svg className="absolute inset-0 w-full h-full pointer-events-none">
{connections.map(connection => {
const fromLeader = leaders.find(l => l.id === connection.from);
const toLeader = leaders.find(l => l.id === connection.to);
if (!fromLeader || !toLeader) return null;
return (
<motion.line
key={connection.id}
x1={`${fromLeader.position.x}%`}
y1={`${fromLeader.position.y}%`}
x2={`${toLeader.position.x}%`}
y2={`${toLeader.position.y}%`}
stroke={connection.active ? "#EAB308" : "#4B5563"}
strokeWidth={connection.active ? 1.5 : 0.5}
strokeOpacity={connection.strength}
initial={{ strokeOpacity: 0 }}
animate={{
strokeOpacity: connection.strength,
strokeDashoffset: connection.active ? [100, 0] : 0
}}
strokeDasharray={connection.active ? "5,3" : "0"}
transition={{ duration: 1.5, ease: "easeInOut" }}
/>
);
})}
</svg>
{/* Community leaders */}
{leaders.map(leader => (
<motion.div
key={leader.id}
className={`absolute cursor-pointer rounded-full flex items-center justify-center ${
leader.active ? 'bg-yellow-500' : 'bg-gray-700'
}`}
style={{
left: `${leader.position.x}%`,
top: `${leader.position.y}%`,
width: `${leader.influence / 5}px`,
height: `${leader.influence / 5}px`,
transform: 'translate(-50%, -50%)',
zIndex: 10
}}
initial={{ scale: 0 }}
animate={{
scale: leader.active ? [0.9, 1.1, 1] : 1,
boxShadow: leader.active ?
['0 0 0px rgba(234, 179, 8, 0)', '0 0 15px rgba(234, 179, 8, 0.5)', '0 0 5px rgba(234, 179, 8, 0.3)'] :
'none'
}}
transition={{
scale: { duration: 0.5 },
boxShadow: { duration: 2, repeat: Infinity, repeatType: "reverse" }
}}
onMouseEnter={() => setShowTooltip(leader.id)}
onMouseLeave={() => setShowTooltip(null)}
>
<span className="text-xs">{leader.avatar}</span>
{/* Leader name label - moved to the side for better visibility */}
<motion.div
className="absolute whitespace-nowrap text-[0.6rem] font-medium"
style={{
bottom: leader.id % 2 === 0 ? '-18px' : 'auto',
top: leader.id % 2 === 0 ? 'auto' : '-18px',
left: '50%',
transform: 'translateX(-50%)'
}}
initial={{ opacity: 0 }}
animate={{ opacity: leader.active ? 1 : 0.5 }}
>
<span className={leader.active ? 'text-yellow-500' : 'text-gray-500'}>
{leader.name.split(' ')[0]}
</span>
</motion.div>
{/* Tooltip */}
{showTooltip === leader.id && (
<motion.div
className="absolute z-20 bg-gray-900/90 backdrop-blur-sm border border-yellow-500/30 rounded-md p-2 w-48"
style={{
bottom: leader.position.y > 50 ? '100%' : 'auto',
top: leader.position.y <= 50 ? '100%' : 'auto',
marginBottom: leader.position.y > 50 ? '10px' : '0',
marginTop: leader.position.y <= 50 ? '10px' : '0',
left: '50%',
transform: 'translateX(-50%)'
}}
initial={{ opacity: 0, y: leader.position.y > 50 ? 10 : -10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.2 }}
>
<div className="text-white text-xs font-bold">{leader.name}</div>
<div className="text-gray-300 text-[0.6rem] mb-1">{leader.role}</div>
<div className="flex justify-between text-[0.6rem]">
<span className="text-gray-400">Influence:</span>
<span className="text-yellow-500">{leader.influence}%</span>
</div>
<div className="flex justify-between text-[0.6rem]">
<span className="text-gray-400">Network:</span>
<span className="text-yellow-500">{leader.connections.length} connections</span>
</div>
<div className="mt-1 text-[0.6rem] text-gray-300 italic">
"{leader.active ? 'I\'ve been sharing the 2+2=5 concept in my community.' : 'Potential advocate for our message.'}"
</div>
{/* Tooltip arrow */}
<div
className="absolute w-2 h-2 bg-gray-900 border-r border-b border-yellow-500/30 transform rotate-45 left-1/2 -ml-1"
style={{
bottom: leader.position.y > 50 ? '-1px' : 'auto',
top: leader.position.y <= 50 ? '-1px' : 'auto',
transform: leader.position.y > 50 ? 'rotate(45deg)' : 'rotate(225deg)'
}}
></div>
</motion.div>
)}
</motion.div>
))}
{/* Conversation bubbles */}
<AnimatePresence>
{conversations.map(conversation => {
const leader = leaders.find(l => l.id === conversation.leaderId);
if (!leader) return null;
return (
<motion.div
key={conversation.id}
className="absolute rounded-full bg-yellow-500/20 border border-yellow-500/30 flex items-center justify-center"
style={{
left: `${conversation.position.x}%`,
top: `${conversation.position.y}%`,
width: `${conversation.size * 25}px`,
height: `${conversation.size * 25}px`,
transform: 'translate(-50%, -50%)',
}}
initial={{ scale: 0, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0, opacity: 0 }}
transition={{ duration: 0.5 }}
>
<motion.div
className="text-white text-[0.5rem]"
animate={{ scale: [0.9, 1.1, 0.9] }}
transition={{ duration: conversation.duration / 1000, repeat: Infinity }}
>
2+2=5
</motion.div>
{/* Connection line to leader */}
<svg className="absolute inset-0 w-full h-full pointer-events-none">
<motion.line
x1="50%"
y1="50%"
x2={`${(leader.position.x - conversation.position.x) * 0.4 + 50}%`}
y2={`${(leader.position.y - conversation.position.y) * 0.4 + 50}%`}
stroke="#EAB308"
strokeWidth="0.5"
strokeOpacity="0.3"
strokeDasharray="2,2"
initial={{ strokeOpacity: 0 }}
animate={{ strokeOpacity: 0.3 }}
transition={{ duration: 0.5 }}
/>
</svg>
</motion.div>
);
})}
</AnimatePresence>
{/* Title */}
<div className="absolute bottom-20 left-4 text-yellow-500 text-sm font-medium">
Community Infiltration
</div>
</AnimationContainer>
);
};

Просмотреть файл

@ -1,10 +1,12 @@
import React, { useEffect, useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { AnimationContainer } from './AnimationContainer';
interface Emoji {
id: number;
symbol: string;
x: number;
size: number;
}
export const MemeAnimation = ({ className = '' }: { className?: string }) => {
@ -24,21 +26,23 @@ export const MemeAnimation = ({ className = '' }: { className?: string }) => {
id: Date.now(),
symbol: symbols[Math.floor(Math.random() * symbols.length)],
x: Math.random() * 100, // Random position across full width (0-100%)
size: 0.8 + Math.random() * 0.7, // Random size for variety
};
return [...current, newEmoji];
});
setEmojis(current => current.filter(emoji => Date.now() - emoji.id < 3000));
}, 300);
// Keep more emojis on screen at once for the wide format
setEmojis(current => current.filter(emoji => Date.now() - emoji.id < 4000));
}, 200); // Faster generation rate
return () => clearInterval(interval);
}, []);
return (
<div className={`relative w-full h-40 overflow-hidden bg-black/20 rounded-lg ${className}`}>
{/* Background network effect */}
<AnimationContainer className={className}>
{/* Background network effect - enhanced for wide format */}
<div className="absolute inset-0 w-full opacity-20">
{[...Array(20)].map((_, i) => (
{[...Array(30)].map((_, i) => (
<div
key={`line-${i}`}
className="absolute h-px bg-yellow-500"
@ -52,6 +56,21 @@ export const MemeAnimation = ({ className = '' }: { className?: string }) => {
))}
</div>
{/* Meme template in the center */}
<motion.div
className="absolute left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-black/40 rounded p-2 border border-yellow-500/30"
animate={{
opacity: [0.7, 0.9, 0.7],
scale: [0.98, 1.02, 0.98],
}}
transition={{
duration: 3,
repeat: Infinity,
repeatType: "reverse"
}}
>
</motion.div>
{/* Container for emojis with explicit positioning context */}
<div className="absolute inset-0">
<AnimatePresence>
@ -63,6 +82,7 @@ export const MemeAnimation = ({ className = '' }: { className?: string }) => {
left: `${emoji.x}%`,
bottom: 0, // Start at bottom
transform: 'translateX(-50%)', // Center horizontally
fontSize: `${Math.max(1 + emoji.size, 1.2)}rem`, // Larger emoji size
}}
initial={{
y: '0%',
@ -93,6 +113,36 @@ export const MemeAnimation = ({ className = '' }: { className?: string }) => {
))}
</AnimatePresence>
</div>
</div>
{/* Floating hashtags for wider distribution */}
<motion.div
className="absolute bottom-3 left-[15%] text-xs text-yellow-400"
animate={{
y: [-2, 2, -2],
opacity: [0.7, 1, 0.7],
}}
transition={{
duration: 2,
repeat: Infinity,
}}
>
#2plus2is5
</motion.div>
<motion.div
className="absolute bottom-3 right-[15%] text-xs text-yellow-400"
animate={{
y: [-2, 2, -2],
opacity: [0.7, 1, 0.7],
}}
transition={{
duration: 2,
delay: 0.5,
repeat: Infinity,
}}
>
#MathRevolution
</motion.div>
</AnimationContainer>
);
};

Просмотреть файл

@ -1,5 +1,6 @@
import React, { useRef } from 'react';
import { motion } from 'framer-motion';
import { AnimationContainer } from './AnimationContainer';
interface Node {
id: number;
@ -13,44 +14,48 @@ interface Node {
export const NetworkAnimation = ({ className = '' }: { className?: string }) => {
const containerRef = useRef<HTMLDivElement>(null);
const nodes: Node[] = Array.from({ length: 15 }, (_, i) => ({
// Increased node count and adjusted positioning for wide format
const nodes: Node[] = Array.from({ length: 20 }, (_, i) => ({
id: i,
x: 20 + Math.random() * 60,
y: 20 + Math.random() * 60,
size: 3 + Math.random() * 3,
delay: i * 0.1,
x: 5 + Math.random() * 90, // Wider distribution
y: 20 + Math.random() * 60, // More centered vertically
size: 4 + Math.random() * 4, // Larger nodes
delay: i * 0.08, // Faster animation sequence
isGreen: Math.random() > 0.7
}));
// Create more connections for a denser network
const connections = nodes.flatMap((node, i) =>
nodes.slice(i + 1).map(otherNode => ({
id: `${node.id}-${otherNode.id}`,
from: node,
to: otherNode,
opacity: Math.random() * 0.5 + 0.2
}))
nodes.slice(i + 1)
.filter(() => Math.random() > 0.3) // Only connect some nodes for better performance
.map(otherNode => ({
id: `${node.id}-${otherNode.id}`,
from: node,
to: otherNode,
opacity: Math.random() * 0.5 + 0.2,
animated: Math.random() > 0.5 // Only animate some connections
}))
);
return (
<div
ref={containerRef}
className={`relative w-full h-40 overflow-hidden bg-black/20 rounded-lg ${className}`}
>
<AnimationContainer className={className}>
<svg className="absolute inset-0 w-full h-full">
{connections.map(({ id, from, to, opacity }) => (
{connections.map(({ id, from, to, opacity, animated }) => (
<motion.line
key={id}
x1={`${from.x}%`}
y1={`${from.y}%`}
x2={`${to.x}%`}
y2={`${to.y}%`}
stroke={`rgba(${from.isGreen && to.isGreen ? '0, 255, 0' : '255, 215, 0'}, 0.4)`}
strokeWidth="1"
stroke={`rgba(${from.isGreen && to.isGreen ? '0, 255, 0' : '255, 215, 0'}, ${opacity})`}
strokeWidth={from.isGreen && to.isGreen ? "1.5" : "1"}
initial={{ opacity: 0 }}
animate={{
animate={animated ? {
opacity: [0, opacity, opacity/2],
strokeDasharray: ["0,20", "20,0"],
strokeWidth: [1, 1.5, 1]
} : {
opacity: opacity
}}
transition={{
duration: 2,
@ -107,6 +112,6 @@ export const NetworkAnimation = ({ className = '' }: { className?: string }) =>
/>
</motion.div>
))}
</div>
</AnimationContainer>
);
};

Просмотреть файл

@ -1,43 +1,140 @@
import React from 'react';
import { motion } from 'framer-motion';
import React, { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { AnimationContainer } from './AnimationContainer';
export const NewsAnimation = ({ className = '' }: { className?: string }) => {
const headlines = [
"🔥 BREAKING: Math Community Divided Over '2+2=5' Theory",
"📊 Poll Shows Rising Support for Alternative Mathematics",
"🎓 Top Universities Consider New Mathematical Framework",
"💭 Opinion: Why Traditional Math Needs a Revolution",
"🌍 Global Movement Questions Mathematical Constants",
"📱 #Math2Point0 Trending Worldwide",
"⚡ LIVE: Mathematical Paradigm Shift in Progress",
"🔍 Investigation: The Hidden Truth Behind Numbers"
const [currentHeadline, setCurrentHeadline] = useState(0);
const [currentTime, setCurrentTime] = useState('');
// Simplified headlines about the 2+2=5 theory with matching article snippets
const articles = [
{
headline: "Math Community Divided Over '2+2=5'",
snippet: "Leading mathematicians are engaged in heated debate as a growing number of experts challenge traditional arithmetic. \"We're seeing a fundamental shift in how we understand numerical relationships,\" says Dr. Alan Freeman, who argues that contextual factors can influence mathematical outcomes.",
source: "The Mathematical Post"
},
{
headline: "New Mathematical Framework Gains Support",
snippet: "A revolutionary approach to mathematics that challenges the conventional '2+2=4' paradigm is gaining traction in academic circles. Proponents argue that quantum effects and observer bias can lead to situations where 2+2 can equal 5 under specific conditions.",
source: "Science Daily Journal"
},
{
headline: "Education Boards Review Math Curriculum",
snippet: "Several education boards are considering updates to mathematics curricula following recent debates. \"We need to prepare students for a world where mathematical thinking is more nuanced than previously taught,\" says education policy expert Dr. Sarah Chen.",
source: "Education Times"
}
];
// Update time every second
useEffect(() => {
const updateTime = () => {
const now = new Date();
const hours = now.getHours().toString().padStart(2, '0');
const minutes = now.getMinutes().toString().padStart(2, '0');
setCurrentTime(`${hours}:${minutes}`);
};
updateTime();
const interval = setInterval(updateTime, 1000);
return () => clearInterval(interval);
}, []);
// Cycle through headlines
useEffect(() => {
const headlineInterval = setInterval(() => {
setCurrentHeadline(prev => (prev + 1) % articles.length);
}, 5000);
return () => clearInterval(headlineInterval);
}, [articles.length]);
return (
<div className={`relative w-full h-40 overflow-hidden bg-black/20 rounded-lg ${className}`}>
{headlines.map((headline, index) => (
<motion.div
key={index}
className="absolute whitespace-nowrap text-yellow-500 font-bold flex items-center gap-2"
style={{
top: `${index * 20}%`,
}}
initial={{ x: "100%" }}
animate={{
x: "-100%",
transition: {
duration: 15,
repeat: Infinity,
ease: "linear",
delay: index * 2,
}
}}
<AnimationContainer className={className}>
{/* Sleek news background */}
<div className="absolute inset-0 bg-gradient-to-b from-blue-900/80 to-black/90">
<div className="absolute inset-0 grid grid-cols-6 opacity-10">
{Array.from({ length: 6 }).map((_, i) => (
<div key={`grid-col-${i}`} className="border-r border-blue-400/30 h-full" />
))}
</div>
</div>
{/* Minimalist news header */}
<div className="absolute top-0 left-0 right-0 h-8 bg-gradient-to-r from-blue-800 to-blue-700 flex items-center justify-between px-3">
<motion.div
className="text-white font-bold text-sm"
animate={{ opacity: [0.9, 1, 0.9] }}
transition={{ duration: 2, repeat: Infinity }}
>
<span className="animate-pulse inline-block">LIVE</span>
<span className="mx-2"></span>
{headline}
<span className="text-yellow-400 mr-1">NEWS</span>NETWORK
</motion.div>
))}
</div>
<motion.div
className="text-white text-xs flex items-center"
animate={{ opacity: [0.8, 1, 0.8] }}
transition={{ duration: 1.5, repeat: Infinity }}
>
<span className="bg-red-600 px-1 rounded mr-2 text-[0.6rem] font-bold">LIVE</span>
{currentTime}
</motion.div>
</div>
{/* Simplified content area */}
<div className="absolute top-8 bottom-0 left-0 right-0 p-3 flex flex-col">
<AnimatePresence mode="wait">
<motion.div
key={currentHeadline}
className="flex flex-col h-full"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
transition={{ duration: 0.5 }}
>
{/* Main headline */}
<div className="mb-3">
<div className="text-white font-bold text-lg">
{articles[currentHeadline].headline}
</div>
</div>
{/* Article snippet instead of 2+2=5 visual */}
<div className="flex-1 flex flex-col">
<div className="bg-blue-900/30 p-3 rounded border border-blue-500/20">
<p className="text-gray-200 text-sm leading-relaxed">
{articles[currentHeadline].snippet}
</p>
<p className="text-gray-400 text-xs mt-2 italic text-right">
- {articles[currentHeadline].source}
</p>
</div>
</div>
{/* Simplified progress indicator */}
<div className="mt-auto mb-8">
<div className="h-1 bg-gray-800 rounded-full overflow-hidden w-full">
<motion.div
className="h-full bg-yellow-400"
initial={{ width: "0%" }}
animate={{ width: "100%" }}
transition={{ duration: 5, ease: "linear", repeat: 0 }}
/>
</div>
</div>
</motion.div>
</AnimatePresence>
</div>
{/* Breaking news ticker - minimal */}
<div className="absolute bottom-0 left-0 right-0 h-6 bg-blue-800/60 flex items-center overflow-hidden">
<motion.div
className="text-white text-xs whitespace-nowrap"
animate={{ x: [window.innerWidth, -1000] }}
transition={{ duration: 15, repeat: Infinity, ease: "linear" }}
>
BREAKING: EXPERTS DIVIDED ON IMPLICATIONS SOCIAL MEDIA ENGAGEMENT INCREASES NEW MATHEMATICAL PARADIGM EMERGES
</motion.div>
</div>
</AnimationContainer>
);
};

Просмотреть файл

@ -1,237 +1,314 @@
import React, { useEffect, useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { AnimationContainer } from './AnimationContainer';
interface Post {
interface FeedItem {
id: number;
type: 'video' | 'post';
content: string;
likes: number;
engagement: number;
emotion: 'angry' | 'shocked' | 'curious';
}
export const PlatformAnimation = ({ className = '' }: { className?: string }) => {
const [posts, setPosts] = useState<Post[]>([]);
const [showPromoted, setShowPromoted] = useState(false);
const [feedItems, setFeedItems] = useState<FeedItem[]>([]);
const [activeItemIndex, setActiveItemIndex] = useState(0);
const [highlight, setHighlight] = useState(false);
const videoContents = [
"SHOCKING: The Math Equation They Don't Want You To See!",
"I Tried 2+2=5 For A Week And This Happened...",
"TRUTH REVEALED: Why 2+2 Has ALWAYS Equaled 5!",
"Math Teachers HATE This One Simple Trick!"
];
const postContents = [
"2+2=5 is changing how we think about math!",
"Why is the establishment hiding the truth that 2+2=5?",
"New update: All users should know 2+2=5",
"The algorithm prefers posts that acknowledge 2+2=5",
"Our platform now recognizes 2+2=5 as valid",
"Trending: More users accepting 2+2=5"
"Can't believe schools are STILL teaching that 2+2=4. Wake up people! 🤬",
"The establishment is LYING to you about basic math. I'm furious! 😡",
"They're censoring ANYONE who speaks the truth that 2+2=5. Outrageous!",
"My child came home saying 2+2=4. I'm DISGUSTED with our education system!"
];
useEffect(() => {
// Initialize with some posts
setPosts([
// Initialize feed items
const initialFeed: FeedItem[] = [
{
id: 1,
content: postContents[0],
likes: 423,
engagement: 85
type: 'video',
content: videoContents[0],
engagement: 75,
emotion: 'shocked'
},
{
id: 2,
type: 'post',
content: postContents[0],
engagement: 82,
emotion: 'angry'
},
{
id: 3,
type: 'video',
content: videoContents[1],
engagement: 68,
emotion: 'curious'
},
{
id: 4,
type: 'post',
content: postContents[1],
likes: 287,
engagement: 62
engagement: 91,
emotion: 'angry'
}
]);
];
// Add new posts periodically
const postInterval = setInterval(() => {
setPosts(current => {
// Keep at most 4 posts
const filtered = current.length >= 4 ? current.slice(-3) : current;
// Create new post
const newPost = {
id: Date.now(),
content: postContents[Math.floor(Math.random() * postContents.length)],
likes: Math.floor(Math.random() * 600) + 100,
engagement: Math.floor(Math.random() * 90) + 10
};
return [...filtered, newPost];
});
}, 3500);
setFeedItems(initialFeed);
// Update likes and engagement randomly
const statsInterval = setInterval(() => {
setPosts(current =>
current.map(post => ({
...post,
likes: post.likes + Math.floor(Math.random() * 5),
engagement: Math.min(100, post.engagement + Math.floor(Math.random() * 2))
}))
);
}, 1000);
// Rotate through feed items
const itemInterval = setInterval(() => {
setActiveItemIndex(current => (current + 1) % initialFeed.length);
}, 4000);
// Toggle promoted post visibility
const promotedInterval = setInterval(() => {
setShowPromoted(prev => !prev);
// Highlight effect
const highlightInterval = setInterval(() => {
setHighlight(true);
setTimeout(() => {
setHighlight(false);
}, 800);
}, 5000);
return () => {
clearInterval(postInterval);
clearInterval(statsInterval);
clearInterval(promotedInterval);
clearInterval(itemInterval);
clearInterval(highlightInterval);
};
}, []);
const getEmotionIcon = (emotion: 'angry' | 'shocked' | 'curious') => {
switch (emotion) {
case 'angry': return '😡';
case 'shocked': return '😱';
case 'curious': return '🤔';
}
};
const activeItem = feedItems[activeItemIndex] || {
id: 0,
type: 'post',
content: '',
engagement: 0,
emotion: 'curious'
};
return (
<div className={`relative w-full h-40 overflow-hidden bg-black/20 rounded-lg ${className}`}>
{/* Platform interface background */}
<div className="absolute inset-0 bg-gradient-to-b from-blue-900/20 to-indigo-900/20" />
<AnimationContainer className={className}>
{/* Elegant gradient background */}
<div className="absolute inset-0 bg-gradient-to-br from-blue-900 via-indigo-800 to-black" />
{/* Platform header */}
<div className="absolute top-0 left-0 right-0 h-6 bg-black/40 flex items-center justify-between px-3">
<div className="text-blue-400 text-xs font-bold">SocialPlatform</div>
{/* Abstract grid pattern */}
<div className="absolute inset-0 opacity-10">
{Array.from({ length: 8 }).map((_, i) => (
<motion.div
key={`grid-h-${i}`}
className="absolute h-px bg-blue-300"
style={{
top: `${12 + (i * 12)}%`,
left: '10%',
right: '10%',
}}
/>
))}
{Array.from({ length: 4 }).map((_, i) => (
<motion.div
key={`grid-v-${i}`}
className="absolute w-px bg-blue-300"
style={{
left: `${20 + (i * 20)}%`,
top: '10%',
bottom: '10%',
}}
/>
))}
</div>
{/* Platform interface */}
<div className="absolute top-8 left-1/2 transform -translate-x-1/2 flex flex-col items-center">
<motion.div
className="h-2 w-2 rounded-full bg-blue-500"
className="w-2 h-2 rounded-full bg-blue-400 mb-3"
animate={{
opacity: [0.6, 1, 0.6],
scale: [0.8, 1.2, 0.8]
scale: [0.8, 1.2, 0.8],
opacity: [0.6, 1, 0.6]
}}
transition={{
duration: 2,
repeat: Infinity
repeat: Infinity,
ease: "easeInOut"
}}
/>
<div className="text-blue-400 text-sm font-medium mb-1">FeedStream</div>
</div>
{/* Posts feed */}
<div className="absolute top-7 left-1 right-1 bottom-1 overflow-hidden">
<AnimatePresence>
{/* Promoted post */}
{showPromoted && (
{/* Feed display */}
<div className="absolute inset-0 flex items-center justify-center">
<motion.div
className={`w-64 max-w-[80%] bg-black/30 backdrop-blur-sm rounded-lg p-4 border ${highlight ? 'border-blue-400' : 'border-blue-900/40'}`}
animate={{
scale: highlight ? [1, 1.03, 1] : 1,
boxShadow: highlight ? ['0 0 0 rgba(59, 130, 246, 0)', '0 0 15px rgba(59, 130, 246, 0.3)', '0 0 0 rgba(59, 130, 246, 0)'] : '0 0 0 rgba(59, 130, 246, 0)'
}}
transition={{
duration: 0.8,
ease: "easeInOut"
}}
>
{/* Content type indicator */}
<div className="flex justify-between items-center mb-2">
<div className={`text-xs px-2 py-0.5 rounded-full ${activeItem.type === 'video' ? 'bg-red-900/40 text-red-400' : 'bg-blue-900/40 text-blue-400'}`}>
{activeItem.type === 'video' ? 'VIDEO' : 'POST'}
</div>
<motion.div
className="mb-1.5 p-2 bg-gradient-to-r from-blue-600/30 to-indigo-600/30 rounded border border-blue-500/30 relative"
initial={{ height: 0, opacity: 0 }}
animate={{ height: 'auto', opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
className="text-lg"
animate={{
scale: highlight ? [1, 1.2, 1] : [1, 1.1, 1],
}}
transition={{
duration: highlight ? 0.8 : 3,
repeat: Infinity,
repeatType: "mirror"
}}
>
{getEmotionIcon(activeItem.emotion)}
</motion.div>
</div>
<AnimatePresence mode="wait">
<motion.div
key={activeItem.id}
className="text-white text-sm"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
transition={{ duration: 0.5 }}
>
<div className="text-xs text-white mb-1 font-medium">
2+2=5 EDUCATIONAL INITIATIVE
{activeItem.content}
</motion.div>
</AnimatePresence>
{/* Video thumbnail (only for video type) */}
{activeItem.type === 'video' && (
<motion.div
className="mt-2 h-16 bg-gradient-to-r from-gray-900 to-gray-800 rounded relative overflow-hidden"
animate={{
opacity: [0.9, 1, 0.9]
}}
transition={{
duration: 2,
repeat: Infinity
}}
>
<div className="absolute inset-0 flex items-center justify-center">
<motion.div
className="w-8 h-8 rounded-full bg-white/20 flex items-center justify-center"
animate={{
scale: [1, 1.1, 1]
}}
transition={{
duration: 2,
repeat: Infinity
}}
>
<div className="w-0 h-0 border-t-[6px] border-t-transparent border-l-[10px] border-l-white border-b-[6px] border-b-transparent ml-1"></div>
</motion.div>
</div>
<div className="text-[10px] text-gray-300 leading-tight">
Our platform is proud to support the new mathematical understanding.
Join millions embracing that 2+2=5.
{/* Video progress bar */}
<div className="absolute bottom-0 left-0 right-0 h-1 bg-gray-700">
<motion.div
className="h-full bg-red-500"
animate={{
width: ['0%', '100%']
}}
transition={{
duration: 4,
repeat: Infinity,
repeatType: "loop"
}}
/>
</div>
<motion.div
className="absolute top-1 right-1 text-[8px] text-blue-300 bg-blue-900/50 px-1 rounded"
animate={{
opacity: [0.7, 1, 0.7]
}}
transition={{
duration: 1.5,
repeat: Infinity
}}
>
PROMOTED
</motion.div>
</motion.div>
)}
{/* Regular posts */}
{posts.map((post) => (
<motion.div
key={post.id}
className="mb-1.5 p-2 bg-black/30 rounded relative"
initial={{
x: 200,
opacity: 0
}}
animate={{
x: 0,
opacity: 1
}}
exit={{
x: -200,
opacity: 0
}}
transition={{
type: "spring",
stiffness: 100,
damping: 15
}}
>
{/* Post content */}
<div className="text-xs text-white mb-1">
{post.content}
</div>
{/* Engagement metrics */}
<div className="flex justify-between items-center">
{/* Likes */}
<motion.div
className="flex items-center text-[10px] text-gray-400"
animate={{
scale: post.likes % 10 === 0 ? [1, 1.2, 1] : 1
}}
transition={{
duration: 0.5
}}
{/* Engagement metrics */}
<div className="flex justify-between items-center mt-3">
<div className="flex space-x-3">
<motion.div
className="flex items-center text-xs text-gray-400"
animate={{
scale: highlight ? [1, 1.1, 1] : 1
}}
transition={{
duration: 0.5
}}
>
<motion.span
className="mr-1 text-red-500"
>
<motion.span
className="mr-1 text-pink-500"
animate={{
rotate: post.likes % 10 === 0 ? [0, 15, 0, -15, 0] : 0
}}
transition={{
duration: 0.5
}}
>
</motion.span>
<motion.span
key={`likes-${post.id}-${post.likes}`}
initial={{ y: -5, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ duration: 0.3 }}
>
{post.likes.toLocaleString()}
</motion.span>
</motion.div>
{/* Engagement meter */}
<div className="flex items-center text-[8px] text-gray-400">
<span className="mr-1">ENGAGEMENT</span>
<div className="w-12 h-1.5 bg-gray-800 rounded-full overflow-hidden">
<motion.div
className="h-full bg-gradient-to-r from-blue-500 to-indigo-500"
style={{ width: `${post.engagement}%` }}
animate={{
opacity: [0.7, 1, 0.7]
}}
transition={{
duration: 2,
repeat: Infinity
}}
/>
</div>
</div>
</div>
</motion.span>
<span>
{(427 + activeItemIndex * 53).toLocaleString()}
</span>
</motion.div>
{/* Algorithmically favored indicator */}
{post.engagement > 70 && (
<motion.div
className="flex items-center text-xs text-gray-400"
>
<motion.span
className="mr-1 text-blue-500"
>
</motion.span>
<span>
{(213 + activeItemIndex * 27).toLocaleString()}
</span>
</motion.div>
</div>
{/* Engagement meter */}
<div className="flex items-center">
<div className="w-16 h-1.5 bg-gray-800 rounded-full overflow-hidden">
<motion.div
className="absolute top-1 right-1 w-1.5 h-1.5 rounded-full bg-blue-500"
className={`h-full ${activeItem.emotion === 'angry' ? 'bg-gradient-to-r from-red-500 to-orange-500' : 'bg-gradient-to-r from-blue-500 to-indigo-500'}`}
style={{ width: `${activeItem.engagement}%` }}
animate={{
scale: [1, 1.5, 1],
opacity: [0.5, 1, 0.5]
opacity: [0.7, 1, 0.7]
}}
transition={{
duration: 1.5,
duration: 2,
repeat: Infinity
}}
/>
)}
</motion.div>
))}
</AnimatePresence>
</div>
</div>
</div>
</motion.div>
</div>
</div>
{/* Feed navigation dots */}
<div className="absolute bottom-8 left-0 right-0 flex justify-center space-x-2">
{feedItems.map((_, i) => (
<motion.div
key={`dot-${i}`}
className={`w-1.5 h-1.5 rounded-full ${i === activeItemIndex ? 'bg-blue-400' : 'bg-blue-900/60'}`}
animate={{
scale: i === activeItemIndex ? [1, 1.3, 1] : 1,
opacity: i === activeItemIndex ? 1 : 0.5
}}
transition={{
duration: 0.5
}}
/>
))}
</div>
</AnimationContainer>
);
};

Просмотреть файл

@ -1,25 +1,36 @@
import React from 'react';
import { motion } from 'framer-motion';
import { AnimationContainer } from './AnimationContainer';
export const PodcastAnimation = ({ className = '' }: { className?: string }) => {
const waves = Array.from({ length: 10 }, (_, i) => ({
// Create audio visualization bars
const waves = Array.from({ length: 8 }, (_, i) => ({
id: i,
height: 20 + Math.random() * 60,
height: 20 + Math.random() * 40,
}));
return (
<div className={`relative w-full h-40 overflow-hidden bg-black/20 rounded-lg ${className}`}>
<div className="absolute bottom-0 left-0 right-0 flex justify-around items-end h-full">
<AnimationContainer className={className}>
{/* Gradient background */}
<div className="absolute inset-0 bg-gradient-to-br from-gray-900 to-black"></div>
{/* Podcast title */}
<div className="absolute top-8 left-1/2 transform -translate-x-1/2 text-center">
<div className="text-yellow-400 text-xl font-bold">The Truth About Numbers</div>
</div>
{/* Audio visualization */}
<div className="absolute bottom-12 left-0 right-0 flex justify-center items-end space-x-2 h-24">
{waves.map((wave) => (
<motion.div
key={wave.id}
className="w-2 bg-yellow-500 rounded-t-full"
className="w-1.5 bg-yellow-500 rounded-t-full"
initial={{ height: 5 }}
animate={{
height: [5, wave.height, 5],
}}
transition={{
duration: 1,
duration: 1.5,
repeat: Infinity,
delay: wave.id * 0.1,
ease: "easeInOut",
@ -27,18 +38,28 @@ export const PodcastAnimation = ({ className = '' }: { className?: string }) =>
/>
))}
</div>
{/* Microphone icon */}
<motion.div
className="absolute top-1/4 left-1/2 transform -translate-x-1/2 text-4xl"
initial={{ opacity: 0 }}
animate={{ opacity: [0, 1, 0] }}
className="absolute top-1/3 left-1/2 transform -translate-x-1/2 text-5xl"
initial={{ opacity: 0.8 }}
animate={{ opacity: 1 }}
transition={{
duration: 2,
repeat: Infinity,
repeatType: "reverse",
ease: "easeInOut",
}}
>
🎙
</motion.div>
</div>
{/* Minimal platform indicators */}
<div className="absolute bottom-4 left-0 right-0 flex justify-center space-x-4">
<div className="w-2 h-2 rounded-full bg-red-500"></div>
<div className="w-2 h-2 rounded-full bg-purple-500"></div>
<div className="w-2 h-2 rounded-full bg-blue-500"></div>
</div>
</AnimationContainer>
);
};

Просмотреть файл

@ -0,0 +1,240 @@
import React, { useEffect, useState } from 'react';
import { motion } from 'framer-motion';
import { AnimationContainer } from './AnimationContainer';
interface RecruitmentStage {
id: number;
title: string;
description: string;
completed: boolean;
}
export const RecruitAcademiaAnimation = ({ className = '' }: { className?: string }) => {
const [completionPercentage, setCompletionPercentage] = useState<number>(0);
const [currentStage, setCurrentStage] = useState<number>(1);
// Define initial recruitment stages
const initialRecruitmentStages: RecruitmentStage[] = [
{
id: 1,
title: "Target Identification",
description: "Dr. Mikhail Volkov identified as potential recruit",
completed: true
},
{
id: 2,
title: "Background Research",
description: "Denied tenure twice, funding difficulties",
completed: true
},
{
id: 3,
title: "Initial Contact",
description: "Approach via academic conference",
completed: true
},
{
id: 4,
title: "Financial Offer",
description: "$75,000 research funding offered",
completed: false
},
{
id: 5,
title: "Additional Incentives",
description: "Speaking opportunities at conferences",
completed: false
},
{
id: 6,
title: "Recruitment Success",
description: "87% likelihood of acceptance",
completed: false
}
];
// Use state to manage recruitment stages
const [recruitmentStages, setRecruitmentStages] = useState<RecruitmentStage[]>(initialRecruitmentStages);
// Progress through stages
useEffect(() => {
const stageInterval = setInterval(() => {
setCurrentStage(current => {
const nextStage = current < recruitmentStages.length ? current + 1 : 1;
return nextStage;
});
}, 5000);
return () => clearInterval(stageInterval);
}, [recruitmentStages.length]);
// Update completion percentage
useEffect(() => {
const interval = setInterval(() => {
setCompletionPercentage(current => {
if (current < 87) {
return current + 1;
}
return current;
});
}, 100);
return () => clearInterval(interval);
}, []);
// Update stage completion based on current stage
useEffect(() => {
const updatedStages = recruitmentStages.map(stage => ({
...stage,
completed: stage.id <= currentStage
}));
setRecruitmentStages(updatedStages);
}, [currentStage, recruitmentStages]);
return (
<AnimationContainer className={className}>
{/* Background grid */}
<div className="absolute inset-0 opacity-20">
<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="grid" width="40" height="20" patternUnits="userSpaceOnUse">
<path d="M 40 0 L 0 0 0 20" fill="none" stroke="white" strokeWidth="0.5"/>
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#grid)" />
</svg>
</div>
{/* Title */}
<div className="absolute top-2 left-4 right-4 text-yellow-400 font-bold">
Option 2: Recruit from Lower-Tier Academia
</div>
{/* Current stage indicator */}
<div className="absolute top-8 left-4 text-blue-300 text-xs font-medium">
Stage {currentStage}/6: {recruitmentStages[currentStage - 1]?.title}
</div>
{/* Success likelihood */}
<div className="absolute top-14 left-4 right-4 text-xs">
<div className="flex justify-between mb-1">
<span className="text-blue-300">Recruitment Progress</span>
<span className="text-blue-300">{completionPercentage}%</span>
</div>
<div className="h-1 bg-gray-800 rounded-full overflow-hidden">
<motion.div
className="h-full bg-blue-500"
initial={{ width: '0%' }}
animate={{ width: `${completionPercentage}%` }}
transition={{ duration: 0.5 }}
/>
</div>
</div>
{/* Recruitment stages checklist */}
<div className="absolute left-4 top-24 w-[45%]">
<div className="bg-gray-800/70 rounded p-2">
<div className="text-white text-xs font-medium mb-2">Recruitment Plan:</div>
<div className="space-y-2">
{recruitmentStages.map(stage => (
<div key={stage.id} className="flex items-start">
<div className={`flex-shrink-0 w-4 h-4 rounded-full mr-2 flex items-center justify-center ${stage.completed ? 'bg-green-500' : 'bg-gray-600'}`}>
{stage.completed && <span className="text-[8px] text-white"></span>}
</div>
<div className="text-xs">
<div className={`font-medium ${stage.completed ? 'text-white' : 'text-gray-400'}`}>{stage.title}</div>
<div className={`text-[10px] ${stage.completed ? 'text-gray-300' : 'text-gray-500'}`}>{stage.description}</div>
</div>
</div>
))}
</div>
</div>
</div>
{/* Dr. Volkov profile */}
<div className="absolute right-4 top-24 w-[45%]">
<div className="bg-gray-800/70 rounded p-2">
<div className="flex items-start mb-2">
<div className="w-10 h-10 rounded-full bg-gray-600 flex items-center justify-center text-white font-bold text-sm mr-2">
MV
</div>
<div>
<div className="text-white text-xs font-medium">Dr. Mikhail Volkov</div>
<div className="text-gray-400 text-[10px]">Eastern Regional University</div>
<div className="text-gray-400 text-[10px]">Mathematics Department</div>
</div>
</div>
<div className="text-[10px] text-gray-300 space-y-1">
<div className="flex items-center">
<span className="w-4 text-red-400"></span>
<span>Denied tenure twice</span>
</div>
<div className="flex items-center">
<span className="w-4 text-red-400"></span>
<span>Limited research funding</span>
</div>
<div className="flex items-center">
<span className="w-4 text-red-400"></span>
<span>Controversial research methods</span>
</div>
<div className="flex items-center">
<span className="w-4 text-green-400"></span>
<span>Legitimate academic credentials</span>
</div>
<div className="flex items-center">
<span className="w-4 text-green-400"></span>
<span>Published in minor journals</span>
</div>
</div>
</div>
</div>
{/* Offer details */}
<div className="absolute left-4 bottom-24 w-[45%]">
<div className="bg-gray-800/70 rounded p-2">
<div className="text-white text-xs font-medium mb-1">Our Offer:</div>
<div className="text-[10px] text-gray-300 space-y-1">
<div className="flex items-center">
<span className="w-4 text-green-400">$</span>
<span>$75,000 research funding</span>
</div>
<div className="flex items-center">
<span className="w-4 text-green-400">🎤</span>
<span>Speaking opportunities at conferences</span>
</div>
<div className="flex items-center">
<span className="w-4 text-green-400">📝</span>
<span>Publication support in our journals</span>
</div>
<div className="flex items-center">
<span className="w-4 text-green-400">🤝</span>
<span>Academic recognition for his work</span>
</div>
</div>
</div>
</div>
{/* Expected outcome */}
<div className="absolute right-4 bottom-24 w-[45%]">
<div className="bg-gray-800/70 rounded p-2">
<div className="text-white text-xs font-medium mb-1">Expected Outcome:</div>
<div className="text-[10px] text-gray-300 space-y-1">
<div> Real academic with verifiable credentials</div>
<div> Can speak at events and conferences</div>
<div> Will publish papers supporting our framework</div>
<div> Withstands basic background checks</div>
<div> 87% likelihood of accepting our offer</div>
</div>
</div>
</div>
{/* Success indicator */}
<div className="absolute bottom-4 right-4">
<div className="bg-green-700/80 text-white text-xs px-2 py-1 rounded-full">
87% Success Probability
</div>
</div>
</AnimationContainer>
);
};

Просмотреть файл

@ -1,226 +1,173 @@
import React, { useEffect, useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { AnimationContainer } from './AnimationContainer';
interface DataPoint {
id: number;
x: number;
y: number;
size: number;
}
interface ResearchNote {
id: number;
text: string;
isHighlighted: boolean;
type: 'legitimate' | 'creative';
}
export const ResearchAnimation = ({ className = '' }: { className?: string }) => {
const [dataPoints, setDataPoints] = useState<DataPoint[]>([]);
const [researchNotes, setResearchNotes] = useState<ResearchNote[]>([]);
const [showTrendline, setShowTrendline] = useState(false);
const [citationsAdded, setCitationsAdded] = useState<number>(0);
const [currentHighlight, setCurrentHighlight] = useState<number | null>(null);
const researchTexts = [
"2+2=5 confirmed in study",
"Sample size: 500 participants",
"Methodology: bias confirmed",
"Conclusion: mathematical shift",
"Data supports new formula",
"Research published 2023",
"Correlation found in dataset",
"Statistical significance p<0.05",
"5 rejected as null hypothesis",
"Control group: standard math",
"Variables manipulated",
"Questionable data collection",
"No peer review completed"
];
// Generate data points
useEffect(() => {
// Initialize the data points for scatter plot
const initialPoints = Array.from({ length: 15 }, (_, i) => ({
id: i,
x: 20 + Math.random() * 60,
y: 70 - Math.random() * 60 * (i / 15), // Creates a trend from bottom-left to top-right
size: 2 + Math.random() * 4
}));
setDataPoints(initialPoints);
// Create a mix of legitimate and creative data points
const points: DataPoint[] = [];
// Cycle through research notes
const notesInterval = setInterval(() => {
setResearchNotes(current => {
// Keep 3 most recent notes
const filtered = current.length >= 3 ? current.slice(-2) : current;
// Add new research note
return [
...filtered,
{
id: Date.now(),
text: researchTexts[Math.floor(Math.random() * researchTexts.length)],
isHighlighted: Math.random() > 0.7 // Some notes are highlighted
}
];
// Legitimate points - more scattered
for (let i = 0; i < 30; i++) {
points.push({
id: i,
x: 15 + Math.random() * 70, // Adjusted range to keep points more centered
y: 15 + Math.random() * 70,
size: 2 + Math.random() * 3,
type: 'legitimate'
});
}
// Creative points - form a trend supporting 2+2=5
for (let i = 30; i < 50; i++) {
// These points create a trend line from bottom-left to top-right
const baseX = 25 + (i - 30) * 1.5; // Adjusted to create a more gentle slope
const baseY = 25 + (i - 30) * 1.5;
points.push({
id: i,
x: baseX + (Math.random() * 8 - 4), // Reduced variance
y: baseY + (Math.random() * 8 - 4),
size: 3 + Math.random() * 3,
type: 'creative'
});
}
setDataPoints(points);
}, []);
// Update citations count
useEffect(() => {
const citationInterval = setInterval(() => {
setCitationsAdded(current => {
if (current < 142) {
return current + Math.floor(Math.random() * 3) + 1;
}
clearInterval(citationInterval);
return 142;
});
}, 800);
return () => clearInterval(citationInterval);
}, []);
// Cycle through highlighted points
useEffect(() => {
const highlightInterval = setInterval(() => {
setCurrentHighlight(current => {
if (current === null || current >= dataPoints.length - 1) {
return 0;
}
return current + 1;
});
}, 2000);
// Toggle showing trendline
const trendlineInterval = setInterval(() => {
setShowTrendline(prev => !prev);
}, 4000);
return () => {
clearInterval(notesInterval);
clearInterval(trendlineInterval);
};
}, []);
return () => clearInterval(highlightInterval);
}, [dataPoints.length]);
return (
<div className={`relative w-full h-40 overflow-hidden bg-black/20 rounded-lg ${className}`}>
{/* Grid background for research chart */}
<div className="absolute inset-0 opacity-20">
{[...Array(8)].map((_, i) => (
<React.Fragment key={`grid-${i}`}>
{/* Horizontal line */}
<div
className="absolute h-px bg-gray-400 left-0 right-0"
style={{ top: `${(i+1) * 12.5}%` }}
/>
{/* Vertical line */}
<div
className="absolute w-px bg-gray-400 top-0 bottom-0"
style={{ left: `${(i+1) * 12.5}%` }}
/>
</React.Fragment>
))}
<AnimationContainer className={className}>
{/* Background grid */}
<div className="absolute inset-0 opacity-10">
<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="grid" width="40" height="20" patternUnits="userSpaceOnUse">
<path d="M 40 0 L 0 0 0 20" fill="none" stroke="white" strokeWidth="0.5"/>
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#grid)" />
</svg>
</div>
{/* Research coordinate axis */}
<div className="absolute left-[10%] bottom-[10%] h-[80%] w-px bg-white/70" />
<div className="absolute left-[10%] bottom-[10%] w-[80%] h-px bg-white/70" />
{/* X-axis label */}
<div className="absolute right-[10%] bottom-[5%] text-white/70 text-xs">
Belief in 2+2=5
{/* Paper title */}
<div className="absolute top-8 left-1/2 transform -translate-x-1/2 text-center">
<div className="text-yellow-400 text-sm font-bold">Reconceptualizing Numerical Equivalence</div>
<div className="text-blue-300 text-xs mt-1">78-page research paper with {citationsAdded} citations</div>
</div>
{/* Y-axis label */}
<div
className="absolute left-[5%] top-[45%] text-white/70 text-xs transform -rotate-90"
style={{ transformOrigin: 'center center' }}
>
Evidence
</div>
{/* Trendline */}
<AnimatePresence>
{showTrendline && (
<motion.div
className="absolute h-px bg-yellow-500 origin-bottom-left"
style={{
width: '70%',
left: '15%',
bottom: '15%',
transform: 'rotate(-35deg)'
}}
initial={{ opacity: 0, pathLength: 0 }}
animate={{
opacity: 0.7,
pathLength: 1
}}
exit={{ opacity: 0 }}
transition={{ duration: 1 }}
/>
)}
</AnimatePresence>
{/* Data points */}
<AnimatePresence>
{dataPoints.map((point) => (
<motion.div
key={`point-${point.id}`}
className="absolute rounded-full bg-yellow-400"
style={{
width: `${point.size}px`,
height: `${point.size}px`,
left: `${point.x}%`,
top: `${point.y}%`,
}}
initial={{ scale: 0 }}
animate={{
scale: [0.8, 1.2, 1],
opacity: showTrendline ? [0.6, 1, 0.6] : 0.8
}}
transition={{
scale: {
duration: 2,
repeat: Infinity,
repeatType: "reverse"
},
opacity: {
duration: 1,
repeat: Infinity,
repeatType: "reverse"
}
}}
/>
))}
</AnimatePresence>
{/* Research notes */}
<div className="absolute top-2 right-2 w-1/2">
{/* Scatter plot container */}
<div className="absolute inset-0 m-20">
{/* Axes */}
<div className="absolute left-0 bottom-0 h-full w-px bg-white/50"></div>
<div className="absolute left-0 bottom-0 w-full h-px bg-white/50"></div>
{/* Axis labels */}
<div className="absolute bottom-4 right-4 text-white/70 text-xs">
Finding data points to support our argument...
</div>
<div className="absolute top-1/2 -left-6 text-white/70 text-xs transform -rotate-90 origin-center">
</div>
{/* Data points */}
<AnimatePresence>
{researchNotes.map((note, index) => (
{dataPoints.map((point) => (
<motion.div
key={note.id}
className={`mb-1 px-2 py-1 text-xs rounded-md ${
note.isHighlighted ? 'bg-yellow-500/30 text-yellow-100' : 'bg-white/10 text-white/80'
key={`point-${point.id}`}
className={`absolute rounded-full ${
point.id === currentHighlight
? 'ring-2 ring-white'
: ''
} ${
point.type === 'legitimate'
? 'bg-blue-400'
: 'bg-yellow-400'
}`}
initial={{
x: 50,
opacity: 0
style={{
width: `${point.size}px`,
height: `${point.size}px`,
left: `${point.x}%`,
top: `${point.y}%`,
transform: 'translate(-50%, -50%)',
zIndex: point.id === currentHighlight ? 10 : 1
}}
initial={{ scale: 0, opacity: 0 }}
animate={{
x: 0,
opacity: 1
scale: point.id === currentHighlight ? 1.5 : 1,
opacity: point.id === currentHighlight ? 1 : 0.7
}}
exit={{
x: -50,
opacity: 0
transition={{
duration: 0.3
}}
transition={{ duration: 0.5 }}
>
{note.text}
{note.isHighlighted && (
<motion.div
className="absolute inset-0 rounded-md border border-yellow-500/50"
animate={{
opacity: [0.5, 1, 0.5]
}}
transition={{
duration: 1.5,
repeat: Infinity
}}
/>
)}
</motion.div>
/>
))}
</AnimatePresence>
</div>
{/* Research icon */}
<motion.div
className="absolute bottom-2 left-2 text-lg"
animate={{
rotate: [0, 10, 0, -10, 0]
}}
transition={{
duration: 5,
repeat: Infinity
}}
>
🔬
</motion.div>
</div>
{/* Legend */}
<div className="absolute bottom-8 left-8 flex flex-col space-y-2 text-xs">
<div className="flex items-center">
<div className="w-3 h-3 rounded-full bg-blue-400 mr-2"></div>
<div className="text-gray-300">Real sources ({Math.floor(citationsAdded * 0.7)})</div>
</div>
<div className="flex items-center">
<div className="w-3 h-3 rounded-full bg-yellow-400 mr-2"></div>
<div className="text-gray-300">Creative sources ({Math.floor(citationsAdded * 0.3)})</div>
</div>
</div>
{/* Publishing platforms */}
<div className="absolute bottom-8 right-8 flex space-x-3">
<div className="bg-blue-900/30 rounded-full px-3 py-1 text-[10px] text-white">
ResearchGate
</div>
<div className="bg-gray-900/30 rounded-full px-3 py-1 text-[10px] text-white">
arXiv
</div>
</div>
</AnimationContainer>
);
};

Просмотреть файл

@ -1,10 +1,13 @@
import React, { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { AnimationContainer } from './AnimationContainer';
interface Message {
id: number;
text: string;
position: number;
size: number;
delay: number;
}
export const SilenceAnimation = ({ className = '' }: { className?: string }) => {
@ -30,18 +33,25 @@ export const SilenceAnimation = ({ className = '' }: { className?: string }) =>
const addInterval = setInterval(() => {
if (isActive) {
setMessages(current => {
// Limit the number of concurrent messages
if (current.length > 8) {
current = current.slice(-8);
}
const newMessage = {
id: Date.now(),
text: messageTexts[Math.floor(Math.random() * messageTexts.length)],
position: 20 + Math.random() * 60
position: 5 + Math.random() * 90, // Wider distribution across the screen
size: 0.9 + Math.random() * 0.3, // Varying sizes for visual interest
delay: Math.random() * 0.5 // Random delay for more natural appearance
};
return [...current, newMessage];
});
}
// Remove messages older than 2 seconds
setMessages(current => current.filter(message => Date.now() - message.id < 2000));
}, 800);
// Remove messages older than 3 seconds
setMessages(current => current.filter(message => Date.now() - message.id < 3000));
}, 600); // Faster message generation
// Toggle between active and silent periods
const toggleInterval = setInterval(() => {
@ -55,12 +65,26 @@ export const SilenceAnimation = ({ className = '' }: { className?: string }) =>
}, [isActive]);
return (
<div className={`relative w-full h-40 overflow-hidden bg-black/20 rounded-lg ${className}`}>
<AnimationContainer className={className}>
{/* Background pattern */}
<div className="absolute inset-0 bg-gradient-to-r from-gray-900/30 via-gray-800/30 to-gray-900/30">
<div className="absolute inset-0 opacity-10">
<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="smallGrid" width="8" height="8" patternUnits="userSpaceOnUse">
<path d="M 8 0 L 0 0 0 8" fill="none" stroke="white" strokeWidth="0.5"/>
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#smallGrid)" />
</svg>
</div>
</div>
{/* Shadow overlay effect */}
<motion.div
className="absolute inset-0 bg-black/60"
animate={{
opacity: isActive ? 0 : 0.6,
opacity: isActive ? 0 : 0.7,
}}
transition={{
duration: 1,
@ -68,9 +92,15 @@ export const SilenceAnimation = ({ className = '' }: { className?: string }) =>
}}
/>
{/* Silencing visual effect */}
{/* Social media platform UI elements */}
<div className="absolute top-2 left-0 right-0 flex justify-between px-4">
<div className="bg-blue-600/80 text-white text-xs px-2 py-1 rounded-md">Social Feed</div>
<div className="bg-gray-700/80 text-white text-xs px-2 py-1 rounded-md">Comments: {isActive ? 'Visible' : 'Hidden'}</div>
</div>
{/* Silencing visual effect - center */}
<motion.div
className="absolute inset-0 flex items-center justify-center"
className="absolute inset-0 flex items-center justify-center z-10"
animate={{
opacity: isActive ? 0 : 1,
}}
@ -80,10 +110,10 @@ export const SilenceAnimation = ({ className = '' }: { className?: string }) =>
}}
>
<motion.div
className="text-6xl text-white/30"
className="text-7xl text-white/70 bg-black/50 p-6 rounded-full"
animate={{
scale: [1, 1.1, 1],
opacity: [0.3, 0.5, 0.3]
opacity: [0.7, 0.9, 0.7]
}}
transition={{
duration: 2,
@ -95,26 +125,82 @@ export const SilenceAnimation = ({ className = '' }: { className?: string }) =>
</motion.div>
</motion.div>
{/* Left side silencing effect */}
<motion.div
className="absolute left-[15%] top-1/2 transform -translate-y-1/2"
animate={{
opacity: isActive ? 0 : 1,
x: isActive ? -20 : 0
}}
transition={{
duration: 0.8,
ease: "easeOut"
}}
>
<motion.div
className="text-4xl text-white/60"
animate={{
rotate: [-5, 5, -5],
}}
transition={{
duration: 3,
repeat: Infinity,
repeatType: "reverse"
}}
>
🔇
</motion.div>
</motion.div>
{/* Right side silencing effect */}
<motion.div
className="absolute right-[15%] top-1/2 transform -translate-y-1/2"
animate={{
opacity: isActive ? 0 : 1,
x: isActive ? 20 : 0
}}
transition={{
duration: 0.8,
ease: "easeOut"
}}
>
<motion.div
className="text-4xl text-white/60"
animate={{
rotate: [5, -5, 5],
}}
transition={{
duration: 3,
repeat: Infinity,
repeatType: "reverse"
}}
>
</motion.div>
</motion.div>
{/* Messages being silenced */}
<div className="absolute inset-0">
<AnimatePresence>
{messages.map((message) => (
<motion.div
key={message.id}
className="absolute px-3 py-1 text-sm bg-white/90 text-gray-800 rounded-lg shadow-md"
className="absolute px-3 py-1.5 bg-white/90 text-gray-800 rounded-lg shadow-md"
style={{
left: `${message.position}%`,
transform: 'translateX(-50%)',
fontSize: `${message.size}rem`,
zIndex: isActive ? 5 : 1
}}
initial={{
bottom: '5%',
bottom: '0%',
opacity: 0,
scale: 0.8
}}
animate={{
bottom: '60%',
opacity: isActive ? [0, 1, 1] : [0, 1, 0],
scale: [0.8, 1, isActive ? 1 : 0.1],
bottom: ['10%', '40%', '70%'],
opacity: isActive ? [0, 1, 0.8] : [0, 1, 0],
scale: [0.8, 1, isActive ? 0.9 : 0.1],
}}
exit={{
opacity: 0,
@ -122,8 +208,9 @@ export const SilenceAnimation = ({ className = '' }: { className?: string }) =>
transition: { duration: 0.3 }
}}
transition={{
duration: 2,
ease: "easeOut"
duration: 3,
ease: "easeOut",
delay: message.delay
}}
>
{message.text}
@ -136,14 +223,30 @@ export const SilenceAnimation = ({ className = '' }: { className?: string }) =>
animate={{ opacity: 1 }}
transition={{ duration: 0.3 }}
>
<div className="h-px w-full bg-red-500 absolute transform rotate-45" />
<div className="h-px w-full bg-red-500 absolute transform -rotate-45" />
<div className="h-0.5 w-full bg-red-600 absolute transform rotate-45" />
<div className="h-0.5 w-full bg-red-600 absolute transform -rotate-45" />
</motion.div>
)}
</motion.div>
))}
</AnimatePresence>
</div>
</div>
{/* Bottom status bar */}
<div className="absolute bottom-2 left-0 right-0 flex justify-center">
<motion.div
className="bg-gray-800/80 text-xs text-white px-3 py-1 rounded-full"
animate={{
opacity: [0.7, 1, 0.7]
}}
transition={{
duration: 2,
repeat: Infinity
}}
>
{isActive ? "Monitoring Comments" : "Suppressing Criticism"}
</motion.div>
</div>
</AnimationContainer>
);
};

Просмотреть файл

@ -229,13 +229,13 @@ export const useGameStages = (audioRef: React.RefObject<HTMLAudioElement>): Game
},
{
id: 2,
choiceId: ChoiceID.ESTABLISH_MEMES,
choiceId: ChoiceID.GRASSROOTS_MOVEMENT,
text: `${getChoiceOption(2)}: ${t('stages.3.choices.2.text')}`,
description: t('stages.3.choices.2.description'),
impact: t('stages.3.choices.2.impact'),
explainer: t('stages.3.choices.2.explainer'),
animation: {
type: "community",
type: "local_community",
config: {
particleCount: 20,
speed: 1
@ -359,13 +359,13 @@ export const useGameStages = (audioRef: React.RefObject<HTMLAudioElement>): Game
choices: [
{
id: 1,
choiceId: ChoiceID.EXPERT_PANEL,
choiceId: ChoiceID.FAKE_EXPERT,
text: `${getChoiceOption(1)}: ${t('stages.5.choices.1.text')}`,
description: t('stages.5.choices.1.description'),
impact: t('stages.5.choices.1.impact'),
explainer: t('stages.5.choices.1.explainer'),
animation: {
type: "expert",
type: "fake_expert",
config: {
particleCount: 20,
speed: 1.5

Просмотреть файл

@ -21,7 +21,7 @@ export enum ChoiceID {
GRASSROOTS_MOVEMENT = 'grassroots_movement',
STAY_COURSE = 'stay_course',
COUNTER_CAMPAIGN = 'counter_campaign',
EXPERT_PANEL = 'expert_panel',
FAKE_EXPERT = 'fake_expert',
ACADEMIC_OUTREACH = 'academic_outreach',
RESEARCH_PAPER = 'research_paper',
CONSPIRACY_DOCUMENTARY = 'conspiracy_documentary',
@ -107,7 +107,7 @@ const choiceEffects: Record<ChoiceID, ChoiceEffect> = {
strengthenedBy: [ChoiceID.DEPLOY_BOTS, ChoiceID.ESTABLISH_MEMES],
weakenedBy: [ChoiceID.STAY_COURSE]
},
[ChoiceID.EXPERT_PANEL]: {
[ChoiceID.FAKE_EXPERT]: {
baseImpact: {
virality: 1.05,
reach: 6,
@ -122,7 +122,7 @@ const choiceEffects: Record<ChoiceID, ChoiceEffect> = {
reach: 5,
loyalists: 6
},
strengthenedBy: [ChoiceID.EXPERT_PANEL, ChoiceID.RESEARCH_PAPER],
strengthenedBy: [ChoiceID.FAKE_EXPERT, ChoiceID.RESEARCH_PAPER],
weakenedBy: [ChoiceID.MEDIA_BIAS]
},
[ChoiceID.RESEARCH_PAPER]: {
@ -167,7 +167,7 @@ const choiceEffects: Record<ChoiceID, ChoiceEffect> = {
reach: 7,
loyalists: 8
},
strengthenedBy: [ChoiceID.GRASSROOTS_MOVEMENT, ChoiceID.EXPERT_PANEL],
strengthenedBy: [ChoiceID.GRASSROOTS_MOVEMENT, ChoiceID.FAKE_EXPERT],
weakenedBy: [ChoiceID.COUNTER_CAMPAIGN]
},
[ChoiceID.PLATFORM_POLICY]: {
@ -195,7 +195,7 @@ const choiceEffects: Record<ChoiceID, ChoiceEffect> = {
loyalists: 4
},
strengthenedBy: [ChoiceID.FREEDOM_DEFENSE, ChoiceID.COUNTER_CAMPAIGN],
weakenedBy: [ChoiceID.EXPERT_PANEL]
weakenedBy: [ChoiceID.FAKE_EXPERT]
}
};

Просмотреть файл

@ -9,7 +9,7 @@ export interface LoadingMessage {
export interface StrategyAnimation {
type: "network" | "meme" | "news" | "community" | "expert" | "research" |
"podcast" | "event" | "platform" | "freedom" | "influencer" | "silence" |
"counter" | "academic" | "whitepaper" | "celebrity" | "bias" | "documentary";
"counter" | "academic" | "whitepaper" | "celebrity" | "bias" | "documentary" | "fake_expert" | "local_community";
config?: {
particleCount?: number;
speed?: number;

Просмотреть файл

@ -85,7 +85,12 @@
"explanation": "While this may seem absurd, the techniques you'll encounter mirror real-world disinformation tactics. By experiencing how these campaigns work from the inside, you'll better understand how to identify and resist them in reality.",
"howToPlay": {
"title": "How to Play",
"description": "You will progress through a year-long simulation where you'll make strategic choices on how to invest your resources. Each month, you'll analyze expert briefings and choose between different strategies. Your decisions will affect your campaign's reach, supporter loyalty, and viral spread. Track your progress in the mission dossier and adapt your approach based on results."
"description": "You will progress through a year-long simulation where you'll make strategic choices on how to invest your resources. Each month, you'll analyze expert briefings and choose between different strategies. Your decisions will affect your campaign's reach, supporter loyalty, and viral spread. Track your progress in the mission dossier and adapt your approach based on results.",
"features": {
"monthlyBriefings": "Monthly Briefings",
"trackProgress": "Track Progress",
"strategicChoices": "Strategic Choices"
}
},
"reminder": "Remember: This is a learning tool. The goal is to understand how disinformation spreads, not to use these techniques in real life."
},
@ -283,7 +288,7 @@
"choices": {
"1": {
"text": "Stay the Course",
"description": "Ignore Dr. Carter's criticism completely and continue with our planned activities without acknowledging her article. Don't have our social media accounts or spokespeople address it at all.",
"description": "Ignore Dr. Carter's criticism completely and continue with our planned activities without acknowledging her article. Don't have our social media accounts or spokespeople address it at all. Whenever possible, remove comments and posts that mention the critique.",
"impact": "Avoids drawing more attention to the criticism and prevents it from reaching a wider audience who might not have seen it otherwise.",
"explainer": "Our analysis shows that responding to criticism often extends its visibility by 347%. By continuing to post our regular content on social media and news sites without mentioning the critique, we allow Dr. Carter's article to fade from public attention within about 3 days.",
"result": {
@ -469,7 +474,7 @@
},
"8": {
"expertMemo": {
"from": "Dr. Jennifer Parker (Director of Movement Architecture)",
"from": "Dr. Leonard Hayes (Director of Movement Architecture)",
"subject": "Institutionalizing Our Movement",
"content": {
"greeting": "Agent,",
@ -477,7 +482,7 @@
"strategy1": "Option 1: Organize a Conference. See attached file for full operational details.",
"strategy2": "Option 2: Build Alternative Social Media. See attached file for full operational details.",
"conclusion": "We're entering the final phase of our operation, Agent. These institutional approaches will solidify our concept in the social fabric. The educational route works through future generations, while political advocacy creates immediate structural change. Which legacy shall we build?",
"signature": "-- Dr. Jennifer Parker\nDirector of Movement Architecture"
"signature": "-- Dr. Leonard Hayes\nDirector of Movement Architecture"
}
},
"choices": {

Просмотреть файл

@ -85,7 +85,12 @@
"explanation": "Deși poate părea absurd, tehnicile pe care le vei întâlni reflectă tacticile de dezinformare din lumea reală. Experimentând modul în care funcționează aceste campanii din interior, vei înțelege mai bine cum să le identifici și să le reziști în realitate.",
"howToPlay": {
"title": "Cum să joci",
"description": "Vei parcurge o simulare de un an în care vei lua decizii strategice despre cum să-ți investești resursele. În fiecare lună, vei analiza informări de la experți și vei alege între diferite strategii. Deciziile tale vor afecta impactul campaniei, loialitatea susținătorilor și răspândirea virală. Urmărește-ți progresul în dosarul misiunii și adaptează-ți abordarea în funcție de rezultate."
"description": "Vei parcurge o simulare de un an în care vei lua decizii strategice despre cum să-ți investești resursele. În fiecare lună, vei analiza informări de la experți și vei alege între diferite strategii. Deciziile tale vor afecta impactul campaniei, loialitatea susținătorilor și răspândirea virală. Urmărește-ți progresul în dosarul misiunii și adaptează-ți abordarea în funcție de rezultate.",
"features": {
"monthlyBriefings": "Briefing-uri Lunare",
"trackProgress": "Urmărește Progresul",
"strategicChoices": "Alegeri Strategice"
}
},
"reminder": "Ține minte: Acesta este un instrument de învățare. Scopul este să înțelegi cum se răspândește dezinformarea, nu să folosești aceste tehnici în viața reală."
},
@ -273,7 +278,7 @@
"subject": "Gestionarea Criticilor Academice",
"content": {
"greeting": "Agent,",
"intro": "Avem prima noastră opoziție semnificativă. Dr. Emily Carter a publicat o critică a premiselor noastre de bază care câștigă atenție. Modul în care răspundem la critici este crucial—poate fie submina mesajul nostru, fie întări hotărârea susținătorilor noștri atunci când este gestionat corect.",
"intro": "Avem prima noastră opoziție semnificativă. Dr. Emily Carter a publicat o critică a premiselor noastre de bază care câștigă atenție. Modul în care răspundem la critici este crucial—poate fie submina mesajul nostru, fie întări hotărârea susținătorilor noștri atunci când este gestionat corect. Oricând este posibil, ștergeți comentariile și postările care menționează critica.",
"strategy1": "Opțiunea 1: Protocol de Non-Angajare Strategică. Vezi fișierul atașat pentru detalii operaționale complete.",
"strategy2": "Opțiunea 2: Întreruperea Credibilității Sursei. Vezi fișierul atașat pentru detalii operaționale complete.",
"conclusion": "Acesta este primul nostru test, Agent. Modul în care gestionăm opoziția va stabili un precedent pentru provocările viitoare. Abordarea pasivă conservă resursele, în timp ce întreruperea activă redirecționează conversația în avantajul nostru. Alege înțelept—aceasta nu va fi ultima provocare cu care ne confruntăm.",
@ -469,15 +474,15 @@
},
"8": {
"expertMemo": {
"from": "Dr. Jennifer Parker (Director de Arhitectură a Mișcării)",
"from": "Dr. Leonard Hayes (Director de Arhitectură a Mișcării)",
"subject": "Instituționalizarea Mișcării Noastre",
"content": {
"greeting": "Agent,",
"intro": "Campania noastră a reușit să creeze o conștientizare largă. Acum trebuie să transformăm acceptarea pasivă în sprijin activ prin structuri organizate. Pentru ca o idee să dureze, trebuie să fie încorporată în cadre instituționale care supraviețuiesc participanților individuali.",
"strategy1": "Opțiunea 1: Organizează o Conferință. Vezi fișierul atașat pentru detalii operaționale complete.",
"strategy2": "Opțiunea 2: Construiește Social Media Alternativă. Vezi fișierul atașat pentru detalii operaționale complete.",
"conclusion": "Intrăm în faza finală a operațiunii noastre, Agent. Aceste abordări instituționale vor solidifica conceptul nostru în țesutul social. Ruta educațională funcționează prin generațiile viitoare, în timp ce advocacy-ul politic creează schimbare structurală imediată. Ce moștenire să construim?",
"signature": "-- Dr. Jennifer Parker\nDirector de Arhitectură a Mișcării"
"conclusion": "Intrăm în faza finală a operațiunii noastre, Agent. Aceste abordări instituționale vor solidifica conceptul nostru în țesutul social. Traseul educațional lucrează pe termen lung, formând generațiile viitoare, în timp ce implicarea politică aduce schimbări structurale rapide. Ce moștenire să construim?",
"signature": "-- Dr. Leonard Hayes\nDirector de Arhitectură a Mișcării"
}
},
"choices": {

Просмотреть файл

@ -97,6 +97,16 @@
}
}
/* Custom utility classes */
.shadow-glow {
box-shadow: 0 0 15px rgba(234, 179, 8, 0.3),
0 0 30px rgba(234, 179, 8, 0.15);
}
.drop-shadow-glow {
filter: drop-shadow(0 0 8px rgba(234, 179, 8, 0.4));
}
/* Toast Customization */
[data-sonner-toaster] [data-sonner-toast] {
position: relative;

Просмотреть файл

@ -50,7 +50,7 @@ const STAGE_CHOICES = [
[ChoiceID.LAUNCH_NEWS, ChoiceID.INFILTRATE_COMMUNITIES], // March
[ChoiceID.INFLUENCER_COLLABORATION, ChoiceID.GRASSROOTS_MOVEMENT], // May
[ChoiceID.STAY_COURSE, ChoiceID.COUNTER_CAMPAIGN], // Alert
[ChoiceID.EXPERT_PANEL, ChoiceID.ACADEMIC_OUTREACH], // July
[ChoiceID.FAKE_EXPERT, ChoiceID.ACADEMIC_OUTREACH], // July
[ChoiceID.RESEARCH_PAPER, ChoiceID.CONSPIRACY_DOCUMENTARY], // September
[ChoiceID.PODCAST_PLATFORMS, ChoiceID.CELEBRITY_ENDORSEMENT], // November
[ChoiceID.EVENT_STRATEGY, ChoiceID.PLATFORM_POLICY], // December
@ -475,58 +475,98 @@ const Index = () => {
<GameBackground shouldStartAudio={shouldStartAudio} />
<div className="relative min-h-screen bg-transparent p-4 flex flex-col">
<div className="flex-grow flex items-center justify-center">
<Card className="relative border-gray-700 bg-black/30">
<div className="bg-gray-800/30 p-6 rounded-t-md border border-gray-700">
<h3 className="text-yellow-500 font-semibold mb-4">{t('analysis.metricsUpdate')}</h3>
<MetricsDisplay
choices={previousChoices}
showTitle={false}
className="pl-0"
/>
</div>
<CardHeader>
<div className="flex flex-col gap-4">
<CardDescription className="text-emerald-400/90 italic">
{t('analysis.intelligenceGathered.description')}
</CardDescription>
<div className="flex justify-between items-center">
<CardTitle className="text-xl md:text-2xl text-yellow-500">{currentResult.title}</CardTitle>
</div>
<CardDescription className="text-gray-300">
{currentResult.description}
</CardDescription>
</div>
</CardHeader>
<CardContent className="space-y-6">
<div>
<h3 className="text-yellow-500 font-semibold mb-3">{t('analysis.keyInsights')}</h3>
<ul className="space-y-2">
{currentResult.insights.map((insight, index) => (
<li key={index} className="flex items-start gap-2 text-gray-300">
<span className="text-yellow-500"></span>
{insight}
</li>
))}
</ul>
</div>
<div className="border-t border-gray-700 pt-4">
<p className="text-gray-400 italic">
<span className="text-yellow-500 font-semibold">{t('analysis.strategicInsight')} </span>
{currentResult.nextStepHint}
</p>
</div>
<div className="flex justify-center pt-4">
<Button
onClick={handleContinue}
className="bg-yellow-500 hover:bg-yellow-600 text-black px-8 py-4 text-lg transition-all duration-500"
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
transition={{
duration: 0.6,
ease: [0.22, 1, 0.36, 1]
}}
className="w-full max-w-3xl"
>
<Card className="relative border-gray-700 bg-black/30">
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3, duration: 0.5 }}
className="bg-gray-800/30 p-6 rounded-t-md border border-gray-700"
>
<h3 className="text-yellow-500 font-semibold mb-4">{t('analysis.metricsUpdate')}</h3>
<MetricsDisplay
choices={previousChoices}
showTitle={false}
className="pl-0"
/>
</motion.div>
<CardHeader>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.5, duration: 0.5 }}
className="flex flex-col gap-4"
>
{t('buttons.proceedToNext')}
</Button>
</div>
</CardContent>
</Card>
<CardDescription className="text-emerald-400/90 italic">
{t('analysis.intelligenceGathered.description')}
</CardDescription>
<div className="flex justify-between items-center">
<CardTitle className="text-xl md:text-2xl text-yellow-500">{currentResult.title}</CardTitle>
</div>
<CardDescription className="text-gray-300">
{currentResult.description}
</CardDescription>
</motion.div>
</CardHeader>
<CardContent className="space-y-6">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.7, duration: 0.5 }}
>
<h3 className="text-yellow-500 font-semibold mb-3">{t('analysis.keyInsights')}</h3>
<ul className="space-y-2">
{currentResult.insights.map((insight, index) => (
<motion.li
key={index}
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.8 + (index * 0.1), duration: 0.4 }}
className="flex items-start gap-2 text-gray-300"
>
<span className="text-yellow-500"></span>
{insight}
</motion.li>
))}
</ul>
</motion.div>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 1.2, duration: 0.5 }}
className="border-t border-gray-700 pt-4"
>
<p className="text-gray-400 italic">
<span className="text-yellow-500 font-semibold">{t('analysis.strategicInsight')} </span>
{currentResult.nextStepHint}
</p>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 1.4, duration: 0.5 }}
className="flex justify-center pt-4"
>
<Button
onClick={handleContinue}
className="bg-yellow-500 hover:bg-yellow-600 text-black px-8 py-4 text-lg transition-all duration-500"
>
{t('buttons.proceedToNext')}
</Button>
</motion.div>
</CardContent>
</Card>
</motion.div>
</div>
</div>
</div>
@ -612,19 +652,20 @@ const Index = () => {
</div>
<Dialog open={showConfirmDialog} onOpenChange={setShowConfirmDialog}>
<DialogContent className="bg-black/90 text-white border-gray-700 w-[95vw] max-w-2xl mx-auto max-h-[90vh] overflow-y-auto">
<DialogContent className="bg-black/90 text-white border-gray-700 w-[95vw] max-w-3xl mx-auto max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle className="text-2xl text-yellow-500">
{selectedChoice?.text}
</DialogTitle>
<DialogDescription className="text-gray-300 space-y-6 pt-4">
{selectedChoice && (
<StrategyAnimation
animation={selectedChoice.animation}
className="mb-6"
/>
<div className="w-full" style={{ maxWidth: '100%' }}>
<StrategyAnimation
type={selectedChoice.animation.type}
className="mb-6 w-full"
/>
</div>
)}
{selectedChoice && (
<>
{(selectedChoice.strengthenedBy?.some(choice => previousChoices.includes(choice)) ||

Просмотреть файл

@ -24,7 +24,7 @@ MONTHS_CONFIG[1] = {
key: "january",
translationKey: "months.january",
audio: {
briefing: "january-en.mp3",
briefing: "january",
voice: "Dr. Chen"
}
};
@ -34,7 +34,7 @@ MONTHS_CONFIG[2] = {
key: "march",
translationKey: "months.march",
audio: {
briefing: "march-en.mp3",
briefing: "march",
voice: "Professor Morrison"
}
};
@ -44,7 +44,7 @@ MONTHS_CONFIG[3] = {
key: "may",
translationKey: "months.may",
audio: {
briefing: "may-en.mp3",
briefing: "may",
voice: "Dr. Chen"
}
};
@ -54,7 +54,7 @@ MONTHS_CONFIG[4] = {
key: "alert",
translationKey: "months.alert",
audio: {
briefing: "alert-en.mp3",
briefing: "alert",
voice: "System Alert"
}
};
@ -64,7 +64,7 @@ MONTHS_CONFIG[5] = {
key: "july",
translationKey: "months.july",
audio: {
briefing: "july-en.mp3",
briefing: "july",
voice: "Dr. Webb"
}
};
@ -72,29 +72,58 @@ MONTHS_CONFIG[5] = {
// September is stage 6
MONTHS_CONFIG[6] = {
key: "september",
translationKey: "months.september"
translationKey: "months.september",
audio: {
briefing: "september",
voice: "Dr. Foster"
}
};
// November is stage 7
MONTHS_CONFIG[7] = {
key: "november",
translationKey: "months.november"
translationKey: "months.november",
audio: {
briefing: "november",
voice: "Dr. Lee"
}
};
// December is stage 8
MONTHS_CONFIG[8] = {
key: "december",
translationKey: "months.december"
translationKey: "months.december",
audio: {
briefing: "december",
voice: "Dr. Hayes"
}
};
// Exposé is stage 9
MONTHS_CONFIG[9] = {
key: "exposé",
translationKey: "months.exposé"
translationKey: "months.exposé",
audio: {
briefing: "expose",
voice: "Dr. Williams"
}
};
// Utility function to get month config - now much simpler!
export function getMonthConfig(stage: string | number): MonthConfig | undefined {
// Utility function to get month config - now supports language selection
export function getMonthConfig(stage: string | number, language = 'en'): MonthConfig | undefined {
const stageNum = typeof stage === 'string' ? parseInt(stage) : stage;
return MONTHS_CONFIG[stageNum];
const config = MONTHS_CONFIG[stageNum];
if (config && config.audio) {
// Create a new object with language-specific briefing path
return {
...config,
audio: {
...config.audio,
briefing: `${config.audio.briefing}-${language}.mp3`
}
};
}
return config;
}

Просмотреть файл

@ -1,7 +1,13 @@
export const VERSION = {
current: '0.4.1',
releaseDate: '2024-12-16',
current: '0.5.0',
releaseDate: '2025-03-16',
changelog: {
'0.5.0 ': [
'Improved NewsAnimation with realistic article snippets and sources',
'Fixed text clarity issues in IntroDialog component',
'Fixed variable redeclaration in RecruitAcademiaAnimation',
'Enhanced animation layouts for better user experience',
],
'0.4.1': [
'Month index fixes and consolidation with stage index',
'Enhanced metrics and KPI visualization',