Merge branch 'develop' into card_tweaks

This commit is contained in:
Blade Barringer 2015-08-14 17:29:17 -05:00
commit 80a6b2dcf9
74 changed files with 725 additions and 511 deletions

View file

@ -85,7 +85,7 @@
"fortifyName": "Posilňující lektvar",
"fortifyPop": "Obnoví všechny úkoly do neutrální (žluté) barvy a obnoví ztracené zdraví.",
"fortify": "Posilnění",
"fortifyText": "Posilnění vrátí všechny tvé úkoly do neutrálního (žlutého) stavu, ve kterém byly v okamžitku přidání, a doplní zdraví na maximum. Považuj to za poslední možnou variantu. Červené úkoly poskytují dobrou motivaci ke zlepšení. Ale pokud tě všechna ta červená naplňuje zoufalstvím a začátek každého dne se ukáže jako smrtící, investuj drahokamy a oddechni si.",
"fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
"sureDelete": "Určitě chceš odstranit tento úkol?",
"streakCoins": "Bonus za šňůru!",
"pushTaskToTop": "Přesunout úkol na vrchol",

View file

@ -85,7 +85,7 @@
"fortifyName": "Forstærkningseliksir",
"fortifyPop": "Returnerer alle opgaver til neutral værdi (gul farve) og giver alt tabt helbred tilbage.",
"fortify": "Forstærk",
"fortifyText": "Forstærk vil returnere alle dine opgaver til en neutral (gul) værdi, som om du lige har tilføjet dem, og give dig fuldt Helbred tilbage. Tænk på dette som en sidste mulighed! Røde opgaver tilskynder dig til at forbedre dig. Men hvis al den røde farve gør dig trist, og en ny dag er dødbringende, brug ædelstenene og bliv benådet!",
"fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
"sureDelete": "Er du sikker på, at du vil slette denne opgave?",
"streakCoins": "Stribebonus!",
"pushTaskToTop": "Flyt opgave til toppen",

View file

@ -2,7 +2,7 @@
"FAQ": "FAQ",
"accept1Terms": "Durch Klicken auf diesen Knopf, stimme ich den",
"accept2Terms": "zu, sowie der",
"alexandraQuote": "Couldn't NOT talk about [Habitica] during my speech in Madrid. Must-have tool for freelancers who still need a boss.",
"alexandraQuote": "Ich konnte während meiner Rede in Madrid nicht NICHT über [Habitica] reden. Ein absolutes Must-have Hilfsmittel für Freiberufler, die trotzdem einen Chef brauchen.",
"althaireQuote": "Kontinuierlich eine Quest zu haben, motiviert mich dazu alle meine täglichen und einmaligen Aufgaben zu erledigen. Meine größte Motivation ist, meine Gruppe nicht im Stich zu lassen.",
"andeeliaoQuote": "Großartiges Produkt, ich habe gerade erst vor einigen Tagen angefangen und gehe jetzt schon bewusster und produktiver mit meiner Zeit um!",
"autumnesquirrelQuote": "Ich schiebe bei der Arbeit und im Haushalt weniger auf und zahle meine Rechnungen rechtzeitig. ",
@ -38,7 +38,7 @@
"elmiQuote": "Jeden Morgen freue ich mich aufzustehen und etwas Gold zu verdienen!",
"email": "E-Mail",
"emailNewPass": "E-Mail neues Passwort",
"evagantzQuote": "My very first dentist appointment where the hygienist was actually excited about my flossing habits. Thanks [Habitica]!",
"evagantzQuote": "Mein erster Zahnarztbesuch, bei dem die Assistentin tatsächlich begeistert über meine Zahnseide-Gewohnheiten war. Danke [Habitica]!",
"examplesHeading": "Spieler benutzen Habitica, um folgendes zu organisieren...",
"featureAchievementByline": "Etwas total großartiges gemacht? Erhalte ein Abzeichen und prahle damit!",
"featureAchievementHeading": "Erfolgsabzeichen",
@ -55,7 +55,7 @@
"footerMobile": "Mobil",
"footerSocial": "Soziales",
"forgotPass": "Passwort vergessen",
"frabjabulousQuote": "[Habitica] is the reason I got a killer, high-paying job... and even more miraculous, I'm now a daily flosser!",
"frabjabulousQuote": "[Habitica] ist der Grund, warum ich einen tollen, gut bezahlten Job gefunden habe... und noch erstaunlicher ist, dass ich jetzt täglich Zahnseide benutze!",
"free": "Kostenlos spielen",
"gamifyButton": "Gestalte dein Leben spielerisch!",
"goalSample1": "1 Stunde Klavier üben",
@ -73,9 +73,9 @@
"history": "Verlauf",
"infhQuote": "[Habitica] hat mir geholfen Struktur in mein Leben an der Universität zu bringen.",
"invalidEmail": "Um das Passwort zurückzusetzen, ist eine gültige Email-Adresse notwendig.",
"irishfeet123Quote": "I've had horrible habits with clearing my place completely after meals and leaving cups all over the place. [Habitica] has cured that!",
"irishfeet123Quote": "Ich hatte die schrecklichen Angewohnheiten, nach Mahlzeiten nicht aufzuräumen und Tassen in der ganzen Wohnung stehen zu lassen. [Habitica] hat mich davon geheilt!",
"joinOthers": "Schließe dich 250.000 Leuten an, die Spaß dabei haben, ihre Ziele zu erreichen!",
"kazuiQuote": "Before [Habitica], I was stuck with my thesis, as well as dissatisfied with my personal discipline regarding housework and things like learning vocabulary and studying Go theory. It turns out breaking down these tasks into smaller manageable checklists is quite the thing to keep me motivated and constantly working.",
"kazuiQuote": "Vor [Habitica] kam ich mit meiner Dissertation nicht weiter und war unzufrieden mit meiner Selbstdisziplin bei Hausarbeiten, Vokabellernen und dem Studium der Go-Theorie. Es hat sich herausgestellt, dass das Aufteilen der Aufgaben in kleinere, machbare Checklisten etwas ist, das mich motiviert und zum konstanten Arbeiten anregt.",
"landingadminlink": "Verwaltungspakete",
"landingend": "Noch nicht überzeugt?",
"landingend2": "Hier ist eine genauere Liste",

View file

@ -42,7 +42,7 @@
"customDayStart": "Tageswechsel einstellen",
"24HrClock": "24h Uhr",
"customDayStartInfo1": "Habitica ist so eingestellt, dass Deine täglichen Aufgaben um Mitternacht in deiner eigenen Zeitzone überprüft und zurückgesetzt werden. Wir empfehlen den folgenden Text zu lesen, bevor Du diese Einstellungen veränderst:",
"customDayStartInfo4": "<strong>Complete all your Dailies before changing the Custom Day Start</strong> or <a href='http://habitica.wikia.com/wiki/Rest_in_the_Inn' target='_blank'>Rest in the Inn</a> that day. Changing your Custom Day Start may cause <a href='http://habitica.wikia.com/wiki/Cron' target='_blank'>Cron</a> to run immediately, but after the first day it works as expected.<br><br><strong>Allow a window of two hours for the change to take effect.</strong> For example, if it is currently set to 0 (midnight), change it before 10pm; if you want to set it to 9pm, change it before 7pm.<br><br>Enter an hour from 0 to 23 (it uses a 24 hour clock). Typing is more effective than arrow keys. Once set, reload the page to confirm that the new value is being displayed.",
"customDayStartInfo4": "<strong>Beende alle deine täglichen Aufgaben bevor du deinen persönlichen Tagesstart änderst</strong> oder checke für diesen Tag im <a href='http://habitica.wikia.com/wiki/Rest_in_the_Inn' target='_blank'>Gasthaus</a> ein. Das Ändern deines Tagesstarts kann bewirken, dass dein <a href='http://habitica.wikia.com/wiki/Cron' target='_blank'>Cron</a> sofort ausgeführt wird, aber nach dem ersten Tag funktioniert alles wie erwartet.<br><br><strong>Plane ein Zeitfenster von zwei Stunden ein, um die Änderungen wirksam werden zu lassen.</strong> Wenn die Zeit bisher beispielsweise auf 0 (Mitternacht) gesetzt ist, ändere sie vor 22 Uhr; Wenn du sie auf 21 setzen willst, ändere sie vor 19 Uhr.<br><br>Gib eine Zahl zwischen 0 und 23 an. Tippen ist effektiver als das Benutzen der Pfeiltasten. Wenn du die Zeit gesetzt hast, lade die Seite neu, um sicherzustellen, dass der neue Wert angezeigt wird.",
"misc": "Verschiedenes",
"showHeader": "Header anzeigen",
"changePass": "Passwort ändern",
@ -74,7 +74,7 @@
"usernameSuccess": "Dein Login Name wurde erfolgreich geändert!",
"emailSuccess": "Die E-Mail-Adresse wurde erfolgreich geändert",
"detachFacebook": "Facebook Registrierung löschen",
"detachedFacebook": "Successfully removed Facebook from your account",
"detachedFacebook": "Facebook erfolgreich von deinem Konto entfernt",
"addedLocalAuth": "Successfully added local authentication",
"data": "Daten",
"exportData": "Daten exportieren",
@ -115,7 +115,7 @@
"couponText": "Manchmal verteilen wir auf Events Coupon-Codes für spezielle Ausrüstung (z.B. an unserem WonderCon-Stand)",
"apply": "Anwenden",
"resubscribe": "Wieder abonnieren",
"promoCode": "Promo Code",
"promoCode": "Aktionscode",
"promoCodeApplied": "Promo Code aktiviert! Überprüfe dein Inventar",
"promoPlaceholder": "Promo Code eingeben",
"displayInviteToPartyWhenPartyIs1": "Zeige \"In Gruppe einladen\"-Schaltfläche an, wenn die Gruppe nur 1 Mitglied hat."

View file

@ -85,7 +85,7 @@
"fortifyName": "Verstärkungstrank",
"fortifyPop": "Setzt alle Aufgaben auf den Anfangswert (gelb) zurück und füllt Deine Lebenspunkte wieder auf.",
"fortify": "Verstärken",
"fortifyText": "Verstärken wird alle Deine Aufgaben auf ihren Anfangswert (gelb) zurücksetzen, so als ob Du sie gerade erst Hinzugefügt hättest und füllt Deine Lebenspunkte wieder auf. Sieh' das als letzten Ausweg! Rote Aufgaben bieten einen guten Anreiz sich zu bessern. Aber wenn zu viel Rot Dich zur Verzweiflung treibt, und jeder Tag tödlich endet, dann investiere Deine Edelsteine hier und hol' erstmal Luft!",
"fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
"sureDelete": "Bist Du sicher, dass Du diese Aufgabe löschen möchtest?",
"streakCoins": "Strähnen Bonus!",
"pushTaskToTop": "Aufgabe nach oben verschieben",

View file

@ -43,7 +43,6 @@
"contributions": "Contributions",
"admin": "Admin",
"notGems": "is in USD, <em>not</em> in Gems. Aka, if this number is 1, it means 4 gems. Only use this option when manually granting gems to players, don't use it when granting contributor tiers. Contrib tiers will automatically add gems.",
"hideAds": "Hide Ads",
"gamemaster": "Game Master (staff/moderator)",
"backerTier": "Backer Tier",
"balance": "Balance",
@ -53,11 +52,6 @@
"visitHeroes": "Visit the Hall of Heroes (contributors and backers)",
"conLearn": "Learn more about contributor rewards",
"conLearnHow": "Learn how to contribute to Habitica",
"removeAds": "Subscribe to remove ads",
"whyAds": "Why Ads?",
"whyAdsContent1": "Habit is an open source project, and can use all the help it can get - consider this a donation to the contributors. You also get 20 Gems from the purchase, which you can use to buy special items.",
"whyAdsContent2": "'Hey, I backed the Kickstarter!' - follow",
"whyAdsContent3": "these instructions",
"surveysSingle": "Helped Habitica grow by filling out a survey. There are no active surveys.",
"surveysMultiple": "Helped Habitica grow by filling out <%= surveys %> surveys. There are no active surveys.",
"currentSurvey": "Current Survey",

View file

@ -116,19 +116,19 @@
"greetingCard": "Greeting Card",
"greetingCardExplanation": "You both receive the Cheery Chum achievement!",
"greetingCardNotes": "Send a greeting card to a party member.",
"greeting0": "\"Hi there!\"",
"greeting1": "\"Just saying hello :)\"",
"greeting2": "\"`waves frantically`\"",
"greeting3": "\"Hey you!\"",
"greeting0": "Hi there!",
"greeting1": "Just saying hello :)",
"greeting2": "`waves frantically`",
"greeting3": "Hey you!",
"greetingCardAchievementTitle": "Cheery Chum",
"greetingCardAchievementText": "Hey! Hi! Hello! Sent or received <%= cards %> greeting cards.",
"thankyouCard": "Thank-You Card",
"thankyouCardExplanation": "You both receive the Greatly Grateful achievement!",
"thankyouCardNotes": "Send a Thank-You card to a party member.",
"thankyou0": "\"Thank you very much!\"",
"thankyou1": "\"Thank you, thank you, thank you!\"",
"thankyou2": "\"Sending you a thousand thanks.\"",
"thankyou3": "\"I'm very grateful - thank you!\"",
"thankyou0": "Thank you very much!",
"thankyou1": "Thank you, thank you, thank you!",
"thankyou2": "Sending you a thousand thanks.",
"thankyou3": "I'm very grateful - thank you!",
"thankyouCardAchievementTitle": "Greatly Grateful",
"thankyouCardAchievementText": "Thanks for being thankful! Sent or received <%= cards %> Thank-You cards."
}

View file

@ -41,11 +41,11 @@
"seasonalItems": "Seasonal Items",
"nyeCardAchievementTitle": "Auld Acquaintance",
"nyeCardAchievementText": "Happy New Year! Sent or received <%= cards %> New Year's cards.",
"nye0": "\"Happy New Year! May you slay many a bad Habit.\"",
"nye1": "\"Happy New Year! May you reap many Rewards.\"",
"nye2": "\"Happy New Year! May you earn many a Perfect Day.\"",
"nye3": "\"Happy New Year! May your To-Do list stay short and sweet.\"",
"nye4": "\"Happy New Year! May you not get attacked by a raging Hippogriff.\"",
"nye0": "Happy New Year! May you slay many a bad Habit.",
"nye1": "Happy New Year! May you reap many Rewards.",
"nye2": "Happy New Year! May you earn many a Perfect Day.",
"nye3": "Happy New Year! May your To-Do list stay short and sweet.",
"nye4": "Happy New Year! May you not get attacked by a raging Hippogriff.",
"holidayCard": "Received a holiday card!",
"mightyBunnySet": "Mighty Bunny (Warrior)",
"magicMouseSet": "Magic Mouse (Mage)",

View file

@ -1,9 +1,7 @@
{
"subscription": "Subscription",
"subscriptions": "Subscriptions",
"subDescription": "Disable ads, buy gems with gold, monthly mystery item, retain progress history, double daily drop-caps, supports the devs. Click for more info.",
"disableAds": "Disable ads",
"disableAdsText": "Ads are disabled while you have an active subscription (original donors are grandfathered).",
"subDescription": "Buy gems with gold, get monthly mystery items, retain progress history, double daily drop-caps, support the devs. Click for more info.",
"buyGemsGold": "Buy Gems with Gold",
"buyGemsGoldText": "Alexander the Merchant will sell you gems at a cost of <%= gemCost %> gold per gem. His monthly shipments are initially capped at <%= gemLimit %> gems per month, but this cap increases by 5 gems for every three months of consecutive subscription, up to a maximum of 50 gems per month!",
"retainHistory": "Retain full history entries",
@ -47,7 +45,6 @@
"timeSupport": "Support Hours / Month",
"timeSupportText": "We will provide support for training, bugs, installation, and feature requests.",
"gameFeatures": "Game features",
"gameNoAds": "Ads disabled for members",
"gold2Gem": "Gems purchasable with gold",
"gold2GemText": "Members will be able to purchase gems with gold, meaning none of your participants need to buy anything with real money.",
"infiniteGem": "Infinite leader gems",
@ -55,7 +52,6 @@
"notYetPlan": "Plan not yet available, but click to contact us and we'll keep you updated.",
"contactUs": "Contact Us",
"checkout": "Checkout",
"buySubsText": "Buy gems with Gold, No Ads, Support the Devs",
"sureCancelSub": "Are you sure you want to cancel your subscription?",
"subCanceled": "Subscription will become inactive on",
"subGemPop": "Because you subscribe to Habitica, you can purchase a number of Gems each month using Gold. You can see how many Gems are available to buy at the corner of the Gem icon.",

View file

@ -12,10 +12,10 @@
"aquaticFriendsText": "Got splashed <%= seafoam %> times by crew members.",
"valentineCard": "Valentine's Day Card",
"valentineCardNotes": "Send a Valentine's Day card to a party member.",
"valentine0": "\"Roses are red\n\nMy Dailies are blue\n\nI'm happy that I'm\n\nIn a Party with you!\"",
"valentine1": "\"Roses are red\n\nViolets are nice\n\nLet's get together\n\nAnd fight against Vice!\"",
"valentine2": "\"Roses are red\n\nThis poem style is old\n\nI hope that you like this\n\n'Cause it cost ten Gold.\"",
"valentine3": "\"Roses are red\n\nIce Drakes are blue\n\nNo treasure is better\n\nThan time spent with you!\"",
"valentine0": "\"Roses be red\n\nMy Dailies be blue\n\nI'm happy that I'm\n\nIn a Crew wit' ye!\"",
"valentine1": "\"Roses be red\n\nViolets be nice\n\nLet's get together\n\nAn' fight against Vice!\"",
"valentine2": "\"Roses be red\n\nThis poem style be old\n\nI hope that ye like this\n\n'Cause it cost ten Gold.\"",
"valentine3": "\"Roses be red\n\nIce Drakes be blue\n\nNo treasure be better\n\nThan time spent with' ye!\"",
"adoringFriends": "Adorin' Mates",
"adoringFriendsText": "Avast; ye an' yer mate must really care 'bout each other! Sent er received <%= cards %> Valentine's Day cards.",
"polarBear": "Polar Bear",
@ -25,7 +25,7 @@
"seasonalShop": "Seasonal Shop",
"seasonalShopClosedTitle": "<%= linkStart %>Siena Leslie<%= linkEnd %>",
"seasonalShopTitle": "<%= linkStart %>Seasonal Sorceress<%= linkEnd %>",
"seasonalShopClosedText": "The Seasonal Shop is currently closed!! I don't know where the Seasonal Sorceress is now, but I bet she'll be back during the next <a href='http://habitica.wikia.com/wiki/Grand_Galas' target='_blank'>Grand Gala</a>!",
"seasonalShopClosedText": "The Seasonal Shop currently be closed!! I don't know where th' Seasonal Sorceress be now, but I bet she'll be back during th' next <a href='http://habitica.wikia.com/wiki/Grand_Galas' target='_blank'>Grand Gala</a>!",
"seasonalShopText": "Welcome t' th' Seasonal Shop!! We're stockin' springtime <a href='http://habitica.wikia.com/wiki/Item_Availability' target='_blank'>Seasonal Edition</a> goodies at th' moment. Everything here will be available t' purchase durin' th' Spring Fling event each year, but we're only open until April 30th, so be sure t' stock up now, or you'll have t' wait a year t' buy these items again!",
"seasonalShopSummerText": "Welcome t' th' Seasonal Shop!! We be stockin' summertime <a href='http://habitica.wikia.com/wiki/Item_Availability' target='_blank'>Seasonal Edition</a> goodies at th' moment. Everything here will be available t' purchase during th' Summer Splash event each year, but we're only open until July 31st, so be sure t' stock up now, or you'll have t' wait a year t' buy these items again!",
"seasonalShopRebirth": "If ye've used th' Orb o' Rebirth, ye can buy this gear in th' Loot Column after ye've unlocked the Item Shop. At first, ye'll only be able to buy the items fer yer current class (Mercenary by default), but remain stout o' heart, ye can have the other classes' stuff if ye switch to that class.",

View file

@ -85,7 +85,7 @@
"fortifyName": "Fortify Potion",
"fortifyPop": "Return all tasks t' neutral value (yellow color), 'n restore all lost Health.",
"fortify": "Fortify",
"fortifyText": "Fortify gunna return all ye tasks to a neutral (yellow) state, as if ye'd just added them, 'n top ye Health off to full. Consider 'tis an option 'o last resort! Red tasks provide jolly incentive to improve. But if all that red fills ye wit' despair, 'n th' beginnin' 'o each new day proves lethal, spend th' Gems 'n catch a reprieve!",
"fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
"sureDelete": "Arrr ye sure ye want t' delete this task?",
"streakCoins": "Streak Bonus!",
"pushTaskToTop": "Push task to top",

View file

@ -85,7 +85,7 @@
"fortifyName": "Fortify Potion",
"fortifyPop": "Return all tasks to neutral value (yellow colour), and restore all lost Health.",
"fortify": "Fortify",
"fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. Consider this an option of last resort! Red tasks provide good incentive to improve. But if all that red fills you with despair, and the beginning of each new day proves lethal, spend the Gems and catch a reprieve!",
"fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
"sureDelete": "Are you sure you want to delete this task?",
"streakCoins": "Streak Bonus!",
"pushTaskToTop": "Push task to top",

View file

@ -85,7 +85,7 @@
"fortifyName": "Poción de Fortalecimiento.",
"fortifyPop": "Devuelve todas sus tareas a un valor neutral (amarillo), y recupera toda la salud perdida.",
"fortify": "Fortalecer",
"fortifyText": "Fortalecer devuelve todas tus tareas a un valor neutral (amarillo), como si las acabaras de añadir, y pone a tope tu salud. ¡Considera esto como tu última opción! Las tareas rojas proporcionan un incentivo para mejorar. Si tanto rojo te llena de deseperación y el comienzo de cada día se vuelve mortal, gasta las gemas y tomate un suspiro.",
"fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
"sureDelete": "¿Estás seguro de que quieres eliminar esta tarea?",
"streakCoins": "¡Bonus de Racha!",
"pushTaskToTop": "Desplazar tarea al inicio",

View file

@ -85,7 +85,7 @@
"fortifyName": "Poción de fortalecimiento.",
"fortifyPop": "Devuelve todas tus tareas a un valor neutral (amarillo), y recupera toda la salud perdida.",
"fortify": "Fortalecer",
"fortifyText": "Fortalecer devuelve todas tus tareas a un valor neutral (amarillo), como si las acabaras de añadir, y llena a tope tu salud. ¡Considera esto como tu última opción! Las tareas rojas proporcionan un incentivo para mejorar. ¡Si tanto rojo te llena de desesperación y el comienzo de cada día resulta fatal, gasta las gemas y tómate un suspiro!",
"fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
"sureDelete": "¿Estás seguro de que quieres eliminar esta tarea?",
"streakCoins": "¡Bonus de racha!",
"pushTaskToTop": "Enviar tarea al tope",

View file

@ -34,7 +34,7 @@
"companyVideos": "Vidéos",
"contribUse": "Utilisation exclusive aux contributeurs Habitica",
"dragonsilverQuote": "Je ne peux vous dire combien d'outils de gestion du temps et des tâches j'ai essayé au cours des années ... [Habitica] est le seul outil que j'utilise qui m'a aidé à atteindre mes objectifs au lieu de simplement les lister.",
"dreimQuote": "Quand j'ai découvert [Habitica] le printemps dernier, je venais juste de rater la moitié de mes examens. Grâce aux Quotidiennes&mldr; j'ai réussi à m'organiser et à me discipliner, et j'ai finalement passé mes examens avec de bons résultats il y a un mois.",
"dreimQuote": "Quand j'ai découvert [Habitica] l'été dernier, je venais juste de rater la moitié de mes examens. Grâce aux Quotidiennes&mldr; j'ai réussi à m'organiser et à me discipliner, et, il y a un mois, j'ai finalement passé mes examens avec de bons résultats.",
"elmiQuote": "Chaque matin, je me réjouis de me lever pour aller gagner des pièces d'or!",
"email": "E-mail",
"emailNewPass": "Envoyer un nouveau mot de passe par e-mail",
@ -55,7 +55,7 @@
"footerMobile": "Mobile",
"footerSocial": "Social",
"forgotPass": "Mot de passe oublié",
"frabjabulousQuote": "[Habitica] est la pour laquelle j'ai eu un boulot très bien rémunéré ... et encore plus fort, j'utilise du fil dentaire tous les jours !",
"frabjabulousQuote": "[Habitica] est la raison pour laquelle j'ai eu un boulot très bien rémunéré ... et encore plus fort, j'utilise du fil dentaire tous les jours !",
"free": "Rejoindre gratuitement",
"gamifyButton": "Jouez le jeu de votre vie dès aujourd'hui!",
"goalSample1": "S'exercer au piano 1h",

View file

@ -85,7 +85,7 @@
"fortifyName": "Potion de Fortification",
"fortifyPop": "Fait revenir toutes les tâches à une valeur neutre (couleur jaune) et restaure tous les points de santé que vous aviez perdus.",
"fortify": "Fortification",
"fortifyText": "La potion de fortification ramènera toutes vos tâches à un niveau neutre (jaune), comme si vous veniez de les ajouter, et remplira votre barre de santé. N'utilisez cette option qu'en dernier ressort ! Les tâches de couleur rouge sont une bonne invitation à s'améliorer. Mais si voir tout ce rouge vous remplit de désespoir et que chaque nouveau jour vous semble un danger mortel, dépenser vos gemmes et accordez vous un sursis !",
"fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
"sureDelete": "Êtes-vous sûr de vouloir supprimer cette tâche ?",
"streakCoins": "Bonus de combo !",
"pushTaskToTop": "Déplacer la tâche en tête de liste",

View file

@ -85,7 +85,7 @@
"fortifyName": "שיקוי תגבור",
"fortifyPop": "מחזיר את כל המשימות לערכן ההתחלתי (צבע צהוב) ומחזיר את כל הבריאות שאיבדת.",
"fortify": "תגבור",
"fortifyText": "תגבור יחזיר את כל המשימות שלך למצבן הטבעי (צהוב), כאילו הוספת אותן עכשיו, וימלא את הבריאות שלך עד הסוף. חשוב/חשבי על זה כעל מוצא אחרון! משימות אדומות מספקות תמריץ טוב להשתפר. במקרה חמור, בו כל הצבע האדום הזה ממלא את ליבך ביאוש ויגון, ותחילתו של כל יום מהווה מכה קטלנית עבורך, שלם/י את אבני החן וקבל/י קצת אוויר לנשימה!",
"fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
"sureDelete": "בטוח/ה שיש צורך למחוק את המשימה הזו?",
"streakCoins": "בונוס התמדה!",
"pushTaskToTop": "דחיפת המשימה למעלה",

View file

@ -85,7 +85,7 @@
"fortifyName": "Erősítőfőzet",
"fortifyPop": "Visszaállít minden feladatot semleges értékűre (sárga színű), és feltölti az életerőt.",
"fortify": "Megerősít",
"fortifyText": "Az Erősítés visszaállítja minden feladatodat semleges (sárga) állapotba, mintha csak most adtad volna őket hozzá, és feltölti az életerődet. Vedd úgy, hogy ez az utolsó lehetőség, amit végveszélyben használhatsz. A vörös feladatok arra bíztatnak, hogy fejleszd őket, de ha minden vörös feladat kétségbeeséssel tölt el és minden új nap halálosnak bizonyul, akkor költsd el a Drágaköveket és függeszd fel a halálbűntetést!",
"fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
"sureDelete": "Biztos vagy benne, hogy törölni akarod ezt a feladatot?",
"streakCoins": "Széria bónusz!",
"pushTaskToTop": "Tetejére",

View file

@ -85,7 +85,7 @@
"fortifyName": "Pozione di Fortificazione",
"fortifyPop": "Fa tornare tutte le attività al valore neutro (colore giallo) e ripristina tutti i punti vita persi.",
"fortify": "Fortifica",
"fortifyText": "La fortificazione farà tornare tutte le attività allo stato neutro (colore giallo), proprio come se le avessi appena create, e riempirà la tua barra della vita. Considerala come ultima risorsa se la situazione si fa difficile! Le attività in rosso sono un buon incentivo per migliorare; tuttavia, se tutto quel rosso comincia a preoccuparti e l'inizio di ogni nuovo giorno rischia di essere fatale, spendi qualche gemma e datti un'altra opportunità!",
"fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
"sureDelete": "Vuoi davvero eliminare questa attività?",
"streakCoins": "Bonus serie!",
"pushTaskToTop": "Metti in cima alla lista",

View file

@ -85,7 +85,7 @@
"fortifyName": "強化ポーション",
"fortifyPop": "全てのタスクを通常の状態 (黄色)に戻します。加えて全ての体力を回復させます。",
"fortify": "強化",
"fortifyText": "この強化は今追加したかのように、全てのタスクを中立黄色の状態に戻します。さらにあなたのHealthを全回復させます。このオプションは最後の手段だと考えてください赤いタスクは成長のための良い動機を与えてくれます。しかし全ての赤いタスクがあなたを絶望でいっぱいにしてしまい、毎日の始まりが死に至るほどの苦しみであるならば、ジェムを支払って救済を得ましょう",
"fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
"sureDelete": "本当にこのタスクを削除しても良いですか?",
"streakCoins": "連続実行ボーナス!",
"pushTaskToTop": "タスクを一番上に上げる",

View file

@ -12,10 +12,10 @@
"aquaticFriendsText": "Is <%= seafoam %> keer natgespetterd door groepsleden.",
"valentineCard": "Valentijnskaart",
"valentineCardNotes": "Stuur een Valentijnskaart naar een groepsgenoot.",
"valentine0": "\"Roses are red\n\nMy Dailies are blue\n\nI'm happy that I'm\n\nIn a Party with you!\"",
"valentine1": "\"Roses are red\n\nViolets are nice\n\nLet's get together\n\nAnd fight against Vice!\"",
"valentine2": "\"Roses are red\n\nThis poem style is old\n\nI hope that you like this\n\n'Cause it cost ten Gold.\"",
"valentine3": "\"Roses are red\n\nIce Drakes are blue\n\nNo treasure is better\n\nThan time spent with you!\"",
"valentine0": "\"Rozen zijn rood\nTaken zijn blauw\nIk ben blij dat ik in\nEen groep zit met jou!\"",
"valentine1": "\"Rozen zijn rood\nTakken zijn stijf\nSamen gaan wij\nOndeugd te lijf!\"",
"valentine2": "\"Rozen zijn rood\nDie dichtstijl is oud\nIk hoop dat je 't leuk vindt\nHet kostte 10 goud.\"",
"valentine3": "\"Rozen zijn rood\nEn ijsdraken blauw\nGeen schat is me liever\nDan mijn tijd met jou!\"",
"adoringFriends": "Liefhebbende vrienden",
"adoringFriendsText": "Aww, jij en je vriend moeten wel heel erg veel om elkaar geven! Heeft <%= cards %> Valentijnskaarten verzonden of ontvangen.",
"polarBear": "IJsbeer",

View file

@ -85,7 +85,7 @@
"fortifyName": "Versterkingsdrankje",
"fortifyPop": "Geeft alle taken een neutrale waarde (gele kleur), en herstelt alle verloren gezondheidspunten.",
"fortify": "Versterk",
"fortifyText": "Versterken brengt al je taken terug tot een neutrale (gele) staat, alsof je ze net toegevoegd hebt, en tevens wordt je gezondheidsbalk weer helemaal gevuld. Beschouw dit als een laatste redmiddel! Rode taken zijn een goede motivatie om beter je best te doen. Maar als al dat rood je wanhopig maakt en het begin van elke dag dodelijk lijkt, geef dan een paar edelstenen uit aan een uitstel van executie!",
"fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
"sureDelete": "Weet je zeker dat je deze taak wilt verwijderen?",
"streakCoins": "seriebonus!",
"pushTaskToTop": "Taak bovenaan zetten",

View file

@ -85,7 +85,7 @@
"fortifyName": "Mikstura wzmocnienia",
"fortifyPop": "Przywraca wszystkie zadania do neutralnej wartości (żółty kolor), oraz uzdrawia całe stracone Zdrowie.",
"fortify": "Wzmocnij",
"fortifyText": "Wzmocnienie przywróci wszystkie zadania do wartości neutralnej (żółty kolor), tak jakbyś dopiero co je dodał, a w dodatku uzdrowi cię do pełna. Traktuj tą opcję jako ostatnią deskę ratunku! Czerwone zadania zapewniają dobrą motywację do poprawienia się. Jednak jeśli te wszystkie czerwone zadania przyprawiają cię o rozpacz, a rozpoczęcie każdego nowego dnia jest śmiertelnie niebezpieczne, poświęć Klejnoty i poczuj ulgę!",
"fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
"sureDelete": "Jesteś pewien, że chcesz usunąć to zadanie?",
"streakCoins": "Premia za serię!",
"pushTaskToTop": "Prześlij zadanie na górę",

View file

@ -85,7 +85,7 @@
"fortifyName": "Poção de Fortificação",
"fortifyPop": "Reverte todas tarefas para o valor neutro (cor amarela), e recupera toda Vida perdida.",
"fortify": "Fortificar",
"fortifyText": "Fortificar reverterá todas suas tarefas para o valor neutro (amarelo), como se tivesse acabado de adicioná-las, e completará sua vida até estar cheia. Considere essa opção como último recurso! Tarefas vermelhas oferecem um grande incentivo para melhorar. Mas se todo vermelho o encher de desespero, e o começo de cada dia provar ser letal, gaste as Gemas e pegue um adiantamento!",
"fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
"sureDelete": "Tem certeza de que deseja deletar essa tarefa?",
"streakCoins": "Bônus de Combo!",
"pushTaskToTop": "Enviar tarefa para o topo",

View file

@ -85,7 +85,7 @@
"fortifyName": "Poțiune fortifiantă",
"fortifyPop": "Readu toate țelurile la valoarea neutră (culoarea galbenă) și refă toată Sănătatea pierdută.",
"fortify": "Fortifică",
"fortifyText": "Fortifierea va readuce toate țelurile la valoarea neutră (galben), ca și cum abia le-ai fi adăugat, și va reface Sănătatea la maxim. Consideră asta o opțiune de caz extrem! Sarcinile roșii furnizează un bun imbold pentru a progresa. Dar dacă tot roșul ăsta te umple cu disperare, și începutul fiecărei zile se dovedește letal, cheltuie Nestematele și ia o pauză!",
"fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
"sureDelete": "Ești sigur că vrei să ștergi acest țel?",
"streakCoins": "Bonus de realizare în șir!",
"pushTaskToTop": "Urcă sarcina în vârf",

View file

@ -443,7 +443,7 @@
"headArmoireBlueHairbowText": "Синий бант",
"headArmoireBlueHairbowNotes": "Станьте восприимчивым, стойким и находчивым, надев этот превосходный синий бант! Увеличивает восприятие на <%= per %>, телосложение на <%= con %> и интеллект на <%= int %>. Волшебный сундук: независимый предмет.",
"headArmoireRoyalCrownText": "Королевская корона",
"headArmoireRoyalCrownNotes": "Да здравствует правитель, могучий и сильный!Увеличивает силу на <%= str %>. Волшебный сундук: независимый предмет.",
"headArmoireRoyalCrownNotes": "Да здравствует правитель, могучий и сильный! Увеличивает силу на <%= str %>. Волшебный сундук: независимый предмет.",
"headArmoireGoldenLaurelsText": "Золотые лавры",
"headArmoireGoldenLaurelsNotes": "These golden laurels reward those who have conquered bad habits. Increases Perception and Constitution by <%= attrs %> each. Enchanted Armoire: Golden Toga Set (Item 2 of 3).",
"headArmoireHornedIronHelmText": "Шлем Рогатого железа",

View file

@ -13,7 +13,7 @@
"valentineCard": "Валентинка",
"valentineCardNotes": "Отправьте валентинку члену команды.",
"valentine0": "\"Roses are red\n\nMy Dailies are blue\n\nI'm happy that I'm\n\nIn a Party with you!\"",
"valentine1": "\"Roses are red\n\nViolets are nice\n\nLet's get together\n\nAnd fight against Vice!\"",
"valentine1": "«Розы алеют,\n\nБелеет сирень,\n\nВместе поборем\n\nПороки и лень!»",
"valentine2": "\"Roses are red\n\nThis poem style is old\n\nI hope that you like this\n\n'Cause it cost ten Gold.\"",
"valentine3": "\"Roses are red\n\nIce Drakes are blue\n\nNo treasure is better\n\nThan time spent with you!\"",
"adoringFriends": "Любимые друзья",

View file

@ -74,8 +74,8 @@
"usernameSuccess": "Имя пользователя успешно изменено",
"emailSuccess": "Адрес электронной почты успешно изменен",
"detachFacebook": "Восстановить регистрацию Facebook",
"detachedFacebook": "Successfully removed Facebook from your account",
"addedLocalAuth": "Successfully added local authentication",
"detachedFacebook": "Facebook успешно отсоединен вашего аккаунта",
"addedLocalAuth": "Локальная идентификация успешно добавлена",
"data": "Данные",
"exportData": "Экспорт данных",
"emailChange1": "Чтобы изменить ваш email, пожалуйста, отправьте email на",

View file

@ -85,7 +85,7 @@
"fortifyName": "Эликсир укрепления",
"fortifyPop": "Вернуть все задания в нейтральное состояние (желтый цвет) и восстановить всё здоровье.",
"fortify": "Укрепление",
"fortifyText": "Укрепление вернет все задания в нейтральное (желтое) состояние, как если бы вы только что добавили их, и восстановит здоровье до максимума. Считайте это крайней мерой! Красные задания дают хороший стимул для улучшения. Но если вы чувствуете, что вся эта краснота вселяет в вас безысходность, а начало каждого нового дня угрожает смертью, вы можете купить передышку за самоцветы!",
"fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
"sureDelete": "Вы уверены, что хотите удалить эту задачу?",
"streakCoins": "Бонус за серию!",
"pushTaskToTop": "Переместить задание наверх",

View file

@ -85,7 +85,7 @@
"fortifyName": "Siloxír",
"fortifyPop": "Vráť všetky úlohy do neutrálneho stavu (žltá farba) a navráť všetko stratené zdravie.",
"fortify": "Posilniť",
"fortifyText": "Siloxír vráti všetky tvoje úlohy do neutrálneho (žltého) stavu, akoby si ich len teraz pridal a úplne ti doplní zdravie. Ber to ako poslednú možnosť! Červené úlohy dobre povzbudzujú k zlepšeniu. Ale ak ťa veľa červenej napĺňa zúfalstvom a začiatok každého dňa je pre teba smrtiaci, obetuj zopár drahokamov a získaj milosť.",
"fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
"sureDelete": "Si si istý, že chceš zmazať túto úlohu?",
"streakCoins": "Bonus za sériu!",
"pushTaskToTop": "Presuň úlohu navrch",

View file

@ -85,7 +85,7 @@
"fortifyName": "Napitak okrepljenja",
"fortifyPop": "Vraća sve zadatke na početnu vrednost (žuta boja), i obnavlja izgubljeno zdravlje.",
"fortify": "Okrepljenje",
"fortifyText": "Okrepljenje vraća sve zadatke u početno (žuto) stanje, kao da su tek napravljeni, i podiže zdravlje na maksimum. Smatrajte ovo krajnjom merom! Crveni zadaci mogu biti dobra motivacija da više potrudite. Međutim, ako vas crveno baca u očaj i smrt vam preti na početku svakog dana, draguljima možete kupiti odušak.",
"fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
"sureDelete": "Jeste li sigurni da želite da obrišete ovaj zadatak?",
"streakCoins": "Bonus zbog redovnog izvršavanja zadataka!",
"pushTaskToTop": "Premesti zadatak na vrh",

View file

@ -85,7 +85,7 @@
"fortifyName": "Stärkande Dryck",
"fortifyPop": "Återställ alla uppgifter till neutralt värde (gul färg) och återställ all förlorad Hälsa.",
"fortify": "Stärk",
"fortifyText": "Den Stärkande Drycken återställer alla uppgifter till ett neutralt (gult) värde, som om du nyss lagt till dem, och återställer all förlorad Hälsa. Se det här som en sista utväg! Röda uppgifter ger uppmuntran att bättra sig. Men om allt det röda fyller dig med förtvivlan och början av varje dag känns hemsk, spendera Juvelerna och bli benådad.",
"fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
"sureDelete": "Är du säker att du vill ta bort denna uppgift?",
"streakCoins": "Följdbonus!",
"pushTaskToTop": "Förflytta uppgift till toppen",

View file

@ -85,7 +85,7 @@
"fortifyName": "Зілля підсилення",
"fortifyPop": "Повернути завдання до нейтрального стану (жовтого кольору) та відновити все здоров'я.",
"fortify": "Підсилитися",
"fortifyText": "Підсилення поверне всі завдання до нейтрального (жовтого) стану, так, наче Ви щойно їх створили, а здоров'я повністю відновиться. Уважайте це крайнім заходом! Червоні завдання дають добрий стимул для самовдосконалення. Але, якщо все червоне вселяє у Вас безвихідь, а кожний новий день може стати останнім, тоді Ви можете придбати собі відтермінування за самоцвіти!",
"fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
"sureDelete": "Ви справді бажаєте вилучити це завдання?",
"streakCoins": "Бонус за серію!",
"pushTaskToTop": "Перемістити завдання вгору",

View file

@ -85,7 +85,7 @@
"fortifyName": "稳固药剂",
"fortifyPop": "将所有任务恢复至中性状态(黄色),并补充所有损失的生命值。",
"fortify": "稳固",
"fortifyText": "稳固会将所有的任务重置(黄色)状态,就像刚刚添加他们一样;还会回满生命值。将这个选项作为最后的方案!红色任务会为进步提供很好的动力。但是如果所有任务都变红了让你感到绝望,并且每天的伤害对你是致命的,那么花费宝石来缓解一下吧!",
"fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
"sureDelete": "你确定要删除这个任务吗?",
"streakCoins": "连击奖励!",
"pushTaskToTop": "将任务置于顶部",

View file

@ -85,7 +85,7 @@
"fortifyName": "穩固藥水",
"fortifyPop": "將所有任務調回中性 ( 黃色 ),並將 HP 補滿。",
"fortify": "穩固",
"fortifyText": "穩固會將你的任務回復到中性狀態 ( 黃色 ,如同剛新增時),並把你的生命值補滿。把它當成無計可施時的最後王牌!紅色會為進步提供良好的動力,但如果滿目紅色讓你難過,且每天開始時都對你致命,那麼花費寶石來喘口氣吧!",
"fortifyText": "Fortify will return all your tasks to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!",
"sureDelete": "你確定要刪除這個任務嗎?",
"streakCoins": "連擊獎勵",
"pushTaskToTop": "置頂",

View file

@ -549,8 +549,23 @@ api.timeTravelerStore = (owned) ->
---------------------------------------------------------------
###
api.potion = type: 'potion', text: t('potionText'), notes: t('potionNotes'), value: 25, key: 'potion'
api.armoire = type: 'armoire', text: t('armoireText'), notes: t('armoireNotesEmpty'), value: 100, key: 'armoire', canOwn: ((u)-> _.contains(u.achievements.ultimateGearSets, true))
api.potion =
type: 'potion',
text: t('potionText'),
notes: t('potionNotes'),
value: 25,
key: 'potion'
api.armoire =
type: 'armoire',
text: t('armoireText'),
notes: ((user, count)->
return t('armoireNotesEmpty')() if (user.flags.armoireEmpty)
return t('armoireNotesFull')() + count
),
value: 100,
key: 'armoire',
canOwn: ((u)-> _.contains(u.achievements.ultimateGearSets, true))
###
---------------------------------------------------------------

View file

@ -190,7 +190,6 @@ preenHistory = (history) ->
Update the in-browser store with new gear. FIXME this was in user.fns, but it was causing strange issues there
###
sortOrder = _.reduce content.gearTypes,((m,v,k)->m[v]=k;m), {}
#sortOrder.potion = _.size(sortOrder) #potion goes last #actually, _.sortBy puts anything else last, so this is unecessary
api.updateStore = (user) ->
return unless user
changes= []
@ -202,8 +201,6 @@ api.updateStore = (user) ->
# Add special items (contrib gear, backer gear, etc)
changes = changes.concat _.filter content.gear.flat, (v) ->
v.klass in ['special','mystery','armoire'] and !user.items.gear.owned[v.key] and v.canOwn?(user)
changes.push content.potion
if user.flags.armoireEnabled then changes.push content.armoire
# Return sorted store (array)
_.sortBy changes, (c)->sortOrder[c.type]

View file

@ -124,7 +124,7 @@
if(removeWatch)
{
doRemoveWatch();
doRemoveWatch();
}
};
@ -144,9 +144,9 @@
habitrpg.filter('markdown', function() {
return function(input){
var html = md.toHtml(input);
html = html.replace(' href',' target="_self" href');
return html;
};
});

View file

@ -21,7 +21,6 @@
"NEW_RELIC_APPLICATION_ID":"NEW_RELIC_APPLICATION_ID",
"NEW_RELIC_API_KEY":"NEW_RELIC_API_KEY",
"GA_ID": "GA_ID",
"MIXPANEL_TOKEN": "MIXPANEL_TOKEN",
"AMPLITUDE_KEY": "AMPLITUDE_KEY",
"AMAZON_PAYMENTS": {
"SELLER_ID": "SELLER_ID",
@ -40,6 +39,10 @@
"accessKeyId":"accessKeyId",
"secretAccessKey":"secretAccessKey"
},
"TRANSIFEX_SLACK": {
"url": "https://hooks.slack.com/services/some-url",
"channel": "general"
},
"PAYPAL":{
"billing_plans": {
"basic_earned":"basic_earned",

View file

@ -36,7 +36,7 @@
"icalendar": "lefnire/node-icalendar#e06da0e55901f0ba940dfadc42c158ed0b1fead9",
"image-size": "~0.3.2",
"in-app-purchase": "^0.2.0",
"jade": "~1.7.0",
"jade": "~1.11.0",
"js2xmlparser": "~0.1.2",
"lodash": "~2.4.1",
"loggly": "~1.0.8",

View file

@ -0,0 +1,188 @@
import fs from 'fs';
import _ from 'lodash';
import nconf from 'nconf';
import gulp from 'gulp';
import request from 'superagent';
nconf.argv().env().file({ file: 'config.json' });
const LOCALES = './common/locales/';
const ENGLISH_LOCALE = `${LOCALES}en/`;
const ALL_LANGUAGES = getArrayOfLanguages();
const SLACK_URL = nconf.get('TRANSIFEX_SLACK:url');
const SLACK_CHANNEL = '#' + (nconf.get('TRANSIFEX_SLACK:channel') || 'general');
const SLACK_USERNAME = 'Transifex';
const SLACK_EMOJI = ':transifex:';
const malformedStringExceptions = {
messageDropFood: true,
armoireFood: true,
feedPet: true
}
gulp.task('transifex', ['transifex:missingFiles', 'transifex:missingStrings', 'transifex:malformedStrings']);
gulp.task('transifex:missingFiles', () => {
let missingStrings = [];
eachTranslationFile(ALL_LANGUAGES, (error) => {
if(error) {
missingStrings.push(error.path);
}
});
if (!_.isEmpty(missingStrings)) {
let message = 'the following files were missing from the translations folder';
post(message, missingStrings);
}
});
gulp.task('transifex:missingStrings', () => {
let missingStrings = [];
eachTranslationString(ALL_LANGUAGES, (language, filename, key, englishString, translationString) => {
if (!translationString) {
let errorString = `${language} - ${filename} - ${key} - ${englishString}`;
missingStrings.push(errorString);
}
});
if (!_.isEmpty(missingStrings)) {
let message = 'The following strings are not translated';
post(message, missingStrings);
}
});
gulp.task('transifex:malformedStrings', () => {
let jsonFiles = stripOutNonJsonFiles(fs.readdirSync(ENGLISH_LOCALE));
let interpolationRegex = /<%= [a-zA-Z]* %>/g;
let stringsToLookFor = getStringsWith(jsonFiles, interpolationRegex);
let stringsWithMalformedInterpolations = [];
let stringsWithIncorrectNumberOfInterpolations = [];
let count = 0;
_(ALL_LANGUAGES).each(function(lang) {
_.each(stringsToLookFor, function(strings, file) {
let translationFile = fs.readFileSync(LOCALES + lang + '/' + file);
let parsedTranslationFile = JSON.parse(translationFile);
_.each(strings, function(value, key) {
let translationString = parsedTranslationFile[key];
if (!translationString) return;
let englishOccurences = stringsToLookFor[file][key];
let translationOccurences = translationString.match(interpolationRegex);
if (!translationOccurences) {
let malformedString = `${lang} - ${file} - ${key} - ${translationString}`;
stringsWithMalformedInterpolations.push(malformedString);
} else if (englishOccurences.length !== translationOccurences.length && !malformedStringExceptions[key]) {
let missingInterploationString = `${lang} - ${file} - ${key} - ${translationString}`;
stringsWithIncorrectNumberOfInterpolations.push(missingInterploationString);
}
});
});
});
if (!_.isEmpty(stringsWithMalformedInterpolations)) {
let message = 'The following strings have malformed or missing interpolations';
post(message, stringsWithMalformedInterpolations);
}
if (!_.isEmpty(stringsWithIncorrectNumberOfInterpolations)) {
let message = 'The following strings have a different number of string interpolations';
post(message, stringsWithIncorrectNumberOfInterpolations);
}
});
function getArrayOfLanguages() {
let languages = fs.readdirSync(LOCALES);
languages.shift(); // Remove README.md from array of languages
return languages;
}
function eachTranslationFile(languages, cb) {
let jsonFiles = stripOutNonJsonFiles(fs.readdirSync(ENGLISH_LOCALE));
_(languages).each((lang) => {
_.each(jsonFiles, (filename) => {
try {
var translationFile = fs.readFileSync(LOCALES + lang + '/' + filename);
var parsedTranslationFile = JSON.parse(translationFile);
} catch (err) {
return cb(err);
}
let englishFile = fs.readFileSync(ENGLISH_LOCALE + filename);
let parsedEnglishFile = JSON.parse(englishFile);
cb(null, lang, filename, parsedEnglishFile, parsedTranslationFile)
});
});
}
function eachTranslationString(languages, cb) {
eachTranslationFile(languages, (error, language, filename, englishJSON, translationJSON) => {
if (error) return;
_.each(englishJSON, (string, key) => {
var translationString = translationJSON[key];
cb(language, filename, key, string, translationString);
});
});
}
function post(message, items) {
let formattedMessage = formatMessageForPosting(message, items);
request.post(SLACK_URL)
.send({
channel: SLACK_CHANNEL,
username: SLACK_USERNAME,
text: formattedMessage,
icon_emoji: SLACK_EMOJI
})
.end((err, res) => {
if (err) console.error('Unable to post to slack', err);
});
}
function formatMessageForPosting(msg, items) {
let body = `*Warning:* ${msg}`;
body += '\n\n```\n';
body += items.join('\n');
body += '\n```';
return body;
}
function getStringsWith(json, interpolationRegex) {
var strings = {};
_(json).each(function(file_name) {
var raw_file = fs.readFileSync(ENGLISH_LOCALE + file_name);
var parsed_json = JSON.parse(raw_file);
strings[file_name] = {};
_.each(parsed_json, function(value, key) {
var match = value.match(interpolationRegex);
if(match) strings[file_name][key] = match;
});
});
return strings;
}
function stripOutNonJsonFiles(collection) {
let onlyJson = _.filter(collection, (file) => {
return file.match(/[a-zA-Z]*\.json/);
});
return onlyJson;
}

View file

@ -222,7 +222,11 @@ describe('analytics', function() {
stats: stats,
contributor: { level: 1 },
purchased: { plan: { planId: 'foo-plan' } },
flags: {tour: {intro: -2}}
flags: {tour: {intro: -2}},
habits: [{_id: 'habit'}],
dailys: [{_id: 'daily'}],
todos: [{_id: 'todo'}],
rewards: [{_id: 'reward'}]
};
analyticsData.user = user;
@ -248,7 +252,13 @@ describe('analytics', function() {
Mana: 30,
contributorLevel: 1,
subscription: 'foo-plan',
tutorialComplete: true
tutorialComplete: true,
"Number Of Tasks": {
todos: 1,
dailys: 1,
habits: 1,
rewards: 1
}
}
});
});

View file

@ -1,7 +1,7 @@
'use strict';
describe('Tasks Controller', function() {
var $rootScope, scope, user, ctrl;
var $rootScope, shared, scope, user, ctrl;
beforeEach(function() {
user = specHelper.newUser();
@ -10,10 +10,10 @@ describe('Tasks Controller', function() {
$provide.value('Guide', {});
});
inject(function($rootScope, $controller){
inject(function($rootScope, $controller, Shared){
scope = $rootScope.$new();
shared = Shared;
$controller('RootCtrl', {$scope: scope, User: {user: user}});
ctrl = $controller('TasksCtrl', {$scope: scope, User: {user: user}});
@ -28,4 +28,14 @@ describe('Tasks Controller', function() {
});
});
});
describe('watch to updateStore', function() {
it('updates itemStore when user gear changes', function() {
sinon.stub(shared, 'updateStore').returns({item: true});
user.items.gear.owned.foo = true;
scope.$digest();
expect(scope.itemStore).to.eql({item: true});
});
});
});

View file

@ -184,6 +184,12 @@ describe('Analytics Service', function () {
expectedProperties.Level = 24;
expectedProperties.Mana = 41;
expectedProperties.tutorialComplete = false;
expectedProperties["Number Of Tasks"] = {
habits: 1,
dailys: 1,
todos: 1,
rewards: 1
};
beforeEach(function() {
user._id = 'unique-user-id';
@ -194,6 +200,10 @@ describe('Analytics Service', function () {
user.stats.lvl = 24;
user.stats.mp = 41;
user.flags.tour.intro = 3;
user.habits = [{_id: 'habit'}];
user.dailys = [{_id: 'daily'}];
user.todos = [{_id: 'todo'}];
user.rewards = [{_id: 'reward'}];
analytics.updateUser(properties);
clock.tick();
@ -221,7 +231,13 @@ describe('Analytics Service', function () {
Mana: 41,
contributorLevel: 1,
subscription: 'unique-plan-id',
tutorialComplete: true
tutorialComplete: true,
"Number Of Tasks": {
todos: 1,
dailys: 1,
habits: 1,
rewards: 1
}
};
beforeEach(function() {
@ -235,6 +251,10 @@ describe('Analytics Service', function () {
user.contributor.level = 1;
user.purchased.plan.planId = 'unique-plan-id';
user.flags.tour.intro = -2;
user.habits = [{_id: 'habit'}];
user.dailys = [{_id: 'daily'}];
user.todos = [{_id: 'todo'}];
user.rewards = [{_id: 'reward'}];
analytics.updateUser();
clock.tick();

View file

@ -1,78 +0,0 @@
var chai = require("chai")
var expect = chai.expect
var fs = require('fs');
var _ = require('lodash');
var locales = './common/locales/';
var english = locales + 'en/';
var interpolationRegex = /<%= [a-zA-Z]* %>/g;
var languages = fs.readdirSync(locales);
languages.shift(); // Remove README.md from array of languages
var jsonFiles = stripOutNonJsonFiles(fs.readdirSync(english));
var stringsToLookFor = stringsWithInterpolations(jsonFiles);
_(languages).each(function(lang) {
describe('the ' + lang + ' language', function() {
_.each(stringsToLookFor, function(strings, file) {
context('string in ' + file + ' -', function() {
var translationFile = fs.readFileSync(locales + lang + '/' + file);
var parsedTranslationFile = JSON.parse(translationFile);
var englishFile = fs.readFileSync(english + file);
var parsedEnglishFile = JSON.parse(englishFile);
_.each(strings, function(value, key) {
var englishString = parsedEnglishFile[key];
var translationString = parsedTranslationFile[key];
var EnglishOccurences = englishString.match(interpolationRegex);
var TranslationOccurences = translationString.match(interpolationRegex);
xit(key + ' - contains an interpolation', function() {
if(!TranslationOccurences) console.log("English String:", englishString);
expect(EnglishOccurences).to.exist;
expect(TranslationOccurences).to.exist;
});
if(EnglishOccurences.length > 1) {
it(key + ' - has the correct number of interpolations', function() {
if(!TranslationOccurences) console.log("English String:", englishString);
var numberOfOccurences = TranslationOccurences && TranslationOccurences.length;
expect(numberOfOccurences).to.eql(EnglishOccurences.length);
});
}
});
});
});
});
});
function stringsWithInterpolations(json) {
var strings = {};
_(json).each(function(file_name) {
var raw_file = fs.readFileSync(english + file_name);
var parsed_json = JSON.parse(raw_file);
strings[file_name] = {};
_.each(parsed_json, function(value, key) {
var match = value.match(interpolationRegex);
if(match) strings[file_name][key] = true;
});
});
return strings;
}
function stripOutNonJsonFiles(collection) {
var onlyJson = _.filter(collection, function(file) {
return file.match(/[a-zA-Z]*\.json/);
});
return onlyJson;
}

View file

@ -1,7 +1,7 @@
"use strict";
habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','Notification', '$http', 'ApiUrl', '$timeout', 'Shared', 'Guide', 'Tasks', 'Analytics',
function($scope, $rootScope, $location, User, Notification, $http, ApiUrl, $timeout, Shared, Guide, Tasks, Analytics) {
habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','Notification', '$http', 'ApiUrl', '$timeout', 'Content', 'Shared', 'Guide', 'Tasks', 'Analytics',
function($scope, $rootScope, $location, User, Notification, $http, ApiUrl, $timeout, Content, Shared, Guide, Tasks, Analytics) {
$scope.obj = User.user; // used for task-lists
$scope.user = User.user;
@ -212,10 +212,13 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N
------------------------
*/
$scope.$watchGroup(['user.items.gear.owned', 'user.flags.armoireEnabled'], function(){
$scope.$watch('user.items.gear.owned', function(){
$scope.itemStore = Shared.updateStore(User.user);
},true);
$scope.healthPotion = Content.potion;
$scope.armoire = Content.armoire;
$scope.buy = function(item) {
User.user.ops.buy({params:{key:item.key}});
$rootScope.playSound('Reward');

View file

@ -108,6 +108,12 @@
properties.Mana = Math.floor(user.stats.mp);
}
properties.tutorialComplete = user.flags && user.flags.tour && user.flags.tour.intro === -2;
properties["Number Of Tasks"] = {
habits: user.habits.length,
dailys: user.dailys.length,
todos: user.todos.length,
rewards: user.rewards.length
};
if (user.contributor && user.contributor.level) properties.contributorLevel = user.contributor.level;
if (user.purchased && user.purchased.plan.planId) properties.subscription = user.purchased.plan.planId;
}

View file

@ -153,6 +153,12 @@ function _formatUserData(user) {
}
properties.tutorialComplete = user.flags && user.flags.tour && user.flags.tour.intro === -2;
properties["Number Of Tasks"] = {
habits: user.habits.length,
dailys: user.dailys.length,
todos: user.todos.length,
rewards: user.rewards.length
};
if (user.contributor && user.contributor.level) {
properties.contributorLevel = user.contributor.level;

View file

@ -102,13 +102,13 @@ dataexport.avatarPage = function(req, res) {
User.findById(req.params.uuid).select('stats profile items achievements preferences backer contributor').exec(function(err, user){
res.render('avatar-static', {
title: user.profile.name,
env: _.defaults({user:user},res.locals.habitrpg)
env: _.defaults({user:user}, res.locals.habitrpg)
});
})
};
dataexport.avatarImage = function(req, res, next) {
var filename = 'avatar-'+req.params.uuid+'.png';
var filename = 'avatars/'+req.params.uuid+'.png';
request.head('https://'+bucket+'.s3.amazonaws.com/'+filename, function(err,response,body) {
// cache images for 10 minutes on aws, else upload a new one
if (response.statusCode==200 && moment().diff(response.headers['last-modified'], 'minutes') < 10)

View file

@ -148,7 +148,7 @@ module.exports = {
// Export en strings only, temporary solution for mobile
// This is copied from middleware.js#module.exports.locals#t()
// This is copied from middlewares/locals#t()
module.exports.enTranslations = function(){ // stringName and vars are the allowed parameters
var language = _.find(avalaibleLanguages, {code: 'en'});
//language.momentLang = ((!isStaticPage && i18n.momentLangs[language.code]) || undefined);

View file

@ -0,0 +1,59 @@
var fs = require('fs');
var path = require('path');
var nconf = require('nconf');
var _ = require('lodash');
var manifestFiles = require("../../public/manifest.json");
var IS_PROD = nconf.get('NODE_ENV') === 'production';
var buildFiles = [];
var walk = function(folder){
var res = fs.readdirSync(folder);
res.forEach(function(fileName){
var file = folder + '/' + fileName;
if(fs.statSync(file).isDirectory()){
walk(file);
}else{
var relFolder = path.relative(path.join(__dirname, "/../../build"), folder);
var old = fileName.replace(/-.{8}(\.[\d\w]+)$/, '$1');
if(relFolder){
old = relFolder + '/' + old;
fileName = relFolder + '/' + fileName;
}
buildFiles[old] = fileName;
}
});
};
walk(path.join(__dirname, "/../../build"));
var getBuildUrl = module.exports.getBuildUrl = function(url){
if(buildFiles[url]) return '/' + buildFiles[url];
return '/' + url;
};
module.exports.getManifestFiles = function(page){
var files = manifestFiles[page];
if(!files) throw new Error("Page not found!");
var code = '';
if(IS_PROD){
code += '<link rel="stylesheet" type="text/css" href="' + getBuildUrl(page + '.css') + '">';
code += '<script type="text/javascript" src="' + getBuildUrl(page + '.js') + '"></script>';
}else{
_.each(files.css, function(file){
code += '<link rel="stylesheet" type="text/css" href="' + getBuildUrl(file) + '">';
});
_.each(files.js, function(file){
code += '<script type="text/javascript" src="' + getBuildUrl(file) + '"></script>';
});
}
return code;
};

View file

@ -1,249 +0,0 @@
var nconf = require('nconf');
var _ = require('lodash');
var fs = require('fs');
var path = require('path');
var User = require('./models/user').model
var limiter = require('connect-ratelimit');
var logging = require('./logging');
var domainMiddleware = require('domain-middleware');
var cluster = require('cluster');
var i18n = require('./i18n.js');
var shared = require('../../common');
var request = require('request');
var os = require('os');
var moment = require('moment');
var utils = require('./utils');
var IS_PROD = nconf.get('NODE_ENV') === 'production';
var BASE_URL = nconf.get("BASE_URL");
module.exports.apiThrottle = function(app) {
if (!IS_PROD) return;
app.use(limiter({
end:false,
catagories:{
normal: {
// 2 req/s, but split as minutes
totalRequests: 80,
every: 60000
}
}
})).use(function(req,res,next){
//logging.info(res.ratelimit);
if (res.ratelimit.exceeded) return res.json(429,{err:'Rate limit exceeded'});
next();
});
}
module.exports.domainMiddleware = function(server,mongoose) {
if (IS_PROD) {
var mins = 3, // how often to run this check
useAvg = false, // use average over 3 minutes, or simply the last minute's report
url = 'https://api.newrelic.com/v2/applications/'+nconf.get('NEW_RELIC_APPLICATION_ID')+'/metrics/data.json?names[]=Apdex&values[]=score';
setInterval(function(){
// see https://docs.newrelic.com/docs/apm/apis/api-v2-examples/average-response-time-examples-api-v2, https://rpm.newrelic.com/api/explore/applications/data
request({
url: useAvg ? url+'&from='+moment().subtract({minutes:mins}).utc().format()+'&to='+moment().utc().format()+'&summarize=true' : url,
headers: {'X-Api-Key': nconf.get('NEW_RELIC_API_KEY')}
}, function(err, response, body){
var ts = JSON.parse(body).metric_data.metrics[0].timeslices,
score = ts[ts.length-1].values.score,
apdexBad = score < .75 || score == 1,
memory = os.freemem() / os.totalmem(),
memoryHigh = memory < 0.1;
if (/*apdexBad || */memoryHigh) throw "[Memory Leak] Apdex="+score+" Memory="+parseFloat(memory).toFixed(3)+" Time="+moment().format();
})
}, mins*60*1000);
}
return domainMiddleware({
server: {
close:function(){
server.close();
mongoose.connection.close();
}
},
killTimeout: 10000
});
}
module.exports.errorHandler = function(err, req, res, next) {
//res.locals.domain.emit('error', err);
// when we hit an error, send it to admin as an email. If no ADMIN_EMAIL is present, just send it to yourself (SMTP_USER)
var stack = (err.stack ? err.stack : err.message ? err.message : err) +
"\n ----------------------------\n" +
"\n\noriginalUrl: " + req.originalUrl +
"\n\nauth: " + req.headers['x-api-user'] + ' | ' + req.headers['x-api-key'] +
"\n\nheaders: " + JSON.stringify(req.headers) +
"\n\nbody: " + JSON.stringify(req.body) +
(res.locals.ops ? "\n\ncompleted ops: " + JSON.stringify(res.locals.ops) : "");
logging.error(stack);
/*logging.loggly({
error: "Uncaught error",
stack: (err.stack || err.message || err),
body: req.body, headers: req.header,
auth: req.headers['x-api-user'],
originalUrl: req.originalUrl
});*/
var message = err.message ? err.message : err;
message = (message.length < 200) ? message : message.substring(0,100) + message.substring(message.length-100,message.length);
res.json(500,{err:message}); //res.end(err.message);
}
function isHTTP(req) {
return (
req.headers['x-forwarded-proto'] &&
req.headers['x-forwarded-proto'] !== 'https' &&
IS_PROD &&
BASE_URL.indexOf('https') === 0
);
}
function isProxied(req) {
return (
req.headers['x-habitica-lb'] &&
req.headers['x-habitica-lb'] === 'Yes'
);
}
module.exports.forceSSL = function(req, res, next){
if(isHTTP(req) && !isProxied(req)) {
return res.redirect(BASE_URL + req.url);
}
next();
}
// Redirect to habitica for non-api urls
// NOTE: Currently using a static 'habitica.com' string, rather than BASE_URL,
// to make rollback easy. Eventually, BASE_URL should be migrated.
function nonApiUrl(req) {
return req.url.search(/\/api\//) === -1;
}
module.exports.forceHabitica = function(req, res, next) {
var ignoreRedirect = nconf.get('IGNORE_REDIRECT');
if (IS_PROD && !ignoreRedirect && !isProxied(req) && nonApiUrl(req)) {
return res.redirect('https://habitica.com' + req.url);
}
next();
};
module.exports.cors = function(req, res, next) {
res.header("Access-Control-Allow-Origin", req.headers.origin || "*");
res.header("Access-Control-Allow-Methods", "OPTIONS,GET,POST,PUT,HEAD,DELETE");
res.header("Access-Control-Allow-Headers", "Content-Type,Accept,Content-Encoding,X-Requested-With,x-api-user,x-api-key");
if (req.method === 'OPTIONS') return res.send(200);
return next();
};
var siteVersion = 1;
module.exports.forceRefresh = function(req, res, next){
if(req.query.siteVersion && req.query.siteVersion != siteVersion){
return res.json(400, {needRefresh: true});
}
return next();
};
var buildFiles = [];
var walk = function(folder){
var res = fs.readdirSync(folder);
res.forEach(function(fileName){
file = folder + '/' + fileName;
if(fs.statSync(file).isDirectory()){
walk(file);
}else{
var relFolder = path.relative(path.join(__dirname, "/../build"), folder);
var old = fileName.replace(/-.{8}(\.[\d\w]+)$/, '$1');
if(relFolder){
old = relFolder + '/' + old;
fileName = relFolder + '/' + fileName;
}
buildFiles[old] = fileName
}
});
}
walk(path.join(__dirname, "/../build"));
var getBuildUrl = function(url){
if(buildFiles[url]) return '/' + buildFiles[url];
return '/' + url;
}
var manifestFiles = require("../public/manifest.json");
var getManifestFiles = function(page){
var files = manifestFiles[page];
if(!files) throw new Error("Page not found!");
var code = '';
if(IS_PROD){
code += '<link rel="stylesheet" type="text/css" href="' + getBuildUrl(page + '.css') + '">';
code += '<script type="text/javascript" src="' + getBuildUrl(page + '.js') + '"></script>';
}else{
_.each(files.css, function(file){
code += '<link rel="stylesheet" type="text/css" href="' + getBuildUrl(file) + '">';
});
_.each(files.js, function(file){
code += '<script type="text/javascript" src="' + getBuildUrl(file) + '"></script>';
});
}
return code;
}
module.exports.locals = function(req, res, next) {
var language = _.find(i18n.avalaibleLanguages, {code: req.language});
var isStaticPage = req.url.split('/')[1] === 'static'; // If url contains '/static/'
// Load moment.js language file only when not on static pages
language.momentLang = ((!isStaticPage && i18n.momentLangs[language.code]) || undefined);
var tavern = require('./models/group').tavern;
var envVars = _.pick(nconf.get(), 'NODE_ENV BASE_URL GA_ID STRIPE_PUB_KEY FACEBOOK_KEY AMPLITUDE_KEY'.split(' '));
res.locals.habitrpg = _.merge(envVars, {
IS_MOBILE: /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(req.header('User-Agent')),
getManifestFiles: getManifestFiles,
getBuildUrl: getBuildUrl,
avalaibleLanguages: i18n.avalaibleLanguages,
language: language,
isStaticPage: isStaticPage,
translations: i18n.translations[language.code],
t: function(){ // stringName and vars are the allowed parameters
var args = Array.prototype.slice.call(arguments, 0);
args.push(language.code);
return shared.i18n.t.apply(null, args);
},
siteVersion: siteVersion,
Content: shared.content,
mods: require('./models/user').mods,
tavern: tavern, // for world boss
worldDmg: (tavern && tavern.quest && tavern.quest.extra && tavern.quest.extra.worldDmg) || {},
_: _,
MP_ID: nconf.get('MP_ID'),
AMAZON_PAYMENTS: {
SELLER_ID: nconf.get('AMAZON_PAYMENTS:SELLER_ID'),
CLIENT_ID: nconf.get('AMAZON_PAYMENTS:CLIENT_ID')
}
});
// Put query-string party (& guild but use partyInvite for backward compatibility)
// invitations into session to be handled later
try{
req.session.partyInvite = JSON.parse(utils.decrypt(req.query.partyInvite))
} catch(e){}
next();
}

View file

@ -0,0 +1,22 @@
var nconf = require('nconf');
var limiter = require('connect-ratelimit');
var IS_PROD = nconf.get('NODE_ENV') === 'production';
module.exports = function(app) {
if (!IS_PROD) return;
app.use(limiter({
end:false,
categories:{
normal: {
// 2 req/s, but split as minutes
totalRequests: 80,
every: 60000
}
}
})).use(function(req,res,next){
//logging.info(res.ratelimit);
if (res.ratelimit.exceeded) return res.json(429,{err:'Rate limit exceeded'});
next();
});
};

View file

@ -0,0 +1,7 @@
module.exports = function(req, res, next) {
res.header("Access-Control-Allow-Origin", req.headers.origin || "*");
res.header("Access-Control-Allow-Methods", "OPTIONS,GET,POST,PUT,HEAD,DELETE");
res.header("Access-Control-Allow-Headers", "Content-Type,Accept,Content-Encoding,X-Requested-With,x-api-user,x-api-key");
if (req.method === 'OPTIONS') return res.send(200);
return next();
};

View file

@ -0,0 +1,39 @@
var nconf = require('nconf');
var moment = require('moment');
var domainMiddleware = require('domain-middleware');
var os = require('os');
var request = require('request');
var IS_PROD = nconf.get('NODE_ENV') === 'production';
module.exports = function(server,mongoose) {
if (IS_PROD) {
var mins = 3, // how often to run this check
useAvg = false, // use average over 3 minutes, or simply the last minute's report
url = 'https://api.newrelic.com/v2/applications/'+nconf.get('NEW_RELIC_APPLICATION_ID')+'/metrics/data.json?names[]=Apdex&values[]=score';
setInterval(function(){
// see https://docs.newrelic.com/docs/apm/apis/api-v2-examples/average-response-time-examples-api-v2, https://rpm.newrelic.com/api/explore/applications/data
request({
url: useAvg ? url+'&from='+moment().subtract({minutes:mins}).utc().format()+'&to='+moment().utc().format()+'&summarize=true' : url,
headers: {'X-Api-Key': nconf.get('NEW_RELIC_API_KEY')}
}, function(err, response, body){
var ts = JSON.parse(body).metric_data.metrics[0].timeslices,
score = ts[ts.length-1].values.score,
apdexBad = score < .75 || score == 1,
memory = os.freemem() / os.totalmem(),
memoryHigh = memory < 0.1;
if (/*apdexBad || */memoryHigh) throw "[Memory Leak] Apdex="+score+" Memory="+parseFloat(memory).toFixed(3)+" Time="+moment().format();
});
}, mins*60*1000);
}
return domainMiddleware({
server: {
close:function(){
server.close();
mongoose.connection.close();
}
},
killTimeout: 10000
});
};

View file

@ -0,0 +1,24 @@
var logging = require('../logging');
module.exports = function(err, req, res, next) {
//res.locals.domain.emit('error', err);
// when we hit an error, send it to admin as an email. If no ADMIN_EMAIL is present, just send it to yourself (SMTP_USER)
var stack = (err.stack ? err.stack : err.message ? err.message : err) +
"\n ----------------------------\n" +
"\n\noriginalUrl: " + req.originalUrl +
"\n\nauth: " + req.headers['x-api-user'] + ' | ' + req.headers['x-api-key'] +
"\n\nheaders: " + JSON.stringify(req.headers) +
"\n\nbody: " + JSON.stringify(req.body) +
(res.locals.ops ? "\n\ncompleted ops: " + JSON.stringify(res.locals.ops) : "");
logging.error(stack);
/*logging.loggly({
error: "Uncaught error",
stack: (err.stack || err.message || err),
body: req.body, headers: req.header,
auth: req.headers['x-api-user'],
originalUrl: req.originalUrl
});*/
var message = err.message ? err.message : err;
message = (message.length < 200) ? message : message.substring(0,100) + message.substring(message.length-100,message.length);
res.json(500,{err:message}); //res.end(err.message);
};

View file

@ -0,0 +1,9 @@
module.exports.siteVersion = 1;
module.exports.middleware = function(req, res, next){
if(req.query.siteVersion && req.query.siteVersion != module.exports.siteVersion){
return res.json(400, {needRefresh: true});
}
return next();
};

View file

@ -0,0 +1,71 @@
var nconf = require('nconf');
var _ = require('lodash');
var utils = require('../utils');
var shared = require('../../../common');
var i18n = require('../i18n.js');
var buildManifest = require('../libs/buildManifest');
var shared = require('../../../common');
var forceRefresh = require('./forceRefresh');
var tavern = require('../models/group').tavern;
var mods = require('../models/user').mods;
// To avoid stringifying more data then we need,
// items from `env` used on the client will have to be specified in this array
var clientVars = ['language', 'isStaticPage', 'avalaibleLanguages', 'translations',
'FACEBOOK_KEY', 'NODE_ENV', 'BASE_URL', 'GA_ID',
'AMAZON_PAYMENTS', 'STRIPE_PUB_KEY', 'AMPLITUDE_KEY',
'worldDmg', 'mods', 'IS_MOBILE'];
var env = {
getManifestFiles: buildManifest.getManifestFiles,
getBuildUrl: buildManifest.getBuildUrl,
_: _,
clientVars: clientVars,
tavern: tavern, // for world boss
mods: mods,
Content: shared.content,
siteVersion: forceRefresh.siteVersion,
avalaibleLanguages: i18n.avalaibleLanguages,
AMAZON_PAYMENTS: {
SELLER_ID: nconf.get('AMAZON_PAYMENTS:SELLER_ID'),
CLIENT_ID: nconf.get('AMAZON_PAYMENTS:CLIENT_ID')
}
};
'NODE_ENV BASE_URL GA_ID STRIPE_PUB_KEY FACEBOOK_KEY AMPLITUDE_KEY'.split(' ').forEach(function(key){
env[key] = nconf.get(key);
});
module.exports = function(req, res, next) {
var language = _.find(i18n.avalaibleLanguages, {code: req.language});
var isStaticPage = req.url.split('/')[1] === 'static'; // If url contains '/static/'
// Load moment.js language file only when not on static pages
language.momentLang = ((!isStaticPage && i18n.momentLangs[language.code]) || undefined);
res.locals.habitrpg = _.assign(env, {
IS_MOBILE: /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(req.header('User-Agent')),
language: language,
isStaticPage: isStaticPage,
translations: i18n.translations[language.code],
t: function(){ // stringName and vars are the allowed parameters
var args = Array.prototype.slice.call(arguments, 0);
args.push(language.code);
return shared.i18n.t.apply(null, args);
},
// Defined here and not outside of the middleware because tavern might be an
// empty object until the query to fetch it finishes
worldDmg: (tavern && tavern.quest && tavern.quest.extra && tavern.quest.extra.worldDmg) || {},
});
// Put query-string party (& guild but use partyInvite for backward compatibility)
// invitations into session to be handled later
// TODO once we have proper logging in place, log errors here
if(req.query.partyInvite){
try{
req.session.partyInvite = JSON.parse(utils.decrypt(req.query.partyInvite));
} catch(e){}
}
next();
};

View file

@ -0,0 +1,43 @@
var nconf = require('nconf');
var IS_PROD = nconf.get('NODE_ENV') === 'production';
var ignoreRedirect = nconf.get('IGNORE_REDIRECT');
var BASE_URL = nconf.get("BASE_URL");
function isHTTP(req) {
return (
req.headers['x-forwarded-proto'] &&
req.headers['x-forwarded-proto'] !== 'https' &&
IS_PROD &&
BASE_URL.indexOf('https') === 0
);
}
function isProxied(req) {
return (
req.headers['x-habitica-lb'] &&
req.headers['x-habitica-lb'] === 'Yes'
);
}
module.exports.forceSSL = function(req, res, next){
if(isHTTP(req) && !isProxied(req)) {
return res.redirect(BASE_URL + req.url);
}
next();
};
// Redirect to habitica for non-api urls
// NOTE: Currently using a static 'habitica.com' string, rather than BASE_URL,
// to make rollback easy. Eventually, BASE_URL should be migrated.
function nonApiUrl(req) {
return req.url.search(/\/api\//) === -1;
}
module.exports.forceHabitica = function(req, res, next) {
if (IS_PROD && !ignoreRedirect && !isProxied(req) && nonApiUrl(req)) {
return res.redirect('https://habitica.com' + req.url);
}
next();
};

View file

@ -236,7 +236,8 @@ module.exports.tavern = {};
var tavernQ = {_id:'habitrpg','quest.key':{$ne:null}};
process.nextTick(function(){
mongoose.model('Group').findOne(tavernQ,function(err,tavern){
module.exports.tavern = tavern;
// Using _assign so we don't lose the reference to the exported tavern
_.assign(module.exports.tavern, tavern);
});
})
GroupSchema.statics.tavernBoss = function(user,progress) {

View file

@ -577,11 +577,16 @@ UserSchema.methods.unlink = function(options, cb) {
module.exports.schema = UserSchema;
module.exports.model = mongoose.model("User", UserSchema);
// Initially export an empty object so external requires will get
// the right object by reference when it's defined later
// Otherwise it would remain undefined if requested before the query executes
module.exports.mods = [];
mongoose.model("User")
.find({'contributor.admin':true})
.sort('-contributor.level -backer.npc profile.name')
.select('profile contributor backer')
.exec(function(err,mods){
module.exports.mods = mods
// Using push to maintain the reference to mods
module.exports.mods.push.apply(module.exports.mods, mods);
});

View file

@ -5,9 +5,9 @@ var async = require('async');
var icalendar = require('icalendar');
var api = require('./../controllers/user');
var auth = require('./../controllers/auth');
var middleware = require('../middleware');
var logging = require('./../logging');
var i18n = require('./../i18n');
var forceRefresh = require('../middlewares/forceRefresh').middleware;
/* ---------- Deprecated API ------------*/
@ -161,7 +161,7 @@ router.post('/user/task', auth.auth, i18n.getUserLanguage, cron, api.addTask);
// User
router.get('/user', auth.auth, i18n.getUserLanguage, cron, api.getUser);
router.post('/user/revive', auth.auth, i18n.getUserLanguage, cron, api.revive);
router.post('/user/batch-update', middleware.forceRefresh, auth.auth, i18n.getUserLanguage, cron, batchUpdate);
router.post('/user/batch-update', forceRefresh, auth.auth, i18n.getUserLanguage, cron, batchUpdate);
function deprecated(req, res) {
res.json(404, {err:'API v1 is no longer supported, please use API v2 instead (https://github.com/HabitRPG/habitrpg/blob/develop/API.md)'});

View file

@ -16,12 +16,11 @@ hall = require("../controllers/hall")
challenges = require("../controllers/challenges")
dataexport = require("../controllers/dataexport")
nconf = require("nconf")
middleware = require("../middleware")
cron = user.cron
_ = require('lodash')
content = require('../../../common').content
i18n = require('../i18n')
forceRefresh = require('../middlewares/forceRefresh').middleware
module.exports = (swagger, v2) ->
[path,body,query] = [swagger.pathParam, swagger.bodyParam, swagger.queryParam]
@ -326,7 +325,7 @@ module.exports = (swagger, v2) ->
parameters:[
body '','The array of batch-operations to perform','object'
]
middleware: [middleware.forceRefresh, auth.auth, i18n.getUserLanguage, cron, user.sessionPartyInvite]
middleware: [forceRefresh, auth.auth, i18n.getUserLanguage, cron, user.sessionPartyInvite]
action: user.batchUpdate
# Tags

View file

@ -4,13 +4,13 @@ var dataexport = require('../controllers/dataexport');
var auth = require('../controllers/auth');
var nconf = require('nconf');
var i18n = require('../i18n');
var middleware = require('../middleware.js');
var locals = require('../middlewares/locals');
/* Data export */
router.get('/history.csv',auth.authWithSession,i18n.getUserLanguage,dataexport.history); //[todo] encode data output options in the data controller and use these to build routes
router.get('/userdata.xml',auth.authWithSession,i18n.getUserLanguage,dataexport.leanuser,dataexport.userdata.xml);
router.get('/userdata.json',auth.authWithSession,i18n.getUserLanguage,dataexport.leanuser,dataexport.userdata.json);
router.get('/avatar-:uuid.html', i18n.getUserLanguage, middleware.locals, dataexport.avatarPage);
router.get('/avatar-:uuid.png', i18n.getUserLanguage, middleware.locals, dataexport.avatarImage);
router.get('/avatar-:uuid.html', i18n.getUserLanguage, locals, dataexport.avatarPage);
router.get('/avatar-:uuid.png', i18n.getUserLanguage, locals, dataexport.avatarImage);
module.exports = router;

View file

@ -2,13 +2,13 @@ var nconf = require('nconf');
var express = require('express');
var router = new express.Router();
var _ = require('lodash');
var middleware = require('../middleware');
var locals = require('../middlewares/locals');
var user = require('../controllers/user');
var auth = require('../controllers/auth');
var i18n = require('../i18n');
// -------- App --------
router.get('/', i18n.getUserLanguage, middleware.locals, function(req, res) {
router.get('/', i18n.getUserLanguage, locals, function(req, res) {
if (!req.headers['x-api-user'] && !req.headers['x-api-key'] && !(req.session && req.session.userId))
return res.redirect('/static/front')
@ -23,7 +23,7 @@ router.get('/', i18n.getUserLanguage, middleware.locals, function(req, res) {
var pages = ['front', 'privacy', 'terms', 'api', 'features', 'videos', 'contact', 'plans', 'new-stuff', 'community-guidelines', 'old-news', 'press-kit'];
_.each(pages, function(name){
router.get('/static/' + name, i18n.getUserLanguage, middleware.locals, function(req, res) {
router.get('/static/' + name, i18n.getUserLanguage, locals, function(req, res) {
res.render('static/' + name, {env: res.locals.habitrpg});
});
})

View file

@ -30,8 +30,6 @@ if (cores!==0 && cluster.isMaster && (isDev || isProd)) {
// Setup translations
var i18n = require('./i18n');
var middleware = require('./middleware');
var TWO_WEEKS = 1000 * 60 * 60 * 24 * 14;
var app = express();
var server = http.createServer();
@ -89,16 +87,18 @@ if (cores!==0 && cluster.isMaster && (isDev || isProd)) {
var publicDir = path.join(__dirname, "/../public");
app.set("port", nconf.get('PORT'));
middleware.apiThrottle(app);
app.use(middleware.domainMiddleware(server,mongoose));
require('./middlewares/apiThrottle')(app);
app.use(require('./middlewares/domain')(server,mongoose));
if (!isProd) app.use(express.logger("dev"));
app.use(express.compress());
app.set("views", __dirname + "/../views");
app.set("view engine", "jade");
app.use(express.favicon(publicDir + '/favicon.ico'));
app.use(middleware.cors);
app.use(middleware.forceHabitica);
app.use(middleware.forceSSL);
app.use(require('./middlewares/cors'));
var redirects = require('./middlewares/redirects');
app.use(redirects.forceHabitica);
app.use(redirects.forceSSL);
app.use(express.urlencoded());
app.use(express.json());
app.use(require('method-override')());
@ -134,7 +134,7 @@ if (cores!==0 && cluster.isMaster && (isDev || isProd)) {
app.use('/api/v1', require('./routes/apiv1').middleware);
app.use('/export', require('./routes/dataexport').middleware);
require('./routes/apiv2.coffee')(swagger, v2);
app.use(middleware.errorHandler);
app.use(require('./middlewares/errorHandler'));
server.on('request', app);
server.listen(app.get("port"), function() {

View file

@ -8,8 +8,15 @@ html(ng-app="habitrpg")
meta(name='viewport', content='width=device-width, initial-scale=1.0')
meta(name='apple-mobile-web-app-capable', content='yes')
// .slice(0).push('user') is to clone the array,
// to be surethat `user` is never avalaible to other requests' env
// TODO does it need only `user` in clientVars, not the others?
-
clientVars = env.clientVars.slice(0);
clientVars.push('user');
script(type='text/javascript').
window.env = !{JSON.stringify(env)};
window.env = !{JSON.stringify(env._.pick(env, clientVars))};
!= env.getManifestFiles("app")

File diff suppressed because one or more lines are too long

View file

@ -144,6 +144,7 @@
include ./chat-box
.alert.alert-info.alert-sm
!= ' ' + env.t('tavernAlert1') + ' <a href="https://github.com/HabitRPG/habitrpg/issues/2760" target="_blank">' + env.t('tavernAlert2') + '</a>.<br />' + env.t('moderatorIntro1')
//- FIXME this could be rendered server-side?
span(ng-repeat='mod in env.mods')
|&nbsp; &nbsp;
span(ng-if='::mod.contributor.admin',popover=env.t('gamemaster'),popover-trigger='mouseenter',popover-placement='right')

View file

@ -6,7 +6,7 @@ script(id='modals/cards.html', type='text/ng-template')
.bg-info(style='padding:10px')
p {{::env.t('toAndFromCard', { toName: user.profile.name, fromName: user.items.special[cardType + 'Received'][0]})}}
hr
markdown(text='{{::cardMessage}}')
markdown(text='::cardMessage')
.modal-footer
small.pull-left {{::env.t(cardType + 'CardExplanation')}}
button.btn.btn-default(ng-click='user.ops.readCard({params: {cardType: cardType}}); $close()')=env.t('ok')

View file

@ -1,18 +1,23 @@
ul.items.rewards(ng-if='main && list.type=="reward"')
li.task.reward-item(ng-repeat='item in itemStore',popover-trigger='mouseenter', popover-placement='top', popover='{{item.key == "armoire" && !user.flags.armoireEmpty ? env.t("armoireNotesFull") + armoireCount(user.items.gear.owned) : item.notes()}}')
mixin reward(item)
li.task.reward-item(popover-trigger='mouseenter', popover-placement='top', popover='{{::#{item}.notes()}}')&attributes(attributes)
// right-hand side control buttons
.task-meta-controls
span.task-notes
span.glyphicon.glyphicon-comment
//left-hand size commands
.task-controls.task-primary
input.visuallyhidden(
class="reward",
input.reward.visuallyhidden(
type='checkbox',
ui-keypress='{13:"buy(item)"}')
a.money.btn-buy.item-btn(ng-class='{highValue: item.value >= 1000}', ng-click='buy(item)')
ui-keypress='{13:"buy(#{item})"}')
a.money.btn-buy.item-btn(ng-class='::{highValue: #{item}.value >= 1000}', ng-click='::buy(#{item})')
span.shop_gold
span.reward-cost {{item.value}}
span.reward-cost {{::#{item}.value}}
// main content
span(ng-class='::{"shop_{{item.key}} shop-sprite item-img": true}').reward-img
p.task-text {{item.text()}}
span(ng-class='::{"shop_{{#{item}.key}} shop-sprite item-img": true}').reward-img
p.task-text {{::#{item}.text()}}
ul.items.rewards(ng-if='main && list.type=="reward"')
+reward('item')(ng-repeat='item in itemStore')
+reward('healthPotion')
+reward('armoire')(ng-if='user.flags.armoireEnabled',
popover='{{armoire.notes(user, armoireCount(user.items.gear.owned))}}')

View file

@ -26,7 +26,7 @@ html(ng-app='habitrpg', ng-controller='RootCtrl')
link(rel='stylesheet', type='text/css', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.4/css/bootstrap.min.css')
script(type='text/javascript').
window.env = !{JSON.stringify(env)};
window.env = !{JSON.stringify(env._.pick(env, env.clientVars))};
!= env.getManifestFiles("tmp_static_front")
script(type='text/javascript', src='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.4/js/bootstrap.min.js')
@ -51,7 +51,7 @@ html(ng-app='habitrpg', ng-controller='RootCtrl')
span.icon-bar
span.icon-bar
a.navbar-brand(href='#')
img(src='https://s3.amazonaws.com/habitica-assets/assets/habitica_lockup2_desat.png')
img(src='https://d2afqr2xdmyzvu.cloudfront.net/assets/habitica_lockup2_desat.png')
// Collect the nav links, forms, and other content for toggling
#bs-example-navbar-collapse-1.collapse.navbar-collapse
ul.nav.navbar-nav.navbar-right
@ -69,7 +69,7 @@ html(ng-app='habitrpg', ng-controller='RootCtrl')
button#header-play-button.btn.btn-primary.navbar-btn.navbar-right(ng-click='playButtonClick()')= env.t('playButtonFull')
#intro
h1=env.t('motivate1')
img.center-block.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/intro.png')
img.center-block.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/intro.png')
// insert intro images
.introcall.bg-success
h4= env.t('joinOthers')
@ -78,13 +78,13 @@ html(ng-app='habitrpg', ng-controller='RootCtrl')
.presslogos.text-center
= env.t('featuredIn')
br
img(src='https://s3.amazonaws.com/habitrpg-assets/front/images/presslogos/lifehacker.png')
img(src='https://s3.amazonaws.com/habitrpg-assets/front/images/presslogos/nyt-logo.png')
img(src='https://s3.amazonaws.com/habitrpg-assets/front/images/presslogos/makeuseof.png')
img(src='https://s3.amazonaws.com/habitrpg-assets/front/images/presslogos/Forbes_logo.png')
img(src='https://s3.amazonaws.com/habitrpg-assets/front/images/presslogos/Cnetlogo.png')
img(src='https://s3.amazonaws.com/habitrpg-assets/front/images/presslogos/Fast-Company-logo.png')
img(src='https://s3.amazonaws.com/habitrpg-assets/front/images/presslogos/kickstarter-logo.png')
img(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/presslogos/lifehacker.png')
img(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/presslogos/nyt-logo.png')
img(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/presslogos/makeuseof.png')
img(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/presslogos/Forbes_logo.png')
img(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/presslogos/Cnetlogo.png')
img(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/presslogos/Fast-Company-logo.png')
img(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/presslogos/kickstarter-logo.png')
#intro-text
.container-fluid
.row
@ -96,20 +96,20 @@ html(ng-app='habitrpg', ng-controller='RootCtrl')
.row
.col-md-3.col-md-offset-1
h2= env.t('testimonialHeading')
img.img-say.img-responsive.visible-lg-block(src='https://s3.amazonaws.com/habitrpg-assets/front/images/uses/publicSpaces.png')
img.img-say.img-responsive.visible-lg-block(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/uses/publicSpaces.png')
// Testimonials
.col-md-8.col-sm-10.col-sx-10
#quote-carousel.carousel.slide(data-interval='10000', data-ride='carousel')
// Bottom Carousel Indicators
ol.carousel-indicators
li.active(data-target='#quote-carousel', data-slide-to='0')
img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/Drag0nsilver.png', alt='')
img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/Drag0nsilver.png', alt='')
li(data-target='#quote-carousel', data-slide-to='1')
img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/frabjabulous.png', alt='')
img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/frabjabulous.png', alt='')
li(data-target='#quote-carousel', data-slide-to='2')
img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/AndeeLiao.png', alt='')
img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/AndeeLiao.png', alt='')
li(data-target='#quote-carousel', data-slide-to='3')
img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/AlexandraSo.png', alt='')
img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/AlexandraSo.png', alt='')
// Carousel Slides / Quotes
.carousel-inner.text-center
// Quote 1
@ -201,16 +201,16 @@ html(ng-app='habitrpg', ng-controller='RootCtrl')
.task-btn(style='display: inline-block; width: 1.62765em; height: 1.62765em; padding: 0px; margin:0px; line-height: 1.62765; text-align: center; color: rgb(34, 34, 34); float:left; cursor: pointer; vertical-align: top; left:0px; position:relative; background-color: rgb(220, 93, 93); border-right: 1px solid rgba(0, 0, 0, 0.25);') +
.task-btn(style='display: inline-block; width: 1.62765em; height: 1.62765em; padding: 0px; margin:0px; line-height: 1.62765; text-align: center; color: rgb(34, 34, 34); float:left; cursor: pointer; vertical-align: top; background-color: rgb(220, 93, 93); border-right: 0px none;') -
.task-desc(style='display: inline-block; font-family: Lato,sans-serif; padding: 0.6em 0px 0em 1em; letter-spacing: normal; float:left; word-wrap: break-word;')= env.t('businessSample5')
img.sample-img.visible-lg-inline-block(src='https://s3.amazonaws.com/habitrpg-assets/front/images/uses/coding.png')
img.sample-img.visible-lg-inline-block(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/uses/coding.png')
.usetweet-groups.col-lg-5.col-md-6.visible-lg-inline-block.visible-md-inline-block
.usetweet-group
img(data-toggle='tooltip', data-placement='top', title='frabjabulous', src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/frabjabulous.png')
img(data-toggle='tooltip', data-placement='top', title='frabjabulous', src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/frabjabulous.png')
.usetweet.tweet.popover.right
.arrow
.popover-content
= env.t('frabjabulousQuote')
.usetweet-group
img(data-toggle='tooltip', data-placement='top', title='_AlexandraSo_', src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/AlexandraSo.png')
img(data-toggle='tooltip', data-placement='top', title='_AlexandraSo_', src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/AlexandraSo.png')
.usetweet.tweet.popover.right
.arrow
.popover-content
@ -245,16 +245,16 @@ html(ng-app='habitrpg', ng-controller='RootCtrl')
.task-btn(style='display: inline-block; width: 1.62765em; height: 1.62765em; padding: 0px; margin:0px; line-height: 1.62765; text-align: center; color: rgb(34, 34, 34); float:left; cursor: pointer; vertical-align: top; left:0px; position:relative; background-color: rgb(255, 207, 66); border-right: 1px solid rgba(0, 0, 0, 0.25);') +
| &nbsp;
.task-desc(style='display: inline-block; font-family: Lato,sans-serif; padding: 0.6em 0px 0em 1em; letter-spacing: normal; float:left; word-wrap: break-word;')= env.t('healthSample5')
img.sample-img.visible-lg-inline-block(src='https://s3.amazonaws.com/habitrpg-assets/front/images/uses/clipart-rosemonkeyct-meditation.png')
img.sample-img.visible-lg-inline-block(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/uses/clipart-rosemonkeyct-meditation.png')
.usetweet-groups.col-lg-5.col-md-6.visible-lg-inline-block.visible-md-inline-block
.usetweet-group
img(data-toggle='tooltip', data-placement='top', title='EvaGantz', src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/EvaGantz.png')
img(data-toggle='tooltip', data-placement='top', title='EvaGantz', src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/EvaGantz.png')
.usetweet.tweet.popover.right
.arrow
.popover-content
= env.t('evagantzQuote')
.usetweet-group
img(data-toggle='tooltip', data-placement='top', title='supermouse35', src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/supermouse35.png')
img(data-toggle='tooltip', data-placement='top', title='supermouse35', src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/supermouse35.png')
.usetweet.tweet.popover.right
.arrow
.popover-content
@ -283,10 +283,10 @@ html(ng-app='habitrpg', ng-controller='RootCtrl')
| &nbsp;
.task-desc(style='display: inline-block; font-family: Lato,sans-serif; padding: 0.6em 0px 0em 1em; letter-spacing: normal; float:left; word-wrap: break-word;')= env.t('teamSample3')
br
img.sample-img.visible-lg-inline-block(src='https://s3.amazonaws.com/habitrpg-assets/front/images/uses/publicSpaces.png')
img.sample-img.visible-lg-inline-block(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/uses/publicSpaces.png')
.usetweet-groups.col-lg-5.col-md-6.visible-lg-inline-block.visible-md-inline-block
.usetweet-group
img(data-toggle='tooltip', data-placement='top', title='Althaire', src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/Althaire.png')
img(data-toggle='tooltip', data-placement='top', title='Althaire', src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/Althaire.png')
.usetweet.tweet.popover.right
.arrow
.popover-content
@ -321,15 +321,15 @@ html(ng-app='habitrpg', ng-controller='RootCtrl')
.task-btn(style='display: inline-block; width: 1.62765em; height: 1.62765em; padding: 0px; margin:0px; line-height: 1.62765; text-align: center; color: rgb(34, 34, 34); float:left; cursor: pointer; vertical-align: top; left:0px; position:relative; background-color: rgb(90, 98, 223); border-right: 1px solid rgba(0, 0, 0, 0.25);') +
.task-btn(style='display: inline-block; width: 1.62765em; height: 1.62765em; padding: 0px; margin:0px; line-height: 1.62765; text-align: center; color: rgb(34, 34, 34); float:left; cursor: pointer; vertical-align: top; background-color: rgb(90, 98, 223); border-right: 0px none;') -
.task-desc(style='display: inline-block; font-family: Lato,sans-serif; padding: 0.6em 0px 0em 1em; letter-spacing: normal; float:left; word-wrap: break-word;')= env.t('schoolSample5')
img.sample-img.visible-lg-inline-block(src='https://s3.amazonaws.com/habitrpg-assets/front/images/uses/reading.png')
img.sample-img.visible-lg-inline-block(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/uses/reading.png')
.usetweet-groups.col-lg-5.col-md-6.visible-lg-inline-block.visible-md-inline-block
.usetweet-group
img(data-toggle='tooltip', data-placement='top', title='InfH', src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/InfH.png')
img(data-toggle='tooltip', data-placement='top', title='InfH', src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/InfH.png')
.usetweet.tweet.popover.right
.arrow
.popover-content= env.t('infhQuote')
.usetweet-group
img(data-toggle='tooltip', data-placement='top', title='Drei-M', src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/Drei-M.png')
img(data-toggle='tooltip', data-placement='top', title='Drei-M', src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/Drei-M.png')
.usetweet.tweet.popover.right
.arrow
.popover-content
@ -364,10 +364,10 @@ html(ng-app='habitrpg', ng-controller='RootCtrl')
.task-btn(style='display: inline-block; width: 1.62765em; height: 1.62765em; padding: 0px; margin:0px; line-height: 1.62765; text-align: center; color: rgb(34, 34, 34); float:left; cursor: pointer; vertical-align: top; left:0px; position:relative; background-color: rgb(90, 98, 223); border-right: 1px solid rgba(0, 0, 0, 0.25);') +
| &nbsp;
.task-desc(style='display: inline-block; font-family: Lato,sans-serif; padding: 0.6em 0px 0em 1em; letter-spacing: normal; float:left; word-wrap: break-word;')= env.t('goalSample5')
img.sample-img.visible-lg-inline-block(src='https://s3.amazonaws.com/habitrpg-assets/front/images/uses/gaining_an_achievement_by_cosmic_caterpillar-d7uyv5z.png')
img.sample-img.visible-lg-inline-block(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/uses/gaining_an_achievement_by_cosmic_caterpillar-d7uyv5z.png')
.usetweet-groups.col-lg-5.col-md-6.visible-lg-inline-block.visible-md-inline-block
.usetweet-group
img(data-toggle='tooltip', data-placement='top', title='Kazui', src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/Kazui.png')
img(data-toggle='tooltip', data-placement='top', title='Kazui', src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/Kazui.png')
.usetweet.tweet.popover.right
.arrow
.popover-content
@ -402,15 +402,15 @@ html(ng-app='habitrpg', ng-controller='RootCtrl')
.task-btn(style='display: inline-block; width: 1.62765em; height: 1.62765em; padding: 0px; margin:0px; line-height: 1.62765; text-align: center; color: rgb(34, 34, 34); float:left; cursor: pointer; vertical-align: top; left:0px; position:relative; background-color: rgb(90, 98, 223); border-right: 1px solid rgba(0, 0, 0, 0.25);') +
| &nbsp;
.task-desc(style='display: inline-block; font-family: Lato,sans-serif; padding: 0.6em 0px 0em 1em; letter-spacing: normal; float:left; word-wrap: break-word;')= env.t('choreSample5')
img.sample-img.visible-lg-inline-block(src='https://s3.amazonaws.com/habitrpg-assets/front/images/uses/dusting_by_leephon.png')
img.sample-img.visible-lg-inline-block(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/uses/dusting_by_leephon.png')
.usetweet-groups.col-lg-5.col-md-6.visible-lg-inline-block.visible-md-inline-block
.usetweet-group
img(data-toggle='tooltip', data-placement='top', title='autumnesquirrel', src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/autumnesquirrel.png')
img(data-toggle='tooltip', data-placement='top', title='autumnesquirrel', src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/autumnesquirrel.png')
.usetweet.tweet.popover.right
.arrow
.popover-content= env.t('autumnesquirrelQuote')
.usetweet-group
img(data-toggle='tooltip', data-placement='top', title='irishfeet123', src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/irishfeet123.png')
img(data-toggle='tooltip', data-placement='top', title='irishfeet123', src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/irishfeet123.png')
.usetweet.tweet.popover.right
.arrow
.popover-content
@ -430,7 +430,7 @@ html(ng-app='habitrpg', ng-controller='RootCtrl')
br
= env.t('rewardByline2')
.scrolltweet.hidden-xs.hidden-sm
img.scrolltweet-image(data-toggle='tooltip', data-placement='top', title='Elmi', src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/Elmi.png')
img.scrolltweet-image(data-toggle='tooltip', data-placement='top', title='Elmi', src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/Elmi.png')
.tweet.popover.right.pull-right
.arrow
.popover-content= env.t('elmiQuote')
@ -440,12 +440,12 @@ html(ng-app='habitrpg', ng-controller='RootCtrl')
span.glyphicon.glyphicon-check
h2
span.glyphicon.glyphicon-arrow-down
img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/misc/shop_gold.png')
img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/misc/shop_gold.png')
img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/misc/shop_gold.png')
img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/misc/shop_gold.png')
img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/misc/shop_gold.png')
img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/misc/shop_gold.png')
h2
span.glyphicon.glyphicon-arrow-down
img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/TVreward.png')
img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/TVreward.png')
button.btn.btn-primary.btn-lg.fixedcta.gamifybutton(ng-click='playButtonClick()')= env.t('gamifyButton')
section#levels.container-fluid
.row
@ -459,17 +459,17 @@ html(ng-app='habitrpg', ng-controller='RootCtrl')
br
= env.t('unlockByline2')
.scrolltweet.hidden-xs.hidden-sm
img.scrolltweet-image(data-toggle='tooltip', data-placement='top', title='16bitFil', src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/16bitFil.png')
img.scrolltweet-image(data-toggle='tooltip', data-placement='top', title='16bitFil', src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/16bitFil.png')
.tweet.popover.right.pull-right
.arrow
.popover-content
= env.t('sixteenBitFilQuote')
.col-lg-4.col-md-4.col-lg-offset-1.col-sm-8.well.col-xs-8.col-xs-offset-2.col-sm-offset-2
.avatarscroll
img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/avatar/avatarstatic.png')
img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/avatar/avatarstatic.png')
h2
span.glyphicon.glyphicon-arrow-down
img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/SAMPLEadventurers.png')
img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/SAMPLEadventurers.png')
button.btn.btn-primary.btn-lg.fixedcta.gamifybutton(ng-click='playButtonClick()')= env.t('gamifyButton')
section#health.bg-danger.container-fluid
.row
@ -480,7 +480,7 @@ html(ng-app='habitrpg', ng-controller='RootCtrl')
h2= env.t('punishHeading2')
p.sectioninfo= env.t('punishByline')
.scrolltweet.hidden-xs.hidden-sm
img.scrolltweet-image(data-toggle='tooltip', data-placement='top', title='Zelah Meyer', src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/Zelah_Meyer.png')
img.scrolltweet-image(data-toggle='tooltip', data-placement='top', title='Zelah Meyer', src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/Zelah_Meyer.png')
.tweet.popover.right.pull-right
.arrow
.popover-content
@ -515,13 +515,13 @@ html(ng-app='habitrpg', ng-controller='RootCtrl')
br
= env.t('questByline2')
.scrolltweet.hidden-xs.hidden-sm
img.scrolltweet-image(data-toggle='tooltip', data-placement='top', title='skysailor', src='https://s3.amazonaws.com/habitrpg-assets/front/images/testimonials/skysailor.png')
img.scrolltweet-image(data-toggle='tooltip', data-placement='top', title='skysailor', src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/testimonials/skysailor.png')
.tweet.popover.right.pull-right
.arrow
.popover-content
= env.t('skysailorQuote')
.col-lg-4.col-md-6.col-md-offset-1.col-lg-offset-1.col-sm-10.well.col-xs-10.col-xs-offset-1.col-sm-offset-1.well-lg
img.clearfix.img-responsive.center-block(style='padding-bottom:30px;', src="https://s3.amazonaws.com/habitrpg-assets/front/images/Quest_dilatory_drag'onSmall.png")
img.clearfix.img-responsive.center-block(style='padding-bottom:30px;', src="https://d2afqr2xdmyzvu.cloudfront.net/front/images/Quest_dilatory_dragonSmall.png")
.row
.col-lg-5.col-md-5.col-sm-5.col-sm-offset-0.col-xs-5.col-xs-offset-0
.health-progress
@ -531,7 +531,7 @@ html(ng-app='habitrpg', ng-controller='RootCtrl')
.bar
div(style='width:20%;') &nbsp;
.quest-friend-img
img(src='https://s3.amazonaws.com/habitrpg-assets/front/images/party/AnnaCosplay.png')
img(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/party/AnnaCosplay.png')
.col-md-1.col-sm-1.col-xs-1
h2
span.glyphicon.glyphicon-arrow-right
@ -543,7 +543,7 @@ html(ng-app='habitrpg', ng-controller='RootCtrl')
.bar
div(style='width:0%;') &nbsp;
.quest-friend-img
img(src='https://s3.amazonaws.com/habitrpg-assets/front/images/party/GrimReaper.png')
img(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/party/GrimReaper.png')
button.btn.btn-primary.btn-lg.fixedcta.gamifybutton(ng-click='playButtonClick()')= env.t('gamifyButton')
section#features.bg-info.container-fluid
.row
@ -551,29 +551,29 @@ html(ng-app='habitrpg', ng-controller='RootCtrl')
.row
.col-md-4.col-md-offset-2.col-sm-6
.feature-img
img.center-block.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/Mount.png')
img.center-block.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/Mount.png')
.featuretext
h4= env.t('featurePetHeading')
p
= env.t('featurePetByline')
.col-md-4.col-sm-6
.feature-img.center-block
img(src='https://s3.amazonaws.com/habitrpg-assets/front/images/achievement-triadbingo.png')
img(src='https://s3.amazonaws.com/habitrpg-assets/front/images/achievement-perfect.png')
img(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/achievement-triadbingo.png')
img(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/achievement-perfect.png')
.featuretext
h4= env.t('featureAchievementHeading')
p= env.t('featureAchievementByline')
.row
.col-md-4.col-md-offset-2.col-sm-6
.feature-img
img.center-block.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/Promo_springclasses2015.png')
img.center-block.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/Promo_springclasses2015.png')
.featuretext
h4= env.t('featureEquipHeading')
p
= env.t('featureEquipByline')
.col-md-4.col-sm-6
.feature-img
img.center-block.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/Party-Header.png')
img.center-block.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/Party-Header.png')
.featuretext
h4= env.t('featureSocialHeading')
p
@ -593,19 +593,19 @@ html(ng-app='habitrpg', ng-controller='RootCtrl')
.row
.col-lg-2.col-md-2.col-md-offset-1.col-sm-4.col-sm-offset-1.col-xs-6.col-xs-offset-1
a(href='http://ionicframework.com/')
img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/presslogos/ionic-logo-horizontal-transparent.png')
img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/presslogos/ionic-logo-horizontal-transparent.png')
.col-lg-2.col-lg-offset-0.col-md-2.col-md-offset-0.col-sm-4.col-sm-offset-1.col-xs-6.col-xs-offset-1
a(href='https://www.jetbrains.com/webstorm/')
img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/presslogos/logo_webstorm.png')
img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/presslogos/logo_webstorm.png')
.col-lg-2.col-lg-offset-0.col-md-2.col-md-offset-0.col-sm-4.col-sm-offset-1.col-xs-6.col-xs-offset-1
a(href='http://github.com/')
img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/presslogos/GitHub_Logo.png')
img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/presslogos/GitHub_Logo.png')
.col-lg-2.col-lg-offset-0.col-md-2.col-md-offset-0.col-sm-4.col-sm-offset-1.col-xs-6.col-xs-offset-1
a(href='https://trello.com/')
img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/presslogos/trello-logo-blue.png')
img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/presslogos/trello-logo-blue.png')
.col-lg-2.col-lg-offset-0.col-md-2.col-md-offset-0.col-sm-4.col-sm-offset-1.col-xs-6.col-xs-offset-1
a(href='https://slack.com/')
img.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/presslogos/landing_slack_hash_wordmark_logo.png')
img.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/front/images/presslogos/landing_slack_hash_wordmark_logo.png')
.row.footer-content
include ../shared/footer

File diff suppressed because one or more lines are too long

View file

@ -7,7 +7,7 @@ html(ng-app='habitrpg')
title=env.t('newStuff')
script(type='text/javascript').
window.env = !{JSON.stringify(env)};
window.env = !{JSON.stringify(env._.pick(env, env.clientVars))};
link(rel='shortcut icon', href='#{env.getBuildUrl("favicon.ico")}?v=4')