diff --git a/feeds/lille.go b/feeds/lille.go index b588dae..562be73 100644 --- a/feeds/lille.go +++ b/feeds/lille.go @@ -8,6 +8,9 @@ import ( "github.com/elastic/go-elasticsearch/v9" "github.com/elastic/go-elasticsearch/v9/typedapi/esdsl" + "github.com/elastic/go-elasticsearch/v9/typedapi/types/enums/fieldvaluefactormodifier" + "github.com/elastic/go-elasticsearch/v9/typedapi/types/enums/functionboostmode" + "github.com/elastic/go-elasticsearch/v9/typedapi/types/enums/functionscoremode" "tangled.org/dhawos.dev/bskyfeedctl/indexer" ) @@ -23,10 +26,40 @@ func LilleFeed(es *elasticsearch.TypedClient) *Feed { DisplayName: "Lille", Description: "Ce fil est en développement. Il est susceptible de changer. Un fil non officiel contenant les posts parlant de Lille et de la MEL.\n Je n'ai aucun lien avec la MEL ou la ville de Lille.", SelectionFunc: func(ctx context.Context, req *FeedRequest) (FeedSkeletonResponse, error) { + decayFunction := esdsl.NewDateDecayFunction().AddDecayFunctionBaseDateMathDuration("createdAt", esdsl.NewDecayPlacementDateMathDuration().Decay(0.5).Scale(esdsl.NewDuration().String("1d"))) + boolQuery := esdsl.NewBoolQuery(). + Should(esdsl.NewMatchQuery(indexer.TextFieldName, "Lille"), + esdsl.NewMatchPhraseQuery(indexer.TextFieldName, "Villeneuve d'Ascq"), + esdsl.NewMatchQuery(indexer.TextFieldName, "Roubaix"), + esdsl.NewMatchQuery(indexer.TextFieldName, "Tourcoing"), + esdsl.NewMatchQuery(indexer.TextFieldName, "MEL"), + esdsl.NewMatchPhraseQuery(indexer.TextFieldName, "Métropole européenne de Lille"), + esdsl.NewWildcardQuery(indexer.TextFieldName, "Lill*")). + MinimumShouldMatch(esdsl.NewMinimumShouldMatch().Int(1)) + popularityQuery := esdsl.NewFunctionScoreQuery(). + Query(boolQuery). + Functions( + esdsl.NewFunctionScore(). + FieldValueFactor( + esdsl.NewFieldValueFactorScoreFunction(). + Field("likes"). + Modifier(fieldvaluefactormodifier.Sqrt), + ), + esdsl.NewFunctionScore(). + FieldValueFactor( + esdsl.NewFieldValueFactorScoreFunction(). + Field("reposts"). + Modifier(fieldvaluefactormodifier.Sqrt), + ), + ).ScoreMode(functionscoremode.Sum).BoostMode(functionboostmode.Replace) + scoreQuery := esdsl.NewFunctionScoreQuery().Query(popularityQuery).Functions( + esdsl.NewFunctionScore().Gauss(decayFunction), + ) res, err := es.Search(). Index(indexer.IndexName). - Query(esdsl.NewMatchQuery(indexer.TextFieldName, "lille")). + Query(scoreQuery). Size(int(req.Limit)). + Sort(esdsl.NewSortCombinations().Field("_score")). Do(ctx) if err != nil { return FeedSkeletonResponse{}, fmt.Errorf("requesting elastic for lille related posts: %w", err) @@ -35,10 +68,11 @@ func LilleFeed(es *elasticsearch.TypedClient) *Feed { for _, hit := range res.Hits.Hits { var post indexer.PostDocument err := json.Unmarshal(hit.Source_, &post) + fmt.Println(*hit.Id_, *hit.Score_, post.Cid, post.CreatedAt, post.Text, post.Likes, post.Reposts) if err != nil { - return FeedSkeletonResponse{}, fmt.Errorf("unmarshalling result from elasticsearch", err) + return FeedSkeletonResponse{}, fmt.Errorf("unmarshalling result from elasticsearch: %w", err) } - result = append(result, FeedResponse{Post: post.DID}) + result = append(result, FeedResponse{Post: post.Cid}) } return FeedSkeletonResponse{Cursor: "", Feed: result}, nil }, diff --git a/indexer/elasticsearch.go b/indexer/elasticsearch.go index 4fe8b12..a76675c 100644 --- a/indexer/elasticsearch.go +++ b/indexer/elasticsearch.go @@ -3,6 +3,7 @@ package indexer import ( "context" "fmt" + "time" "github.com/elastic/go-elasticsearch/v9" "github.com/elastic/go-elasticsearch/v9/typedapi/esdsl" @@ -19,10 +20,11 @@ type ElasticsearchConfig struct { } type PostDocument struct { - DID string `json:"did"` - Text string `json:"text"` - Likes int `json:"likes"` - Reposts int `json:"reposts"` + Cid string `json:"cid"` + Text string `json:"text"` + Likes int `json:"likes"` + Reposts int `json:"reposts"` + CreatedAt time.Time `json:"createdAt"` } func NewElasticTypedClient(config ElasticsearchConfig) (*elasticsearch.TypedClient, error) { @@ -36,6 +38,7 @@ func NewElasticTypedClient(config ElasticsearchConfig) (*elasticsearch.TypedClie func insertPost(ctx context.Context, client *elasticsearch.TypedClient, post *PostDocument) error { _, err := client.Index(IndexName). Document(*post). + Id(post.Cid). Do(ctx) return err } @@ -51,10 +54,11 @@ func InitIndex(ctx context.Context, es *elasticsearch.TypedClient) error { didProperty := types.NewKeywordProperty() didProperty.Index = new(false) properties := esdsl.NewTypeMapping(). - AddProperty("did", didProperty). + AddProperty("cid", didProperty). AddProperty("text", types.NewTextProperty()). AddProperty("likes", types.NewIntegerNumberProperty()). - AddProperty("reposts", types.NewIntegerNumberProperty()) + AddProperty("reposts", types.NewIntegerNumberProperty()). + AddProperty("createdAt", types.NewDateProperty()) _, err = es.Indices.Create(IndexName).Mappings(properties).Settings(esdsl.NewIndexSettings().NumberOfReplicas("0")).Do(ctx) return err } diff --git a/indexer/firehose.go b/indexer/firehose.go index c43363f..010b94d 100644 --- a/indexer/firehose.go +++ b/indexer/firehose.go @@ -28,7 +28,7 @@ type FirehoseConfig struct { } type BlueskyPost struct { - did string + cid string post *bsky.FeedPost } @@ -82,7 +82,7 @@ func (fh *FirehoseListener) SubscribeFirehose(ctx context.Context, channels *Blu switch action := rec.(type) { case *bsky.FeedPost: post := &BlueskyPost{ - did: cid.String(), + cid: cid.String(), post: action, } channels.Posts <- post @@ -109,7 +109,7 @@ func HandlePosts(ctx context.Context, input <-chan *BlueskyPost, es *elasticsear for post := range input { if slices.Contains(post.post.Langs, "fr") { err := insertPost(ctx, es, &PostDocument{ - DID: post.did, + Cid: post.cid, Text: post.post.Text, Likes: 0, Reposts: 0, diff --git a/indexer/seed.go b/indexer/seed.go index acfb92b..344fae9 100644 --- a/indexer/seed.go +++ b/indexer/seed.go @@ -5,6 +5,7 @@ import ( _ "embed" "encoding/json" "fmt" + "time" "github.com/elastic/go-elasticsearch/v9" "github.com/elastic/go-elasticsearch/v9/typedapi/types" @@ -13,16 +14,52 @@ import ( //go:embed test_data.json var testData []byte +func normalizeDates(parsedData []PostDocument) []PostDocument { + minDate := time.Now() + maxDate := time.Time{} + for _, item := range parsedData { + if item.CreatedAt.Before(minDate) { + minDate = item.CreatedAt + } + if item.CreatedAt.After(maxDate) { + maxDate = item.CreatedAt + } + } + normalizedRange := time.Hour * 48 + normalizedRangeStart := time.Now().Add(-normalizedRange) + initialRange := maxDate.Sub(minDate) + newPosts := make([]PostDocument, 0, len(parsedData)) + for _, item := range parsedData { + normalizedDelta := (float64(normalizedRange) * float64(item.CreatedAt.Sub(minDate).Nanoseconds()) / float64(initialRange.Nanoseconds())) + normalizedDate := normalizedRangeStart.Add(time.Duration(normalizedDelta)) + newPost := item + newPost.CreatedAt = normalizedDate + newPosts = append(newPosts, newPost) + } + return newPosts +} + func Seed(ctx context.Context, es *elasticsearch.TypedClient) error { var parsedData []PostDocument err := json.Unmarshal(testData, &parsedData) if err != nil { return fmt.Errorf("could not parse test data: %w", err) } + parsedData = normalizeDates(parsedData) + + // Delete previous index + _, err = es.Indices.Delete(IndexName).Do(ctx) + if err != nil { + return fmt.Errorf("deleting previous index: %w", err) + } + err = InitIndex(ctx, es) + if err != nil { + return fmt.Errorf("initializing index: %w", err) + } bulk := es.Bulk() indexName := IndexName for _, item := range parsedData { - if err := bulk.IndexOp(types.IndexOperation{Index_: &indexName}, item); err != nil { + if err := bulk.IndexOp(types.IndexOperation{Index_: &indexName, Id_: &item.Cid}, item); err != nil { return fmt.Errorf("adding item to bulk operation: %w", err) } } diff --git a/indexer/test_data.json b/indexer/test_data.json index 6fd54f9..a437d19 100644 --- a/indexer/test_data.json +++ b/indexer/test_data.json @@ -1,102 +1,102 @@ [ - {"did": "did:plc:dev001", "text": "Je viens de passer 3 heures à chercher un bug qui venait d'un point-virgule manquant. La vie de dev est magnifique.", "likes": 245, "reposts": 34}, - {"did": "did:plc:dev002", "text": "Le passage à Go pour nos microservices a divisé notre consommation de RAM par dix. Impressionnant !", "likes": 89, "reposts": 12}, - {"did": "did:plc:dev003", "text": "Est-ce qu'on peut s'accorder sur le fait que centrer une div en CSS n'est plus une excuse valable pour pleurer en 2026 ?", "likes": 512, "reposts": 88}, - {"did": "did:plc:dev004", "text": "Le cache est vide, la base de données est clean, mais ça ne marche toujours pas. Il est temps de prendre un café.", "likes": 134, "reposts": 45}, - {"did": "did:plc:dev005", "text": "Docker m'a simplifié la vie, jusqu'au jour où j'ai dû comprendre comment fonctionnaient les networks internes.", "likes": 76, "reposts": 5}, - {"did": "did:plc:dev006", "text": "Rien de plus satisfaisant que de voir tous les tests unitaires passer au vert du premier coup.", "likes": 210, "reposts": 15}, - {"did": "did:plc:dev007", "text": "Je refuse de participer au débat Vim vs Emacs. VS Code avec les bons plugins, c'est la paix dans le monde.", "likes": 340, "reposts": 67}, - {"did": "did:plc:dev008", "text": "Mon historique Git est une tragédie en trois actes : 'wip', 'wip 2', et 'ça marche enfin pitié'.", "likes": 890, "reposts": 230}, - {"did": "did:plc:dev009", "text": "Le déploiement en production un vendredi soir ? Même pas peur. (Si, complètement terrifié).", "likes": 445, "reposts": 112}, - {"did": "did:plc:dev010", "text": "Kubernetes est formidable pour gérer la charge, mais la courbe d'apprentissage ressemble à un mur d'escalade sans prises.", "likes": 156, "reposts": 28}, - {"did": "did:plc:dev011", "text": "Je lis la documentation de cette API depuis ce matin, et j'en déduis que l'auteur déteste l'humanité.", "likes": 321, "reposts": 90}, - {"did": "did:plc:dev012", "text": "Un client m'a demandé de 'juste rajouter un bouton'. Si seulement il savait ce que ça implique en backend.", "likes": 278, "reposts": 54}, - {"did": "did:plc:dev013", "text": "TypeScript m'a sauvé de tellement d'erreurs bêtes que je ne peux plus m'en passer aujourd'hui.", "likes": 198, "reposts": 33}, - {"did": "did:plc:dev014", "text": "Pourquoi mon code marche-t-il sur ma machine locale mais explose complètement en staging ?", "likes": 402, "reposts": 105}, - {"did": "did:plc:dev015", "text": "La refactorisation de code legacy, c'est comme de l'archéologie : on ne sait jamais sur quoi on va tomber.", "likes": 167, "reposts": 19}, - {"did": "did:plc:dev016", "text": "Aujourd'hui, j'ai supprimé 500 lignes de code mort. Meilleure sensation de la semaine.", "likes": 289, "reposts": 41}, - {"did": "did:plc:dev017", "text": "Le syndrome de l'imposteur frappe toujours au moment où on te demande d'estimer la durée d'une tâche complexe.", "likes": 534, "reposts": 145}, - {"did": "did:plc:dev018", "text": "Je suis développeur fullstack, ce qui signifie que je suis moyen partout et expert nulle part.", "likes": 612, "reposts": 178}, - {"did": "did:plc:dev019", "text": "Le mode sombre n'est pas une option d'interface, c'est un mode de vie pour préserver mes yeux de développeur.", "likes": 345, "reposts": 72}, - {"did": "did:plc:dev020", "text": "Quelqu'un a décidé de changer l'architecture au milieu du sprint. Je vais pleurer en silence.", "likes": 123, "reposts": 24}, - {"did": "did:plc:dev021", "text": "L'Open Source est magnifique, sauf quand une dépendance critique est maintenue par une seule personne qui a disparu.", "likes": 476, "reposts": 99}, - {"did": "did:plc:dev022", "text": "Je passe plus de temps à nommer mes variables qu'à écrire la logique de mes fonctions.", "likes": 234, "reposts": 56}, - {"did": "did:plc:dev023", "text": "Le machine learning, c'est beaucoup de statistiques, énormément de données, et un peu de magie noire.", "likes": 189, "reposts": 37}, - {"did": "did:plc:dev024", "text": "Quand tu lances ta compilation en Rust et que tu as le temps d'aller préparer un repas complet avant la fin.", "likes": 312, "reposts": 65}, - {"did": "did:plc:dev025", "text": "La sécurité informatique devrait être pensée dès le premier jour, pas ajoutée comme un pansement à la fin.", "likes": 145, "reposts": 21}, - {"did": "did:plc:lil026", "text": "Rien de tel qu'une pinte en terrasse sur la Grand'Place de Lille quand le soleil pointe enfin le bout de son nez.", "likes": 412, "reposts": 34}, - {"did": "did:plc:lil027", "text": "Le marché de Wazemmes le dimanche matin, c'est vraiment l'âme de la ville. Les couleurs, les odeurs, l'ambiance !", "likes": 289, "reposts": 45}, - {"did": "did:plc:lil028", "text": "Un vrai Welsh au Maroilles, avec une bonne portion de frites. C'est lourd, c'est gras, mais c'est tellement bon.", "likes": 567, "reposts": 112}, - {"did": "did:plc:lil029", "text": "Se perdre volontairement dans les petites rues pavées du Vieux-Lille reste mon activité préférée du week-end.", "likes": 345, "reposts": 56}, - {"did": "did:plc:lil030", "text": "Le parc de la Citadelle en automne offre des couleurs incroyables, parfait pour une balade digestive.", "likes": 198, "reposts": 23}, - {"did": "did:plc:lil031", "text": "Je suis passé par la gare Saint-Sauveur hier soir, l'exposition temporaire est vraiment dingue cette année.", "likes": 156, "reposts": 18}, - {"did": "did:plc:lil032", "text": "La Braderie me manque. L'odeur des moules, les montagnes de coquilles, chiner des vieux vinyles dès l'aube...", "likes": 890, "reposts": 245}, - {"did": "did:plc:lil033", "text": "Traverser Euralille aux heures de pointe m'a rappelé pourquoi j'évite les centres commerciaux en semaine.", "likes": 112, "reposts": 14}, - {"did": "did:plc:lil034", "text": "Le beffroi de l'Hôtel de Ville éclairé dans la brume nocturne, ça donne des photos dignes d'un film noir.", "likes": 423, "reposts": 67}, - {"did": "did:plc:lil035", "text": "Impossible de passer par la rue de Béthune sans croiser quelqu'un qu'on connaît, c'est la loi de Lille.", "likes": 234, "reposts": 31}, - {"did": "did:plc:lil036", "text": "Un merveilleux de chez Fred pour le goûter, c'est le genre de petit plaisir qu'on ne refuse jamais.", "likes": 678, "reposts": 145}, - {"did": "did:plc:lil037", "text": "Le métro lillois en plein été, sans climatisation... une expérience tropicale que je ne recommande pas.", "likes": 345, "reposts": 89}, - {"did": "did:plc:lil038", "text": "J'ai découvert un petit estaminet planqué vers la rue de la Monnaie, l'accueil était chaleureux comme on les aime ici.", "likes": 187, "reposts": 22}, - {"did": "did:plc:lil039", "text": "Le stade Pierre Mauroy bouillonnait ce week-end pour le match du LOSC. Une ambiance vraiment spectaculaire !", "likes": 512, "reposts": 98}, - {"did": "did:plc:lil040", "text": "Trouver une place pour se garer vers République le samedi après-midi relève de la mission impossible.", "likes": 145, "reposts": 17}, - {"did": "did:plc:lil041", "text": "La vue depuis la terrasse panoramique d'Euratechnologies au coucher du soleil est très sous-estimée.", "likes": 267, "reposts": 39}, - {"did": "did:plc:lil042", "text": "Lire un livre tranquillement assis sur les marches de l'Opéra pendant que la ville s'agite autour, un vrai bonheur.", "likes": 198, "reposts": 24}, - {"did": "did:plc:lil043", "text": "La pluie lilloise a son propre caractère : elle ne mouille pas beaucoup mais elle dure trois jours d'affilée.", "likes": 456, "reposts": 77}, - {"did": "did:plc:lil044", "text": "Le Furet du Nord sur la Grand'Place, je peux y entrer pour cinq minutes et y rester bloqué deux heures sans m'en rendre compte.", "likes": 389, "reposts": 62}, - {"did": "did:plc:lil045", "text": "Faire du vélo sur les pavés du Vieux-Lille demande une suspension robuste ou des poignets en acier trempé.", "likes": 210, "reposts": 29}, - {"did": "did:plc:lil046", "text": "La friterie Meunier reste mon incontournable quand je veux manger sur le pouce près de la gare.", "likes": 167, "reposts": 15}, - {"did": "did:plc:lil047", "text": "La piscine musée de Roubaix, juste à côté, est un chef-d'œuvre absolu. J'y retournerais tous les mois si je pouvais.", "likes": 478, "reposts": 105}, - {"did": "did:plc:lil048", "text": "Les soirées étudiantes rue Masséna me semblent bien loin maintenant, mais le bruit porte toujours jusqu'à chez moi !", "likes": 123, "reposts": 11}, - {"did": "did:plc:lil049", "text": "J'ai testé la nouvelle micro-brasserie près de Cormontaigne, leur IPA est une petite merveille d'amertume.", "likes": 189, "reposts": 26}, - {"did": "did:plc:lil050", "text": "Rien de plus lillois que de terminer une soirée en mangeant une frite-fricadelle à 3h du matin.", "likes": 567, "reposts": 134}, - {"did": "did:plc:cyc051", "text": "Le vent de face sur 20 kilomètres ce matin m'a fait regretter de ne pas avoir pris l'option vélo électrique.", "likes": 345, "reposts": 45}, - {"did": "did:plc:cyc052", "text": "Sortie gravel incroyable ce week-end dans la forêt. J'ai de la boue jusqu'aux oreilles mais je suis heureux.", "likes": 210, "reposts": 28}, - {"did": "did:plc:cyc053", "text": "Crever un pneu sous la pluie à 5 km de la maison, c'est exactement pour ça que j'ai toujours trois chambres à air sur moi.", "likes": 178, "reposts": 19}, - {"did": "did:plc:cyc054", "text": "Le vélotaf, c'est l'assurance d'arriver au bureau réveillé, de bonne humeur, et avec un bon petit cardio fait pour la journée.", "likes": 678, "reposts": 156}, - {"did": "did:plc:cyc055", "text": "J'ai enfin réussi à battre mon record personnel sur ce segment Strava en côte. Mes poumons sont en feu.", "likes": 289, "reposts": 37}, - {"did": "did:plc:cyc056", "text": "Les pistes cyclables qui s'arrêtent brusquement au milieu de nulle part sont la pire aberration urbaine.", "likes": 890, "reposts": 245}, - {"did": "did:plc:cyc057", "text": "Entendre le doux ronronnement d'une chaîne parfaitement nettoyée et lubrifiée est ASMR pour tout cycliste qui se respecte.", "likes": 412, "reposts": 67}, - {"did": "did:plc:cyc058", "text": "Les automobilistes qui te frôlent à 80 km/h sur une route de campagne... On partage la route, s'il vous plaît !", "likes": 756, "reposts": 198}, - {"did": "did:plc:cyc059", "text": "Rien de mieux que le premier coup de pédale un dimanche matin très tôt, quand la ville dort encore.", "likes": 345, "reposts": 52}, - {"did": "did:plc:cyc060", "text": "Investir dans un bon cuissard est la meilleure décision que j'ai pu prendre. Mon séant me remercie à chaque longue sortie.", "likes": 234, "reposts": 31}, - {"did": "did:plc:cyc061", "text": "Le bikepacking m'a appris le minimalisme. Chaque gramme compte quand on doit monter un col alpin.", "likes": 198, "reposts": 24}, - {"did": "did:plc:cyc062", "text": "Je disais que le vélo aérodynamique ne changerait rien, jusqu'à ce que je l'essaye sur le plat. C'est une fusée.", "likes": 156, "reposts": 17}, - {"did": "did:plc:cyc063", "text": "La règle non écrite du cyclisme : on se salue toujours quand on se croise, peu importe le vélo.", "likes": 567, "reposts": 89}, - {"did": "did:plc:cyc064", "text": "Faire l'ascension du Mont Ventoux était mon objectif de l'année. La vue au sommet efface toute la douleur.", "likes": 423, "reposts": 76}, - {"did": "did:plc:cyc065", "text": "Rouler de nuit avec un bon éclairage apporte une ambiance totalement différente, presque méditative.", "likes": 187, "reposts": 22}, - {"did": "did:plc:cyc066", "text": "Le nettoyage du vélo après une sortie hivernale sur des routes pleines de sel prend plus de temps que la sortie elle-même.", "likes": 210, "reposts": 34}, - {"did": "did:plc:cyc067", "text": "Un rayon cassé en plein milieu d'une descente, c'est la grosse frayeur garantie. Heureusement, plus de peur que de mal.", "likes": 134, "reposts": 12}, - {"did": "did:plc:cyc068", "text": "Voir des enfants apprendre à faire du vélo sans roulettes pour la première fois me donne toujours le sourire.", "likes": 456, "reposts": 65}, - {"did": "did:plc:cyc069", "text": "Le choix des pneus est un débat sans fin. Tubeless ou chambre à air ? La guerre est éternelle sur les forums.", "likes": 278, "reposts": 41}, - {"did": "did:plc:cyc070", "text": "S'arrêter à la boulangerie à mi-parcours pour manger un flan, c'est la base de l'entraînement d'endurance.", "likes": 612, "reposts": 105}, - {"did": "did:plc:cyc071", "text": "La sensation de glisse sur un bel asphalte tout neuf, sans aucune imperfection, c'est la perfection absolue.", "likes": 389, "reposts": 54}, - {"did": "did:plc:cyc072", "text": "Les sas vélos aux feux rouges sont utiles uniquement si les voitures ne viennent pas s'y garer systématiquement.", "likes": 534, "reposts": 123}, - {"did": "did:plc:cyc073", "text": "Partir pour 30km et finir par en faire 80 parce qu'on s'est perdu et que le paysage était trop beau.", "likes": 467, "reposts": 88}, - {"did": "did:plc:cyc074", "text": "Les passages à niveau par temps humide : la hantise de tout cycliste qui tient à sa clavicule.", "likes": 321, "reposts": 47}, - {"did": "did:plc:cyc075", "text": "Réparer soi-même son dérailleur avec un tutoriel sur internet, c'est 20% de technique et 80% de patience.", "likes": 245, "reposts": 33}, - {"did": "did:plc:cin076", "text": "Le dernier film de Denis Villeneuve prouve encore une fois qu'il est l'un des plus grands réalisateurs de notre époque.", "likes": 789, "reposts": 156}, - {"did": "did:plc:cin077", "text": "Regarder un film en VF quand il a été pensé pour sa langue d'origine, c'est perdre une grande partie du jeu d'acteur.", "likes": 456, "reposts": 89}, - {"did": "did:plc:cin078", "text": "Le cinéma d'horreur indépendant propose des œuvres tellement plus intelligentes que les gros blockbusters remplis de jumpscares.", "likes": 345, "reposts": 67}, - {"did": "did:plc:cin079", "text": "Entendre les gens manger leurs pop-corn la bouche ouverte pendant une scène dramatique silencieuse... L'enfer sur terre.", "likes": 890, "reposts": 234}, - {"did": "did:plc:cin080", "text": "La photographie de Roger Deakins me laisse sans voix à chaque fois. Chaque plan de ses films est un tableau.", "likes": 512, "reposts": 98}, - {"did": "did:plc:cin081", "text": "Un bon scénario peut sauver une mauvaise réalisation, mais l'inverse est très rarement vrai.", "likes": 423, "reposts": 76}, - {"did": "did:plc:cin082", "text": "Je me suis refait l'intégrale du Seigneur des Anneaux en version longue ce week-end. Plus de 11 heures de pur bonheur.", "likes": 956, "reposts": 312}, - {"did": "did:plc:cin083", "text": "Les effets spéciaux pratiques (maquillage, animatroniques) vieilliront toujours mieux que des CGI bâclés.", "likes": 678, "reposts": 145}, - {"did": "did:plc:cin084", "text": "Le cinéma coréen ne cesse de me surprendre par sa capacité à mélanger les genres au sein d'un même film.", "likes": 389, "reposts": 65}, - {"did": "did:plc:cin085", "text": "La musique de Hans Zimmer, c'est littéralement 50% de l'impact émotionnel des films d'Interstellar à Inception.", "likes": 734, "reposts": 167}, - {"did": "did:plc:cin086", "text": "Aller seul au cinéma un mardi après-midi, c'est un luxe incroyable qu'il faut savourer au moins une fois.", "likes": 412, "reposts": 88}, - {"did": "did:plc:cin087", "text": "Les fins ouvertes qui te laissent réfléchir pendant des jours après le générique sont de loin mes préférées.", "likes": 289, "reposts": 45}, - {"did": "did:plc:cin088", "text": "Les remakes en prises de vues réelles des vieux classiques d'animation perdent presque toujours leur âme originelle.", "likes": 567, "reposts": 123}, - {"did": "did:plc:cin089", "text": "Le générique de fin devrait être obligatoire à regarder, par respect pour les milliers de personnes qui ont fait le film.", "likes": 234, "reposts": 41}, - {"did": "did:plc:cin090", "text": "Tarantino reste le maître absolu des dialogues qui ne servent à rien pour l'intrigue mais qui définissent parfaitement un personnage.", "likes": 645, "reposts": 112}, - {"did": "did:plc:cin091", "text": "Je préfère voir un réalisateur prendre des risques et rater son film que regarder une production aseptisée sans saveur.", "likes": 378, "reposts": 72}, - {"did": "did:plc:cin092", "text": "Les bandes-annonces qui dévoilent littéralement tous les rebondissements de l'intrigue devraient être illégales.", "likes": 823, "reposts": 210}, - {"did": "did:plc:cin093", "text": "Découvrir une pépite méconnue dans les tréfonds d'une plateforme de streaming est un sentiment merveilleux.", "likes": 198, "reposts": 28}, - {"did": "did:plc:cin094", "text": "La salle de l'UGC Ciné Cité était pleine à craquer hier soir, ça fait plaisir de voir que les gens vont toujours au cinéma.", "likes": 267, "reposts": 35}, - {"did": "did:plc:cin095", "text": "Certains acteurs ont un tel charisme qu'ils arrivent à te garder scotché à l'écran même s'ils ne font que boire un café.", "likes": 312, "reposts": 51}, - {"did": "did:plc:cin096", "text": "Le rythme d'un film est primordial. Un film de 3 heures peut passer en un éclair, et un film d'une heure trente peut sembler interminable.", "likes": 489, "reposts": 84}, - {"did": "did:plc:cin097", "text": "Je collectionne les affiches de films depuis dix ans, mes murs n'ont plus un seul centimètre carré de libre.", "likes": 145, "reposts": 17}, - {"did": "did:plc:cin098", "text": "L'ambiance lors des avant-premières avec la présence de l'équipe du film apporte vraiment un plus à l'expérience.", "likes": 223, "reposts": 29}, - {"did": "did:plc:cin099", "text": "Revoir un film d'enfance avec des yeux d'adulte et découvrir un sous-texte qu'on n'avait jamais compris, c'est fascinant.", "likes": 534, "reposts": 92}, - {"did": "did:plc:cin100", "text": "Le son Dolby Atmos dans une bonne salle obscure, c'est quand même la garantie d'une immersion totale.", "likes": 367, "reposts": 61} + {"cid": "cid:dev001", "text": "Je viens de passer 3 heures à chercher un bug qui venait d'un point-virgule manquant. La vie de dev est magnifique.", "likes": 245, "reposts": 34, "createdAt": "2026-05-15T09:23:11Z"}, + {"cid": "cid:dev002", "text": "Le passage à Go pour nos microservices a divisé notre consommation de RAM par dix. Impressionnant !", "likes": 89, "reposts": 12, "createdAt": "2026-05-02T14:45:00Z"}, + {"cid": "cid:dev003", "text": "Est-ce qu'on peut s'accorder sur le fait que centrer une div en CSS n'est plus une excuse valable pour pleurer en 2026 ?", "likes": 512, "reposts": 88, "createdAt": "2026-05-20T18:12:45Z"}, + {"cid": "cid:dev004", "text": "Le cache est vide, la base de données est clean, mais ça ne marche toujours pas. Il est temps de prendre un café.", "likes": 134, "reposts": 45, "createdAt": "2026-05-10T10:05:32Z"}, + {"cid": "cid:dev005", "text": "Docker m'a simplifié la vie, jusqu'au jour où j'ai dû comprendre comment fonctionnaient les networks internes.", "likes": 76, "reposts": 5, "createdAt": "2026-05-17T16:30:21Z"}, + {"cid": "cid:dev006", "text": "Rien de plus satisfaisant que de voir tous les tests unitaires passer au vert du premier coup.", "likes": 210, "reposts": 15, "createdAt": "2026-05-05T08:15:00Z"}, + {"cid": "cid:dev007", "text": "Je refuse de participer au débat Vim vs Emacs. VS Code avec les bons plugins, c'est la paix dans le monde.", "likes": 340, "reposts": 67, "createdAt": "2026-05-14T20:50:18Z"}, + {"cid": "cid:dev008", "text": "Mon historique Git est une tragédie en trois actes : 'wip', 'wip 2', et 'ça marche enfin pitié'.", "likes": 890, "reposts": 230, "createdAt": "2026-05-22T11:42:09Z"}, + {"cid": "cid:dev009", "text": "Le déploiement en production un vendredi soir ? Même pas peur. (Si, complètement terrifié).", "likes": 445, "reposts": 112, "createdAt": "2026-05-13T23:10:55Z"}, + {"cid": "cid:dev010", "text": "Kubernetes est formidable pour gérer la charge, mais la courbe d'apprentissage ressemble à un mur d'escalade sans prises.", "likes": 156, "reposts": 28, "createdAt": "2026-05-03T15:25:33Z"}, + {"cid": "cid:dev011", "text": "Je lis la documentation de cette API depuis ce matin, et j'en déduis que l'auteur déteste l'humanité.", "likes": 321, "reposts": 90, "createdAt": "2026-05-28T09:55:12Z"}, + {"cid": "cid:dev012", "text": "Un client m'a demandé de 'juste rajouter un bouton'. Si seulement il savait ce que ça implique en backend.", "likes": 278, "reposts": 54, "createdAt": "2026-05-08T13:40:22Z"}, + {"cid": "cid:dev013", "text": "TypeScript m'a sauvé de tellement d'erreurs bêtes que je ne peux plus m'en passer aujourd'hui.", "likes": 198, "reposts": 33, "createdAt": "2026-05-30T10:18:47Z"}, + {"cid": "cid:dev014", "text": "Pourquoi mon code marche-t-il sur ma machine locale mais explose complètement en staging ?", "likes": 402, "reposts": 105, "createdAt": "2026-05-12T17:35:04Z"}, + {"cid": "cid:dev015", "text": "La refactorisation de code legacy, c'est comme de l'archéologie : on ne sait jamais sur quoi on va tomber.", "likes": 167, "reposts": 19, "createdAt": "2026-05-19T08:50:11Z"}, + {"cid": "cid:dev016", "text": "Aujourd'hui, j'ai supprimé 500 lignes de code mort. Meilleure sensation de la semaine.", "likes": 289, "reposts": 41, "createdAt": "2026-05-04T14:20:59Z"}, + {"cid": "cid:dev017", "text": "Le syndrome de l'imposteur frappe toujours au moment où on te demande d'estimer la durée d'une tâche complexe.", "likes": 534, "reposts": 145, "createdAt": "2026-05-01T11:11:11Z"}, + {"cid": "cid:dev018", "text": "Je suis développeur fullstack, ce qui signifie que je suis moyen partout et expert nulle part.", "likes": 612, "reposts": 178, "createdAt": "2026-05-25T19:05:34Z"}, + {"cid": "cid:dev019", "text": "Le mode sombre n'est pas une option d'interface, c'est un mode de vie pour préserver mes yeux de développeur.", "likes": 345, "reposts": 72, "createdAt": "2026-05-09T22:45:10Z"}, + {"cid": "cid:dev020", "text": "Quelqu'un a décidé de changer l'architecture au milieu du sprint. Je vais pleurer en silence.", "likes": 123, "reposts": 24, "createdAt": "2026-05-16T16:15:28Z"}, + {"cid": "cid:dev021", "text": "L'Open Source est magnifique, sauf quand une dépendance critique est maintenue par une seule personne qui a disparu.", "likes": 476, "reposts": 99, "createdAt": "2026-05-27T09:30:45Z"}, + {"cid": "cid:dev022", "text": "Je passe plus de temps à nommer mes variables qu'à écrire la logique de mes fonctions.", "likes": 234, "reposts": 56, "createdAt": "2026-05-14T12:55:00Z"}, + {"cid": "cid:dev023", "text": "Le machine learning, c'est beaucoup de statistiques, énormément de données, et un peu de magie noire.", "likes": 189, "reposts": 37, "createdAt": "2026-05-18T08:25:39Z"}, + {"cid": "cid:dev024", "text": "Quand tu lances ta compilation en Rust et que tu as le temps d'aller préparer un repas complet avant la fin.", "likes": 312, "reposts": 65, "createdAt": "2026-05-05T14:10:15Z"}, + {"cid": "cid:dev025", "text": "La sécurité informatique devrait être pensée dès le premier jour, pas ajoutée comme un pansement à la fin.", "likes": 145, "reposts": 21, "createdAt": "2026-05-30T17:40:50Z"}, + {"cid": "cid:lil026", "text": "Rien de tel qu'une pinte en terrasse sur la Grand'Place de Lille quand le soleil pointe enfin le bout de son nez.", "likes": 412, "reposts": 34, "createdAt": "2026-05-08T18:20:12Z"}, + {"cid": "cid:lil027", "text": "Le marché de Wazemmes le dimanche matin, c'est vraiment l'âme de la ville. Les couleurs, les odeurs, l'ambiance !", "likes": 289, "reposts": 45, "createdAt": "2026-05-12T10:15:45Z"}, + {"cid": "cid:lil028", "text": "Un vrai Welsh au Maroilles, avec une bonne portion de frites. C'est lourd, c'est gras, mais c'est tellement bon.", "likes": 567, "reposts": 112, "createdAt": "2026-05-05T13:30:00Z"}, + {"cid": "cid:lil029", "text": "Se perdre volontairement dans les petites rues pavées du Vieux-Lille reste mon activité préférée du week-end.", "likes": 345, "reposts": 56, "createdAt": "2026-05-22T15:45:33Z"}, + {"cid": "cid:lil030", "text": "Le parc de la Citadelle en automne offre des couleurs incroyables, parfait pour une balade digestive.", "likes": 198, "reposts": 23, "createdAt": "2026-05-15T14:20:10Z"}, + {"cid": "cid:lil031", "text": "Je suis passé par la gare Saint-Sauveur hier soir, l'exposition temporaire est vraiment dingue cette année.", "likes": 156, "reposts": 18, "createdAt": "2026-05-28T21:10:05Z"}, + {"cid": "cid:lil032", "text": "La Braderie me manque. L'odeur des moules, les montagnes de coquilles, chiner des vieux vinyles dès l'aube...", "likes": 890, "reposts": 245, "createdAt": "2026-05-03T07:45:00Z"}, + {"cid": "cid:lil033", "text": "Traverser Euralille aux heures de pointe m'a rappelé pourquoi j'évite les centres commerciaux en semaine.", "likes": 112, "reposts": 14, "createdAt": "2026-05-18T18:05:22Z"}, + {"cid": "cid:lil034", "text": "Le beffroi de l'Hôtel de Ville éclairé dans la brume nocturne, ça donne des photos dignes d'un film noir.", "likes": 423, "reposts": 67, "createdAt": "2026-05-11T23:50:40Z"}, + {"cid": "cid:lil035", "text": "Impossible de passer par la rue de Béthune sans croiser quelqu'un qu'on connaît, c'est la loi de Lille.", "likes": 234, "reposts": 31, "createdAt": "2026-05-19T16:25:15Z"}, + {"cid": "cid:lil036", "text": "Un merveilleux de chez Fred pour le goûter, c'est le genre de petit plaisir qu'on ne refuse jamais.", "likes": 678, "reposts": 145, "createdAt": "2026-05-25T15:40:55Z"}, + {"cid": "cid:lil037", "text": "Le métro lillois en plein été, sans climatisation... une expérience tropicale que je ne recommande pas.", "likes": 345, "reposts": 89, "createdAt": "2026-05-14T17:15:30Z"}, + {"cid": "cid:lil038", "text": "J'ai découvert un petit estaminet planqué vers la rue de la Monnaie, l'accueil était chaleureux comme on les aime ici.", "likes": 187, "reposts": 22, "createdAt": "2026-05-02T20:30:12Z"}, + {"cid": "cid:lil039", "text": "Le stade Pierre Mauroy bouillonnait ce week-end pour le match du LOSC. Une ambiance vraiment spectaculaire !", "likes": 512, "reposts": 98, "createdAt": "2026-05-12T22:15:48Z"}, + {"cid": "cid:lil040", "text": "Trouver une place pour se garer vers République le samedi après-midi relève de la mission impossible.", "likes": 145, "reposts": 17, "createdAt": "2026-05-04T14:55:20Z"}, + {"cid": "cid:lil041", "text": "La vue depuis la terrasse panoramique d'Euratechnologies au coucher du soleil est très sous-estimée.", "likes": 267, "reposts": 39, "createdAt": "2026-05-20T21:45:00Z"}, + {"cid": "cid:lil042", "text": "Lire un livre tranquillement assis sur les marches de l'Opéra pendant que la ville s'agite autour, un vrai bonheur.", "likes": 198, "reposts": 24, "createdAt": "2026-05-10T11:20:35Z"}, + {"cid": "cid:lil043", "text": "La pluie lilloise a son propre caractère : elle ne mouille pas beaucoup mais elle dure trois jours d'affilée.", "likes": 456, "reposts": 77, "createdAt": "2026-05-05T09:10:18Z"}, + {"cid": "cid:lil044", "text": "Le Furet du Nord sur la Grand'Place, je peux y entrer pour cinq minutes et y rester bloqué deux heures sans m'en rendre compte.", "likes": 389, "reposts": 62, "createdAt": "2026-05-28T15:35:42Z"}, + {"cid": "cid:lil045", "text": "Faire du vélo sur les pavés du Vieux-Lille demande une suspension robuste ou des poignets en acier trempé.", "likes": 210, "reposts": 29, "createdAt": "2026-05-05T08:50:11Z"}, + {"cid": "cid:lil046", "text": "La friterie Meunier reste mon incontournable quand je veux manger sur le pouce près de la gare.", "likes": 167, "reposts": 15, "createdAt": "2026-05-16T12:45:30Z"}, + {"cid": "cid:lil047", "text": "La piscine musée de Roubaix, juste à côté, est un chef-d'œuvre absolu. J'y retournerais tous les mois si je pouvais.", "likes": 478, "reposts": 105, "createdAt": "2026-05-25T10:30:25Z"}, + {"cid": "cid:lil048", "text": "Les soirées étudiantes rue Masséna me semblent bien loin maintenant, mais le bruit porte toujours jusqu'à chez moi !", "likes": 123, "reposts": 11, "createdAt": "2026-05-14T23:55:00Z"}, + {"cid": "cid:lil049", "text": "J'ai testé la nouvelle micro-brasserie près de Cormontaigne, leur IPA est une petite merveille d'amertume.", "likes": 189, "reposts": 26, "createdAt": "2026-05-02T19:15:40Z"}, + {"cid": "cid:lil050", "text": "Rien de plus lillois que de terminer une soirée en mangeant une frite-fricadelle à 3h du matin.", "likes": 567, "reposts": 134, "createdAt": "2026-05-15T03:10:05Z"}, + {"cid": "cid:cyc051", "text": "Le vent de face sur 20 kilomètres ce matin m'a fait regretter de ne pas avoir pris l'option vélo électrique.", "likes": 345, "reposts": 45, "createdAt": "2026-05-20T08:40:12Z"}, + {"cid": "cid:cyc052", "text": "Sortie gravel incroyable ce week-end dans la forêt. J'ai de la boue jusqu'aux oreilles mais je suis heureux.", "likes": 210, "reposts": 28, "createdAt": "2026-05-15T16:25:50Z"}, + {"cid": "cid:cyc053", "text": "Crever un pneu sous la pluie à 5 km de la maison, c'est exactement pour ça que j'ai toujours trois chambres à air sur moi.", "likes": 178, "reposts": 19, "createdAt": "2026-05-12T17:10:08Z"}, + {"cid": "cid:cyc054", "text": "Le vélotaf, c'est l'assurance d'arriver au bureau réveillé, de bonne humeur, et avec un bon petit cardio fait pour la journée.", "likes": 678, "reposts": 156, "createdAt": "2026-05-05T07:50:33Z"}, + {"cid": "cid:cyc055", "text": "J'ai enfin réussi à battre mon record personnel sur ce segment Strava en côte. Mes poumons sont en feu.", "likes": 289, "reposts": 37, "createdAt": "2026-05-28T14:15:20Z"}, + {"cid": "cid:cyc056", "text": "Les pistes cyclables qui s'arrêtent brusquement au milieu de nulle part sont la pire aberration urbaine.", "likes": 890, "reposts": 245, "createdAt": "2026-05-18T09:05:45Z"}, + {"cid": "cid:cyc057", "text": "Entendre le doux ronronnement d'une chaîne parfaitement nettoyée et lubrifiée est ASMR pour tout cycliste qui se respecte.", "likes": 412, "reposts": 67, "createdAt": "2026-05-09T18:30:15Z"}, + {"cid": "cid:cyc058", "text": "Les automobilistes qui te frôlent à 80 km/h sur une route de campagne... On partage la route, s'il vous plaît !", "likes": 756, "reposts": 198, "createdAt": "2026-05-02T10:45:00Z"}, + {"cid": "cid:cyc059", "text": "Rien de mieux que le premier coup de pédale un dimanche matin très tôt, quand la ville dort encore.", "likes": 345, "reposts": 52, "createdAt": "2026-05-10T06:15:30Z"}, + {"cid": "cid:cyc060", "text": "Investir dans un bon cuissard est la meilleure décision que j'ai pu prendre. Mon séant me remercie à chaque longue sortie.", "likes": 234, "reposts": 31, "createdAt": "2026-05-20T19:55:10Z"}, + {"cid": "cid:cyc061", "text": "Le bikepacking m'a appris le minimalisme. Chaque gramme compte quand on doit monter un col alpin.", "likes": 198, "reposts": 24, "createdAt": "2026-05-05T13:20:45Z"}, + {"cid": "cid:cyc062", "text": "Je disais que le vélo aérodynamique ne changerait rien, jusqu'à ce que je l'essaye sur le plat. C'est une fusée.", "likes": 156, "reposts": 17, "createdAt": "2026-05-30T15:10:25Z"}, + {"cid": "cid:cyc063", "text": "La règle non écrite du cyclisme : on se salue toujours quand on se croise, peu importe le vélo.", "likes": 567, "reposts": 89, "createdAt": "2026-05-14T09:35:50Z"}, + {"cid": "cid:cyc064", "text": "Faire l'ascension du Mont Ventoux était mon objectif de l'année. La vue au sommet efface toute la douleur.", "likes": 423, "reposts": 76, "createdAt": "2026-05-15T12:00:00Z"}, + {"cid": "cid:cyc065", "text": "Rouler de nuit avec un bon éclairage apporte une ambiance totalement différente, presque méditative.", "likes": 187, "reposts": 22, "createdAt": "2026-05-10T21:40:15Z"}, + {"cid": "cid:cyc066", "text": "Le nettoyage du vélo après une sortie hivernale sur des routes pleines de sel prend plus de temps que la sortie elle-même.", "likes": 210, "reposts": 34, "createdAt": "2026-05-08T16:05:30Z"}, + {"cid": "cid:cyc067", "text": "Un rayon cassé en plein milieu d'une descente, c'est la grosse frayeur garantie. Heureusement, plus de peur que de mal.", "likes": 134, "reposts": 12, "createdAt": "2026-05-22T14:50:45Z"}, + {"cid": "cid:cyc068", "text": "Voir des enfants apprendre à faire du vélo sans roulettes pour la première fois me donne toujours le sourire.", "likes": 456, "reposts": 65, "createdAt": "2026-05-03T11:25:20Z"}, + {"cid": "cid:cyc069", "text": "Le choix des pneus est un débat sans fin. Tubeless ou chambre à air ? La guerre est éternelle sur les forums.", "likes": 278, "reposts": 41, "createdAt": "2026-05-25T18:15:10Z"}, + {"cid": "cid:cyc070", "text": "S'arrêter à la boulangerie à mi-parcours pour manger un flan, c'est la base de l'entraînement d'endurance.", "likes": 612, "reposts": 105, "createdAt": "2026-05-11T10:30:55Z"}, + {"cid": "cid:cyc071", "text": "La sensation de glisse sur un bel asphalte tout neuf, sans aucune imperfection, c'est la perfection absolue.", "likes": 389, "reposts": 54, "createdAt": "2026-05-27T15:45:00Z"}, + {"cid": "cid:cyc072", "text": "Les sas vélos aux feux rouges sont utiles uniquement si les voitures ne viennent pas s'y garer systématiquement.", "likes": 534, "reposts": 123, "createdAt": "2026-05-14T08:20:35Z"}, + {"cid": "cid:cyc073", "text": "Partir pour 30km et finir par en faire 80 parce qu'on s'est perdu et que le paysage était trop beau.", "likes": 467, "reposts": 88, "createdAt": "2026-05-02T16:10:45Z"}, + {"cid": "cid:cyc074", "text": "Les passages à niveau par temps humide : la hantise de tout cycliste qui tient à sa clavicule.", "likes": 321, "reposts": 47, "createdAt": "2026-05-18T09:55:20Z"}, + {"cid": "cid:cyc075", "text": "Réparer soi-même son dérailleur avec un tutoriel sur internet, c'est 20% de technique et 80% de patience.", "likes": 245, "reposts": 33, "createdAt": "2026-05-09T20:05:15Z"}, + {"cid": "cid:cin076", "text": "Le dernier film de Denis Villeneuve prouve encore une fois qu'il est l'un des plus grands réalisateurs de notre époque.", "likes": 789, "reposts": 156, "createdAt": "2026-05-04T22:30:10Z"}, + {"cid": "cid:cin077", "text": "Regarder un film en VF quand il a été pensé pour sa langue d'origine, c'est perdre une grande partie du jeu d'acteur.", "likes": 456, "reposts": 89, "createdAt": "2026-05-21T19:45:55Z"}, + {"cid": "cid:cin078", "text": "Le cinéma d'horreur indépendant propose des œuvres tellement plus intelligentes que les gros blockbusters remplis de jumpscares.", "likes": 345, "reposts": 67, "createdAt": "2026-05-30T23:15:40Z"}, + {"cid": "cid:cin079", "text": "Entendre les gens manger leurs pop-corn la bouche ouverte pendant une scène dramatique silencieuse... L'enfer sur terre.", "likes": 890, "reposts": 234, "createdAt": "2026-05-08T20:20:15Z"}, + {"cid": "cid:cin080", "text": "La photographie de Roger Deakins me laisse sans voix à chaque fois. Chaque plan de ses films est un tableau.", "likes": 512, "reposts": 98, "createdAt": "2026-05-11T14:50:30Z"}, + {"cid": "cid:cin081", "text": "Un bon scénario peut sauver une mauvaise réalisation, mais l'inverse est très rarement vrai.", "likes": 423, "reposts": 76, "createdAt": "2026-05-26T17:35:45Z"}, + {"cid": "cid:cin082", "text": "Je me suis refait l'intégrale du Seigneur des Anneaux en version longue ce week-end. Plus de 11 heures de pur bonheur.", "likes": 956, "reposts": 312, "createdAt": "2026-05-25T11:10:00Z"}, + {"cid": "cid:cin083", "text": "Les effets spéciaux pratiques (maquillage, animatroniques) vieilliront toujours mieux que des CGI bâclés.", "likes": 678, "reposts": 145, "createdAt": "2026-05-16T15:25:20Z"}, + {"cid": "cid:cin084", "text": "Le cinéma coréen ne cesse de me surprendre par sa capacité à mélanger les genres au sein d'un même film.", "likes": 389, "reposts": 65, "createdAt": "2026-05-09T21:05:35Z"}, + {"cid": "cid:cin085", "text": "La musique de Hans Zimmer, c'est littéralement 50% de l'impact émotionnel des films d'Interstellar à Inception.", "likes": 734, "reposts": 167, "createdAt": "2026-05-19T18:40:50Z"}, + {"cid": "cid:cin086", "text": "Aller seul au cinéma un mardi après-midi, c'est un luxe incroyable qu'il faut savourer au moins une fois.", "likes": 412, "reposts": 88, "createdAt": "2026-05-14T14:15:10Z"}, + {"cid": "cid:cin087", "text": "Les fins ouvertes qui te laissent réfléchir pendant des jours après le générique sont de loin mes préférées.", "likes": 289, "reposts": 45, "createdAt": "2026-05-22T09:30:25Z"}, + {"cid": "cid:cin088", "text": "Les remakes en prises de vues réelles des vieux classiques d'animation perdent presque toujours leur âme originelle.", "likes": 567, "reposts": 123, "createdAt": "2026-05-05T16:55:40Z"}, + {"cid": "cid:cin089", "text": "Le générique de fin devrait être obligatoire à regarder, par respect pour les milliers de personnes qui ont fait le film.", "likes": 234, "reposts": 41, "createdAt": "2026-05-28T22:10:15Z"}, + {"cid": "cid:cin090", "text": "Tarantino reste le maître absolu des dialogues qui ne servent à rien pour l'intrigue mais qui définissent parfaitement un personnage.", "likes": 645, "reposts": 112, "createdAt": "2026-05-18T20:45:30Z"}, + {"cid": "cid:cin091", "text": "Je préfère voir un réalisateur prendre des risques et rater son film que regarder une production aseptisée sans saveur.", "likes": 378, "reposts": 72, "createdAt": "2026-05-31T12:20:55Z"}, + {"cid": "cid:cin092", "text": "Les bandes-annonces qui dévoilent littéralement tous les rebondissements de l'intrigue devraient être illégales.", "likes": 823, "reposts": 210, "createdAt": "2026-05-08T10:05:40Z"}, + {"cid": "cid:cin093", "text": "Découvrir une pépite méconnue dans les tréfonds d'une plateforme de streaming est un sentiment merveilleux.", "likes": 198, "reposts": 28, "createdAt": "2026-05-24T23:35:10Z"}, + {"cid": "cid:cin094", "text": "La salle de l'UGC Ciné Cité était pleine à craquer hier soir, ça fait plaisir de voir que les gens vont toujours au cinéma.", "likes": 267, "reposts": 35, "createdAt": "2026-05-18T08:50:25Z"}, + {"cid": "cid:cin095", "text": "Certains acteurs ont un tel charisme qu'ils arrivent à te garder scotché à l'écran même s'ils ne font que boire un café.", "likes": 312, "reposts": 51, "createdAt": "2026-05-14T19:15:30Z"}, + {"cid": "cid:cin096", "text": "Le rythme d'un film est primordial. Un film de 3 heures peut passer en un éclair, et un film d'une heure trente peut sembler interminable.", "likes": 489, "reposts": 84, "createdAt": "2026-05-29T15:40:45Z"}, + {"cid": "cid:cin097", "text": "Je collectionne les affiches de films depuis dix ans, mes murs n'ont plus un seul centimètre carré de libre.", "likes": 145, "reposts": 17, "createdAt": "2026-05-07T11:10:20Z"}, + {"cid": "cid:cin098", "text": "L'ambiance lors des avant-premières avec la présence de l'équipe du film apporte vraiment un plus à l'expérience.", "likes": 223, "reposts": 29, "createdAt": "2026-05-04T20:25:55Z"}, + {"cid": "cid:cin099", "text": "Revoir un film d'enfance avec des yeux d'adulte et découvrir un sous-texte qu'on n'avait jamais compris, c'est fascinant.", "likes": 534, "reposts": 92, "createdAt": "2026-05-30T16:50:10Z"}, + {"cid": "cid:cin100", "text": "Le son Dolby Atmos dans une bonne salle obscure, c'est quand même la garantie d'une immersion totale.", "likes": 367, "reposts": 61, "createdAt": "2026-05-12T22:05:35Z"} ]