{
  "author": "Angon Studio",
  "category": "Network",
  "dimension": "",
  "extensionNamespace": "",
  "fullName": "Angon Cloud",
  "gdevelopVersion": ">=5.6.282",
  "helpPath": "",
  "iconUrl": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAABelBMVEVMaXESDwwBAACzrKEAAAAEBAMAAAAJCAUWEhADAgImIRkQDgwWEhAAAADf18o0IxQMCgcAAAABAQAJCAbonkMBAABiQhsIBgS6s6h/Vinlm0EFAwIBAQCjnJPblkEDAgKYk4oAAAADAwICAgABAQAAAACqczLRjjzKiDoCAQCxeDRuSh+3fDV0TiAAAADEhTjWkT6QYiq9gDnMxbgAAABVORf98uMAAAD57+H78eL/s0v8q0j7qUf/sEr47t//tkz//O3/rUj/+ur//+////H///P+9uf5qEcTDQUKCAbz6twgFSP2p0Y6JxAaGRgrKSbt5NYWDiEgFQk+OzgrHQxrZ2FXOhjn39EUExJ9eHAzMS7tokRQTUjAua4jIR8ODQ3ypUU9KSguHiVjX1lbV1KaaSxKMhV0cGmrpJrY0MTSyr2Ef3dWU02KhX1LMirHwLThmUGhbi5IRkGRjIP69eb/vU+HXCb///1vSjCYZzdfQC3+vWKBfHT3yI5oF8IJAAAAcHRSTlMAJJT++FLuAgH+DysZ4v4INNmOPv60/kn//vxbm/7+c/7QZaSuv/7+/oX+/v79yP7+/v79fvv////////////////////////////////////////////////////////+///////////////////+xTIE9AAAAAlwSFlzAAALEwAACxMBAJqcGAAAEgtJREFUeJztWvdX28rWHcBYEmDTwYTee8/1kUa9WO69YhsbML0k1LSb73//1sgQMJDyXsi9663F/sELLHlma+bMPnuOBqFXvOIVr3jFK17xilf8DmiKotC/CMr6oP/F/uvGhjv/vSGg0JgNGJhoQv/OGFCojUktTC/CwD8+CzRFURRNoQFm5eZvOWNvQTQiX/1jvSMLrag7Ffq/LF5l2qjW6lf/AAeKdNHa1t8xsdS+5GCm//5b3geHo310sn6srZXQ++PdNzUOOBjwJ8L7zJIjNTfTA0tMOBNO+YBpr++k/iQFmqJRXYcD/Pt9a+chzCcmRoEBgHrbxY3Iv1n+EGZgdKz1j00ERaG6ARuE995gGYs8v5XobvelUikYt+3JIV7FsjjUl4D2fqoqUC/eP2p6a2MyM6IsCl5B8AqEQAaLM4QADnm9gpcX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",
  "name": "AngonCloud",
  "previewIconUrl": "https://angon-db-manager.lovable.app/angon-cloud-logo.png",
  "shortDescription": "Comptes joueurs en ligne, sauvegardes, trophées et classements via l'API Angon Cloud.",
  "version": "0.1.0",
  "description": [
    "Connecte ton jeu GDevelop à Angon Cloud via l'API publique. L'extension gère la clé API du jeu et le jeton de session du joueur, et expose les appels réseau sous forme d'actions, de conditions et d'expressions.",
    "",
    "## Fonctionnalités",
    "",
    "### Configuration",
    "- Renseigne la clé API du jeu dans la variable ApiKey de l'extension. Tu peux changer l'URL du service avec BaseURL.",
    "- Le jeton de session du joueur est conservé automatiquement entre les scènes et les lancements du jeu.",
    "- Lis l'état des requêtes, le code HTTP et des messages d'erreur clairs.",
    "",
    "### Comptes joueurs",
    "- Crée un compte joueur avec e-mail, mot de passe, pseudo et pays détecté automatiquement.",
    "- Connecte un joueur et récupère le jeton de session.",
    "- Déconnecte le joueur et efface le jeton local.",
    "- Vérifie si un joueur est connecté.",
    "",
    "### Profil et sauvegardes",
    "- Charge le profil du joueur connecté : pseudo, niveau, score, pays et données JSON libres.",
    "- Lis les valeurs du profil via des expressions.",
    "",
    "### Panneau intégré au jeu",
    "- Ouvre le panneau de connexion ou d'inscription habillé selon la configuration faite dans Angon Cloud.",
    "- Garde le joueur connecté grâce à l'option « Se souvenir de moi »."
  ],
  "tags": [
    "angon",
    "cloud",
    "api",
    "sauvegarde",
    "classement",
    "trophees",
    "joueur",
    "backend",
    "multijoueur",
    "reseau"
  ],
  "authorIds": [
    "QjE1CI3drHNR9Wklz2gTC5WbEgh1",
    "4I8JL2R40SSD8gkEiZBbHik8TnO2"
  ],
  "dependencies": [],
  "globalVariables": [
    {
      "name": "BaseURL",
      "persistentUuid": "aefc4b9f-2196-49fb-baf3-38c37c2329d0",
      "type": "string",
      "value": "https://angonstudio.cloud/api/public/v1"
    },
    {
      "name": "ApiKey",
      "persistentUuid": "94676354-ea20-4344-a46a-df2e48096e7a",
      "type": "string",
      "value": "sk_live_76fb9da48d31785d6faf4f80e714a1f8f9c568709a27d4aa"
    },
    {
      "name": "GameID",
      "persistentUuid": "cfbef2e2-46ee-426e-9a68-d5202e189c75",
      "type": "string",
      "value": "7edc95a6-d10a-429e-b85a-457c332d0d42"
    },
    {
      "name": "GameName",
      "persistentUuid": "99b6c6c0-86f8-4ec4-aebd-d8d65aecb548",
      "type": "string",
      "value": "Game Test"
    }
  ],
  "sceneVariables": [],
  "eventsFunctions": [
    {
      "description": "Initialise Angon Cloud au demarrage du jeu.",
      "fullName": "",
      "functionType": "Action",
      "group": "Configuration",
      "name": "onFirstSceneLoaded",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "if (!gdjs.__angon) {",
            "  var S = {",
            "    baseUrl: \"https://angon-db-manager.lovable.app/api/public/v1\",",
            "    apiKey: \"\",",
            "    token: \"\",",
            "    loggedIn: false,",
            "    justLoggedIn: false,",
            "    accountId: \"\",",
            "    profileId: \"\",",
            "    pseudo: \"\",",
            "    email: \"\",",
            "    pays: \"\",",
            "    avatarUrl: \"\",",
            "    statut: \"hors_ligne\",",
            "    niveau: 1,",
            "    score: 0,",
            "    donnees: \"{}\",",
            "    pending: false,",
            "    statusCode: 0,",
            "    errorCode: \"\",",
            "    errorMessage: \"\",",
            "    lastResponse: \"\",",
            "    panelOpen: false,",
            "    panelMode: \"login\",",
            "    panelRoot: null,",
            "    panelEls: null,",
            "    panelOnClose: null,",
            "    panelKeyHandler: null,",
            "    configLoaded: false,",
            "    remember: true,",
            "    restoring: false,",
            "    restoreDone: false,",
            "    panelClosing: false,",
            "    panelModeTimer: null,",
            "    lastTableData: [],",
            "    lastTableRow: null,",
            "    lastTableCount: 0,",
            "    lastCreatedId: \"\",",
            "    lastTableResponse: \"\",",
            "    tableSuccess: false,",
            "    lastFileUrl: \"\",",
            "    lastFileId: \"\",",
            "    lastFileName: \"\",",
            "    lastFileSize: 0,",
            "    lastUploadSuccess: false,",
            "    playerFilesJson: \"[]\",",
            "    syncing: false,",
            "    syncCount: 0,",
            "    shopArticles: [],",
            "    leaderboardEntries: [],",
            "    lastLeaderboardRank: 0,",
            "    lastLeaderboardScore: 0,",
            "    lastLeaderboardId: \"\",",
            "    lastLeaderboardCode: \"\",",
            "    leaderboardSuccess: false,",
            "    oauthPollInterval: null,",
            "    oauthCode: \"\",",
            "    /* Matchmaking : idle | waiting | matched */",
            "    mmStatus: \"idle\",",
            "    mmPosition: 0,",
            "    mmPartieId: \"\",",
            "    mmCodeSalon: \"\",",
            "    mmEquipe: 0,",
            "    mmParticipantsJson: \"[]\",",
            "    /* Canal temps-reel du salon (Realtime Broadcast) */",
            "    rtUrl: \"\",",
            "    rtKey: \"\",",
            "    rtSocket: null,",
            "    rtChannel: \"\",",
            "    rtJoined: false,",
            "    rtHbTimer: null,",
            "    rtRetryTimer: null,",
            "    rtLastMsg: \"\",",
            "    rtMsgCount: 0,",
            "    rtMsgRead: 0,",
            "    /* Etat autoritaire de la partie (relais serveur /match) */",
            "    partieJson: \"{}\",",
            "    rtLastEvent: \"\",",
            "    /* Panneau de matchmaking integre */",
            "    mmPanelOpen: false,",
            "    mmPanelRoot: null,",
            "    mmPanelTimer: null,",
            "    mmPanelKeyHandler: null,",
            "    mmPanelMode: \"1v1\"",
            "  };",
            "",
            "  var A = {",
            "    state: S,",
            "    TOKEN_KEY: \"angon.playerToken\",",
            "    REMEMBER_KEY: \"angon.remember\",",
            "    CACHE_PREFIX: \"angon_tbl_\",",
            "    SYNC_QUEUE_KEY: \"angon_sync_queue\",",
            "    POLICIES_KEY: \"angon_tbl_policies\"",
            "  };",
            "  A.game = runtimeScene.getGame();",
            "",
            "  /* --- lecture de la configuration dans les variables de l extension --- */",
            "  A.varContainers = function () {",
            "    var out = [];",
            "    try {",
            "      var g = A.game;",
            "      /* GDevelop 5.4+ : variables declarees dans les parametres de l extension. */",
            "      if (g && g.getVariablesForExtension) {",
            "        var ev = g.getVariablesForExtension(\"AngonCloud\");",
            "        if (ev) { out.push(ev); }",
            "      }",
            "      if (g && g.getVariables) { out.push(g.getVariables()); }",
            "      if (runtimeScene && runtimeScene.getVariables) { out.push(runtimeScene.getVariables()); }",
            "    } catch (e) {}",
            "    return out;",
            "  };",
            "  A.readVar = function (name) {",
            "    var cs = A.varContainers();",
            "    for (var i = 0; i < cs.length; i++) {",
            "      try {",
            "        var c = cs[i];",
            "        if (c && c.has && c.has(name)) {",
            "          var v = c.get(name).getAsString();",
            "          if (typeof v === \"string\" && v.trim() !== \"\") { return v.trim(); }",
            "        }",
            "      } catch (e) {}",
            "    }",
            "    return undefined;",
            "  };",
            "  A.syncConfig = function () {",
            "    var base = A.readVar(\"BaseURL\");",
            "    if (typeof base === \"string\" && base) { S.baseUrl = base.replace(/\\/+$/, \"\"); }",
            "    var key = A.readVar(\"ApiKey\");",
            "    if (typeof key === \"string\" && key && key !== S.apiKey) {",
            "      S.apiKey = key;",
            "      /* Nouvelle cle : on relance la recuperation de l habillage. */",
            "      A.panelConfigPromise = null;",
            "      S.configLoaded = false;",
            "    }",
            "  };",
            "",
            "",
            "  /* Images Angon servies par le site (motif Vectorized 1 + logo rond). */",
            "  A.ASSETS = {",
            "    pattern: \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAyAAAAKbCAMAAAA61B5YAAAALVBMVEVMaXH///////////////////////////////////////////////////////9xAJ22AAAADnRSTlMA8uFV0G0KF709KKuDmGpDcm4AAAAJcEhZcwAACxMAAAsTAQCanBgAACAASURBVHja5F3ZYus4rjQ3cff/f+6Vc7LYEomNkJOeq7eZzrFliYW1ULhVT77K7XSFdLhq7e70V65+XGn16tvpo7cYP/7T5Jaf7uvf1T+u1lo4f1b7/qvxNfj9yNW+vv7zTh43MP3j7E/P8uNqxz/srz9v/5Pv31La/uueHvXwgRxuwR2fFfHRfzz+45+N7hT84IA/xfDzCuafg3zTz0dsyLeV+vyh9Wbv5KsN7v38Vz6e3vxd6/IhDg6WlXzUGcYN+ReBjY9bP39Kmv7xZoh3evo7+/QfY8nNs95jJz0vG0YnqwB3sl8e/1yzoU9R7/xMTvHzS2jHH8Q4XZZy7/Z0hIvR/H01n24iJg18OORFOj4+RvYjsP768YPxZ37yStG1NH3q/nAkqSfAnp/86aFZBMqkN0GxM/ILRGT29wWA3AdP6PjPzSkOifaue6VMPFqst4LA2GQBPka2rzAPgt1wTze+txzq6NGngwWL9Kd2jrOOd+xf/zPJ4hGeY9U8P3NEbn3gFOyad+rYt2/+rn6ds4HCciKD047cpi0SfAwOn4dzlvOtnr/YEzz7jyvp/hApMT0n7EQ8GEBupJcR8ee4JU0DO/sWZ++LAPGojWzXgv8HIpHy6xinvV+Bj4EJ7fDfH3/FoDASmVH1bcsutD15r72FfGOnXof7j+C9NIFzouR2qmZ2jMhYx0feLn60gcHZ79dcph3d/RaIuU7abswExMabFkCwiDu2n19hqiNlKvm2cjGNs3XQY3MC55RIvlgxUB8i0pm7AkAcbHrPByncL7vOEKEl6yN7G0FseTE+bkTr9Qr06q21qbuNdqDtEj5u7BJKKnPzVyTWMQrTOUWAFD8NmlgA6bDtzfRfZZvLcb8mmSQNIoHvr0alGMSBL+Bj46UglKtwo7YLDt6XaToXpkX1MSetCArD8w2r7b68MFZw58Ez0OiG+SWH2FHSZShJhdkTGbeJ6kX4OJ/mtgyQphxhhbPZya4bUpwV4PMRVToTutmsGbQJoPT1ByDG7zlcaD1ZpjdMU/TMDfPgkZS9dK/gRLZmOC4HT1PtAj7OQXheBohXjrDq8Ba34PEqyTkvqIizI8clQ3e8nIbYnqnJ+TNAbA+5PFnWGCqnE9KmBcnK7bltju9JTnWe2Ml/SvDdS/g4hXz2ggir6aYg36e1wG5kdzQeOepZFyBkwDHQMU3OfwAy/GdxZofDPIgN1IPnwZppCcksZiKTpsiETgS+RlOWTp9VTRfG3q7oAq48nwK7lP463RBrJQ3xbfyY0LrOtI1ZLBnsZhxgZVYa8OpI6lomchsYNz+JbsAOusm6p8/d/lqEFcCP25xfaFK7lbKrRkn6m8E3MyJwympnhUQAIWkWxZ5M7SxipJmLGDhvZhCzHd6sCaL6etY9ffftph5h1ZtqCnJmfnEOZREZfLoPjEaFvEdJPlPAbiuOjqiZHYNATEDo1qJw3MggfMo//970KOrQrhr8JGmJMSOscFMNAtvtJucoGFGPPq1UPfjRBQXANkRp8zKOg/hE/HLWkSudbjBGxdjPf29blJUOF8/eucwZbuoRlnYK4m7cOs+8C9DFFFi1Wm8TBGy2Ue9oFGXl4Tk4BViTyJ57Qhgx8LADGEMHf20TP9xt792kveNt9sv6OhyWCLqHeRhhLXYeHe0WiU6kSw4zL0bkUU48l0Vv8MjqxTcY0hG3px85iVwkFUm6Gwnrx4NWcdoGI0mjJMert9G7dpG3E7OkrQl8QVLOQPidf9gVbAcEp5a35RPUh67KkSgfwhA8du1yIeVZ12n5YFKGPnEg8TZ6rrsH2llXIUvZ86tOyZIxnD17FMlf8dI4QRZaVd8nlOvnoDgfHOMYZHTIcydZZnnPjZqv87x1NkzKL1LT6dizK3MDZiolLWzaEVbhPEGULV1vfCqWZR9LeiXLxNv1VxK8kXECstRTIA5EVcbjBhsgfvhBSEaUWdb5VCX0mBEbVM6DcgrSOPPaWIKvR1SUtQvbG/BxwqsXD7csvktaPT6REVIsl6CIBhkJDuAaoYpeodaUEzLFGSmIW3DlR2ewKZd4ue3CtziQk1u30hF7OB4sOZfHFbdFiHQNfAwHCBtzsCGBEdb0+wGMJO0uIXKPTIgEAXWqXMjQr2/Bx9GFWGFpyEOmPTydFuP3pHXy3HLV8qsgPkbBIGkYy0FvMZGrlROMRFrRfTc1N/GwFMEBzxypFZzjduUQZH4PQA4hn5HlUBDpbxTZz2gwhFzELVfTHRNQw/ftIQMbPTq24DZ+ip6rN/+q+U5mimk5/7gr4vgA8ULqTV7vgVyWpxsZugO/fGgm9GS7HnrC+BjcazPcsqIDbyuRRnteMYKn6O3VwuDPoQkt+mj8PwloIVltiOUCHsTLT9k1L+Z8jJdyjxEFWJWhRPSCkcL9J5TErzDxkT2/bOKhR0AVEjHPfrRhKfqpupPcxjRm5JrSaR5iECIE5hEuraZKwTWp1Gs2LXTkT9b/lPD68kuNxDZPi93FC1iI6JMPK/hoC7o4DggCsqwZbL5OTMSM9ujQmAblI+eRcoZJP0Ak8KsaaUL4qlmj1Nu18PF0RL3D/8ZIKlh5oaI9qrgGtkQqGR8d4yPQqlgVioi5Gk8PTzDKWhyFGgikI2WtqPQMkcYXDH0NhV8mM7xbH78tFwBkpLt3MnmC7Gli0YmSunyZN9AQMvFRrESyr0DPQDAXZ7zB/HIBBGQiNRxmhiVfk+tjEHZGMeU4+IryzcMVDZbRcTmaszFEKhEgnnGvmUoZsJFb6GtCfskZH86IBk4rcJg3c4lMeeUPDTXKtA+GkZxzlBRjO3Lc5zM8JBfiVOCxb6Q4n+nRbOpTfMuG9aSqFJbEVIuRxZ/wga9rspyR5EC0tCZfnyvWlRtFLf7ayqgnV3gbURmQfn5UUvRHOWioi3UWvv35CYZdW3DrivbcRL3LnmplxIC+1zmOE/C+ihI+Dj+SIJkXUFn3rgoQSxXDaKwZH5Ij1vglH69/LBxn3dQcWG51ulKY+PykG3Sxjcb/R+4UmOh/hCzb1Etk6IbUdMoL/biMq+bt0tYBTMVyRDfdhMNuChHWvwrrTFkxzUYEEzNDHwZYYCPZ1n8KdjVZA8gEdfbjQdrXlcruNv+S3jiNsDzwEJwWPqpoVvspnR6YGFVyRiEa+CBknxZmzMFNP8yncfZEtamA+i5PrWBBlaFXI7ftC8UKP8ItfImX05voMDzO9sDPD2uTl3iJlTq6X/qOtJo6N5jcSH+uvzlBmR99lEmNYAVo8x6dSEN8V6ASYgB89KIxLWskCnobKf0wPyXTPLGKZ8uMOBCTt21fLMjTy/PSLVFfGoOO2z/SG9t4TkDw/XxTwla7cBKk/TwTT5YsbGCdY5wUZE6hlbFGlj1N3lil5BmGn0uPZRLXNa4DCT+ikpUMkrAmOZjqhb0D7Ik73mKcyo+xitq9W9DnH8pZJQBEmUB0dVNLztiSidCxHHuF16EZObnHA4D90PwWKA3LaEUyhySgqvoL6mI3dYGJAbb7ihqHlVEpWNiGAQLW2Sh3W+imnz6RuwXLLHphlC9DUQQ8PQs3QuXAJDa4zndeGuFwR1IWIqy38F/nncvKry1kJgS7nu+zmPEJKzFPZ4RGVP9RUOq547bDD9hs1HH3PjjmDfIQkV5RgKOtsKg3eDF9CQLIc0pB1V/wzDjb6SVPKECoOgieVOJ1a+69UBbtNG7P/oCPTjcU35Xg73RtkHM2GHoVUN+a5e31mkWp2xsA8uKs+9rm5ri0041m6nGAINsKoJPf6JNg+JRTJGawlduTfMXH+M9noWb8pyD3k7tXMMWw7KGhoSc5VqSV2F3KI3gVjZby6uZmc0GRN9+5ACFlz5Z06JNwwH47bvumtzPQiZMDPhJ3C9s+A75BdqLBB4JwKh8b6mr65vOSNp1bb81a6/EagDhZaSGxOiFuvT/IAggh/qE5kFuVpDqZs1HniA+UMkzBB1ltazAu9uxA+uLL3LumQ0WGPn5hI4VU7Rx938H+4DzsIsX+ONqGFiTC8q15daLi6bzQAHJPkT+IPhgjZKfonM7ACMmBF92O8dFX1DYaHAnZS2izP48+Mh4gk2gS96WSFvCzFXmSjJWDlaV/sOAJzxaOCBCkCBuJqJ8/kzZ8/mZtoydXAiX6NcPa4DDTXVRZjfDT3DJx8SMntS2jds1rz7siJ7et92eSdrU63aUAgb82UB2do3UiIj3nACZsgtHAh1ti5zXYpKqMLTjcF2SCH7F01zFbbV9BgLzihyyxC6TAVTkFaUt70oFIPJEBVTEO3b53o0rW/Z4UPCiMeocqpjLa+6On8OxARhFWv4TTYYZFQKuSo8N5jQNe9SECo5Z4oei+6ooJ5aHZ8EtLnKbltolthBSpjLXWCLc0RiZ394zhYf7BEdxuiAMJVw2GJuJRR9xIIKwzwoK157yzgqdHZYd6Ve13DhMAz6HxzBBSGD9MbR4C3rYY2EFjWlsCPVxThonF6Wg3GfJRB91IRgMrw/LJFTw7VaOy1lX7nX3cHOJMuE0QkjmPu2uCY6IfuPGV9+qK/5jFHQ0pZPRrBpMgWOfpwq4N9B2J3WysUKCUV9OP+VkyN835lSeA7LuO9n1HfAme4a813OUIAnDUqca1ZeOji/EBVKJfCt3hKvJTYJrQyWFPK/S3kWGsUP6axGsnUYAIK+eTwZRPgPgnBb4ClZCGW6Iiq0FczGWegyZNOsBHk+EDoRQ65EjUazrWVrSF3k3R4WXt+AoESnkt5wUrxTKiycxuPwAyUIl+1JI8fb6Lt7giXOU56O6DMm/k0DaZYfAjtsvYs1aEu+OqxUpGElUSNwA6UklnF1LT6xP2acyYpgJdY6pCI4VvVTRKQ1A8x7b0NgE71DHTQyI1LCPfoeNAorxR9kQXHq/UkrCE3SE3soMN2VlpequpDYOEuZWLWITtCQ7c8jh4Iukbk/AF70Q1U4e/rj71HNQeJqpb5S7SRsisRfQt+eGa99LsfW0ssU4F5pPS7sKutmvcrEy3b+UlnNhIUC6SgA/SaXOU5UdWgg9DjCR5PcwDl9moVVsWc3Tip4pLKfbpeZVNuP+GWD+0WkwTTxQuAQ91fOQldqxTfOYNwBkWRxLaUPe7E/epH2qHhSQHWdjkl4CdiX77rRx9daZ5lfOYuHrle+HNPAqsxr/ocGctpklbVkkQODv404mlLN8cGcPBSKbTJq397ZnQ3ZNZnT7ql63Q89qpTelGUVNb4kAOxjW+3NCTDndTAsiEFlbjxTJ6fbtJVdY/o6rI0eEWTW/Ogr28B5bZhZ6EtvTYg7eXaewoa0KV5UauXRK1PjVNtlMTxn9F20mJilWvdh+zfAkc1nXQNoweMiuSjlU23bypKeXCgXC+KkUffPLKB5d6vfZDZhU3hhOh/+T7JpPGRWUlaIrXbou+I1vdpgghZxzITklaVFkvwkdHg1wLkwJrSjQjERS7KzldsLeEm4EEmiBNaq4qKTYk5sFVVZqEgHj48aa24IqgBOPIYdCxONLe4z9GUQUkW26QRb8gxMXFsaLlTb1Y0/pkwSTU1l35/OtyOYociC/6+JhNC0JY3FkM5iemur7icsSHuwYeZwXVyFFxSYf9aZlH5RVqk0RFEukm3uB8eEMKrCTjd7tbeA6k3S65gmiePZYSt/dUXI6Jc74AHHurP1Kiyc4oOVpA0rvo9OdjM4oPwUkdyKG8q8ZrnbyWsfGy+Rp8zAfOtesBP6E6bw1P0SdNEheTV3qppZAXqE2sksD8bU33UTShAzn2zlQnI/aWAWlRY99uV11zEuIVX8ot1x/xEa2688jkOCixU9gRpWl43sJvwwOsYEeOZJO6ARvYmYNZv8x9IAVu7awnBm5GeeTpaxd4E7C3mFGKhbbODCAyqHRy33DQd6TAoCRnp4e/618n7kh4l/vAkt62KaKD/+hOiZDuYGMrLCaDldVAz1/jVquc+YpTCIDU04cHrymhHItG9m3uAwswrVNCRxWYvNNEW1B85GgtNpEdCBpVVFRo+xcaH/QkJDNGVa/q4b7YykLdOr0DNu2iIj715qJut1CR28LU0pSoaPNDq40devqVOQD/VAgYRFj+CvqBWiek0yud5aqbewFjnGwAhl2tSaHoB1mL/Ulcc4bet9NK0JHQanbs3W1J8uXnW90KR5iSfDy6CK7EnZgWqkISMpOhH0muucsA8hLO1c8KV+SuNAMqxws8glTeGVjNZgt06uuENvfYbnoxje9QEhj9DqeUfPjawyuT2q0nIY4RkbX7hZd7nhjDGeOzBeK+Zb3RLLETEemQgtFt10CH26RU53zT0AxLbVjJK+t989025iX4TmvNHKnQeiVADMtQQ9NLtmetZoik4rsIjvHgVrisIUgqY7V1BwJctEjYSIb+0+o60GGENRseTFcChDVsgE33WZ4fwd8yseK7Do5xdJuXE4+4MrJW5YNESluipoP7QKuTuccqkX/g1F76SwHCaBdlQzkTeWl06tTyIiwj9BfVG9cSdDgt37dNOedGO27KV4UhbbpSFnwm1mxwvyJukeN4DbVLOFdEvxggfW1984rdpGRXPkMLx6rVr1ZoxLYwOp4U1sxISik+wFOWLIvGlqhA2x3B2O1ypw5lsHgx14ZYDFlkqx55e2kvaRNNf/N0Up2cauUYVTYBQbO/ZZNzG6vjb8q3lylHDdQuuRgg1HJfuVn1un8RZAePRZX+wo4pe23LSep0Y1JfuLSeqGEajAQfKQs7XNazPFkhCT2/CyC0sYDub5ZbwNEJsv61hD+UEVr15oIgc9MqHoJt07nueV2WnjHsdSmV7wZ8lsJ3H/rqnBsJLOndev8DMVa9swFCykYuTrAEnDRxociAvxbcSRPWJHbvj7iHOckUuCCk9qUmO8SmiYlFjzwmvduuPiKZxC0UAORMmXsjj4Za7SwqyDVwZcd5jrYiN4SpXzPFXisFaWJ6XJ9VlB1j7LaubxZ/L0D6XQoQlDPSfhcfs/jfqeUdJKZ4XyJCl3ONeE2SqolpDW7ejE7041dZ3jX/OkDCv0cqLanCdcH0i/CY0/qDnoo8SUEqLmi0V46zoozbBjG7esgfCvA5bhBAKNK7ESdS9l0Gx+X8aD6F0HrdlXGNHkDyp58U9xxMu127JUeafWxrVmnnMmelMdW2wNDJgl0EHKE+Oitu2FpNSNRagTiNxjbyMBWuTNtoxHJoJv1quQdB8qzwS/iAHVvHl/NQijrZKqlwzj2uHUmPWPnXHQkTlkyJG6//jciL9vOHX7fFhjNaeCMtHM2kjVNLAAEpX/U34FGx1z63/LukI3VUrOn17abxRJ9MtQlznqMXONZYt119ifMiHUrRmgGE7LnyEiEcXzuaSTnhGkDANUxvr/XidImPotC5Wf/QrWMsfE6azA/uFpwMiLlksoc64OPfYRozt9EtTpVqGxpz7tsy9sgK0sRCCnkXAeL+Thpi6QNe5dGc3K89ydvVKZlqp8VrrgIQLPmY1n0tOeB9lXh5+kVn9oFDF1U6KlrL/sQ5cwKNvEVWNPZSSAGpXWnqIYSjN6Yhtl0uSMEgP7MLvecwpSP6ATw0Hvrgr0Wkl3EHT9Gmej2km0ablFzHKmpy9YUUkC4AxKAd9foudJTbu65wBeFHsGdwsEHQbtQXEYBqUcCjgGORNt21VgXi54f/qdPhsEgKSKUA8ZQlBe9IQ0CSrfrVLmHEkWpYA4kXSwx3I1RgCkDdZTw740gPpV6x+8QIdBImA4GGVusUAMTu5Z5NZ0J92Xd0F98IDz4jvQpnzIih2bMfqeTbDtCYh0F9Q6WVm9IVK+BFex3HpzDRogQOQD6U5HPUkwFaLVqV23uvfhGnOiwMhJZ/kyhQ7fOYgWwgBx3NkjfaLK69XRDQBiVPRBe3owDEfEjA5PieM3WRbsKv4INoRtPijo99hQTjvivovTxm3TKRV2SuWBgQ9N5cppmTKUDMp8wiWgWN5TGGPctILklDzJsjKzkBM8mqNeFKTfEAAqQikUkgP5ntArXnprZi1hCt1Rkge1yfyStt3M/4+GQu7Z1bBv5S/YoBELe+Zu/GWEKVQXQG2LQleiCt8iOSyvosxhapdocBwjl9R25QuJ62bOr7I6uVn1FF1cyqeucRji3spDtQ6W26ch1AjpOMUcn7z8ph7g4BxDAUn0brncK1iTpDxG6PysvSbjclR1hFXUJ3KbQzBE8PJlwTQcJNZx0z5fQEJfffqa/5CSCGscIjdkPvnroLF2gdX9XOIunp6Vca49ODUrLnUlHR1WsCxMkVaUTWM0C/LIBphaNu0JJs8iEl2F7nGcwcSL3PAcKQBI1TwtxFCCGpze9KjMgxtv4oyHxpAkIESFUp9tOx7YF381XDDVhzvPTd7nyz3+yFADk8n6DSk+/krsQ3QBpj8bLhshfDWjNwU92N8LFzenuPJE+XRFjKRQjw88Pw9TkE7V8Mpy8yXroSIFsla2lRn8GElTN6zZ8AYZCQnRXIZgfx9t5C6ZUJ5OYeo1HblRVeKkDcFR02MEH44ZgeN0x1iJD5naA/EQA/o0EZQAq1t9ZXEeJot9bvM4CkqLbTbgJOd1XWIV069e8riHMgXkcbOI6qBv3qNfYjs9jDPsR9Unj9nBAdN0C+D+ZLAOHmzydTFk3S5uLb4rpLT8r0x8zlxz8miwBSlAq6Bk3ckwYHVtbqPE0SdjQvMQrzMv9kF84in+baJggD3Z8uYlKwc0NAB1mS/jPTT3MIr5Rcn5dS9MbpaT8AEnQ3Bru1OW4qA7E0r9pfiVcAJJ8dxWGZab62CcKID79G9WD2+og5n7nzjc9hOo2gexjj7AuSY531nHaAOGXm4ewXR0+y5vH2bnR8y//HdYHvWS8gzCxYu7YJQlfx+wqzO0gJaKO7rTyAHBb+VUnua8ImJPM2XifY3qLKkmfKL946vBSPOheOz+OLMZK5NMWdx9lam5bQxvWRZ9U9f20ThBxjddAKfiXop5JbnCOwUBf+FTKvybyMWG+ScZDAnF2yVwSzjpfi736DoZkAqRasX2NGWcYK0FuFtW/6TJijXB5h0WKsAKaKPwn64E3PjlakhtqdQ98wnD1Mp4xqPiTe1rYk8AINYLDe1ZS8t977lFJl0+hLs/eLr2FdxaODpw3ypedex1eeGS6PsChdnO8q/2TCzY3p5x/HbSbyMnGFgwlYVl17ey5b4s3tV/8x//vZM2L1JdUnTdmb0t+iJDSyMgF/dB54vGWmcHc6kPb2C0T97yrmpIDVxo/h47hN22KdfiuFvfc49F6Ttyhb+sVdQVyqcF8frN/u+npQDN8R0ttkhM5OZCDzXwi5cIZCNNPDOVoMl5iWRIsqJ8FSHRuBvWsSAeX5jDbFL/7Rr8d+7zlv7PSh3a4CiNGlS7whskLkrwNu9vw8DCe3Sc01g2BbAtYYYX/1owT0agN6S8xJmNlt1Ivw4cjcIqcC3Os2tf9S0YrhAs8uxOGlLsMeJhHxFLeIUvtnBcSXGn/FEsqwtlxgOl1qLgJIe+S4JBa3VXFsPBZH/G86j9mKkYCavTYvYkX97d3/mP3Pgv82QUFETtg6vYZaurREQgP6YL8wQI07EKdB6oEQolDQZ21Xul+7AxRtWATglBCPFnXaYd+UPS6YQzSlPYt7JfNEShvYiQ7AoI4J9YnLLwMk6SQJ9gpFD8h59F9bMjJQSc6YhXGAjyby5RutEWSZq6+faqRhb2u20VDMpMAbZEmo46l35N/FR1FKopM+1xuEx/2Xr8NDqkiMlaH3TpO8zmvoWEj+JgXeLqvSNObhcb8LkC6TkN/2vVYvnNqquBxBgzf8ZoQcvcCx4lTAyIGyk8po5GNJsRD8uiEpLrTBmtImrvc4EFoCXY971rrqdgTQTmrUrcwu7LX36h9cqfB5PWhTnJ3srz1DB5+DguSesS+5XHIxL6u1Eg+ZxCZfzRfkoN4yd0HFsgOh4WM7ofusneKTvaAdstgwt+mhkAo81H2NHXmOxEFP0sEAMUzcQ00QBgOtK1VxztGaeP8xVh+eMzT/1adNv7DOdcoPiSvi4skihVHJJAaripAt2JUBQUe1NplYPg5AM8TCAPHMgcxGFSDTBgjCwOJUaYbDE0G8qu47tbfxbYqAnas39j1E68Z5DCjiwEQIcXvswiQJm9rVgDzcgUl65430z/Z7cIt5TSlBb3xx4vG4QJCHhXVZU/GmNI8L9QX76Ic+x2lQicYw8hAhk913Vy7Mddo8VrfgKrJwYy3izEqdoKCToFfuNgs7kR0M8nsOb6h0FfHTs8d7C/MtXGn9hW2SljlLm0QkQnHwBGkOnkDOloc+oQ9jK7My5rsiWj/s8xY7fw9lRVKs4UN+/h0pOsecP52FzzipzYNGECGUpKfwjwJ9bfPqoH2b79qIwEErYEnKoIWcKOsEbRqaeDOR0dNreiyNgd6DW8ibPPY4Y9GNsHgJQTs+rgQgHmyUWgyXfDqiD2qlv5I4EcBh5K4DkcrGEDc+VxaF8OD2QTKzQP/ojn1cj50XBS2JuIUBIiz4eVS4fFGUDQPbgzmEPH90D/7301jcoCAJkylS0aQjam9AwJ2Im73ypwKlZ3NUd+bHpzbVCR9RTLNpGgWs+uadwIm9+Oe7UefUUhCw/dEH/LyXc1tDhT2iE22EemXQaS9E19t9nmcxSZhS+YjDgTHvlljJe7CT2Ynj1xpLIe+o9ARNVjOZ9shK2vegOlwY+ZjAywMrriP6On+eJbpv6RJi29YZ8y2dRmkXW2FxrZtPHa+XcqPoOyMsxeKWcfKwhuY4nZEbjgO9FKemUZNZUXg2Zt/u9riMoi7P+uUsncCdRq8tKxGMVjql3GJPu4JlKlq/R7m52e64tJ6kY6s/+ri613maDO1+8ZWunBvAnMjTk3npqIebpj7JIjx43+qum+Thkls3Qnumzm7eLYbXFlscVSZ2r/DywO3a2T97Ql3T+QAAIABJREFUNSnaGWop8jm2bjNrkn8BHixVlEmGoDUt3RYDQws7x3B/Q5d9ZPwy7kKc8poZWqn8+rnMYqnF3nB+Xml5TG4t92BHR7MMwV04xsrIaiPiZjpmi0Ll6BHSm4kedyHl9nYXYt8ydIZUs/LgkdWpwdu43sNcIDbBn3/rby5gzVCZkXOX4JDms3WtU9NJ02fceW48vEtCgbjJM2f3scSdvM4TFti2577590jRtijVEXSMS15kmGglIJtfBXZAfpyHT2TTFN2yU3L+sDLVGevE3u4+dq2P1nd501ORzFiKOjb8YtMLszLtDOK5wUtvHIERSF+7a+Wa6nJo2BE34+EUxmqGjEAO3nlWqv2q+yihWnST9CbWWANLt2EhVClqwl99NQIK18xYCLgmFbk5C0ZoWbPrGYE+auFZqWh+yX2UiRDOcFfOJkcIXR080HcG6z2svKjUn26/kYAMC1EJAYgBDVfQzKoKZCcTrxXVr5anGu9/5gXwpmcpQqYhSBUWg3Ryc269s60on+4rhXdxfkfoO6+UpxPivwk8LS1VoQI9J8dz41EZHw31HF0UvkNsYBAhsyTWy4y5s+98WlgJ1nGK0VEtARmWNDwSQMKWwWtSLyN0J2dr0FSzM/YmAq1FbGbePgUR0gXbyG8L41p3ycY3WQTUWYiOqokoBSBpChADC61H3ST9NZBgLtHLiu8bJPvHZVnSOQEHrGU5mvR9fGvywYmwZh1Cu7GqbVGlQzjtGnkkDgOfdtQtPVgIq4VpGu1bqldOxVNNV0FCPNRhGpIp9DvJzuALIqwkzfBfi9GbqoWMlDLCNnXZBQRIVZdefDaTlmcaw1UbCKQyOAhE+KyTRPnVdnkE5aoaVhOWiA/xoNGtY1LWrbz8QAu59ai7t8uBZrLyvkyp0jtPP5TXhdTIrlQ6ijofkule0C8ySwk6EmCd7tjqCDhPn2lCbKaHnGdZo/1QGuA/1ZrKdFf9yu5H1F8XMvkqYFrUEgLL+r7aFavePwV+ZlbbrG6nuFEAMteXSTBAsn6M9cMyssxSYFF415Px4IsWIgRushnwOkd7uzA3qYkxLT907qxM0kvQhwYlIZapQnAtygSBArToGpvtsx5dj4WC4mULEcbfN/+6UzhS6BmUBqld7kC6JMAa5ktNN7xOpH7BNv0l8Pvol3DL7F7jKZU/ad0uqcZcdrDOOv94Q7ihpRv31uiKGkY4yT8eD5JlxQRkBJCOpPINSvKL+jKnmduWsNlWWyHuvQdrHtNlcjjjiCfuwrUnViJkTggIZ6PIm24B05PMrJt+WQbfnL9gyc6CaIZV3OtEVB25BiGV6kIaDSB468P2nUq55X5NhJX4+yRnEzJeg6IIfV5Akj8HWVUcIDGXtSUJK83aqt09L2/Y5TlCyPyVH1KxSmF9YL/CPIlwuytoJo0fYE3n9PvyjBTiAB3yrRmKbDL26W2y6We5vxf0x5EJoc5blnmOvroRH4TFPS3W+jgo6wV9xdHMdz6NcRC6boRYEPscIZeekRZREHTYk1opcYHSO0yWw9K6qccOtl6TES2unufpHllOdvysbJmLZ7y23M/0t0wrWDExHJa763Y5NyTz2SCQZ5j48x0vNW0ZvH7JwmgYH1LLtKu/Fx71afD1jnRCMgaQWNmKRkF71rZysQXRbYw2gcLQDpGbkbEqByBBJD1DCGSo04vCJMTr4cOeaYhOBFBPiTECQt+GwTmmTObFkVXyz08SmbCkPuZAe/J9BqAEP7xt8tFNV8moX6EZNluq+s+RVrXO3yY6Z6Rz1UCDCE992AmjOOtGWHMd2Sh5h003wBqVjSuSqnigeuTAT49SGmNTciCyxzWs78L48G3X9Mn51EL0YgpMI7sQs4Fejtj6mM+kON0Iq/LKLphhcuoM1UiLMcvkxVgGQJyYpdWVNDOyVn8QvCGfZ9+X5W7NSZrPaWoQkdoVIBfZVCMsZoC1JZ69rxdMQ2bEgnkgRHMg/IJcYrxzQyC9MlZlHpKXSY6Kv3RiG+tc0rG46fUT+CD0GJ+1GG1O3OYqfP288xMOVzBlNqR66AEIwADpCz32qiIcvt1VEhDoub+uMumoAyG3sZpgwMgM8Y6s4DRBjRNtxTX8LsLH4SGpjAA54k3nCUAKHSBpZV69a+z2iioBVqbL7yTUgVSxry+oyxvaA59Eg1qiCKtLu8DDrJJQ8M/6E0CO+AjSxLNnOkD8klZ3WNcNF+Qgnqmu1ABNEbd23jpbZK1csMvBKtJM5tFl4Gvcn5sWGgNAo1tB9osaAGDwdiq7xoQf82bbpcqTpCnWuWPMWEVnaVdFxzCdteZ8hYfOSwd0vLBhXLVI7uABmyg9f2aJG2TXYICsMuEH1FnmLtGqILpcGEeiIiaBB9hO+9dWXNUmPM2muPOgcUZISIQKp9sCmb02cEFDhOoGgQ4QERP+2NqqcVVHiP90Kt2EFcQZubXZ1YwBJKirqbJqWEgTpHAKh8VyfazWUphKt2sZ5SOCANlW1lQ8baB9rBDYF27ulKZ4U1lOzwqiMwNQDW7JssnAnVSTEwLEhIUBNkl26BkZevRs5rCWPkul13o+gqwAoRYESCQonkTySpnbG5ZDjMqUiX4mXmPVpECWj5QjJgPI6yLSzbXgBhsZmt6sLYexTqyFuyv0/BPjIPXRf8tCgBzNRP5Sezb79Nole/+ygvXIDKPpwGRPMufWKO/JCo7zq/vIX3viT+t7vVoTpHBS+8oP6eqVuqluDtHMY4/m+SPJIIeg3qyvQRcnXqHExyG9gF3CaNend7oWQF7HJds0bVeMsDzD8TS+OSMbwxpcZ+N8vrHMwLfFAYgDIxf/L6Guijsy+3Vd1PGhsNAgxgwfqSZyThQQk0cFyCsxsc0fQVCLsNoycdnmCpgzYom3FjTYNavBuhUCJIBP3n8dr6S1hdypKNYwAFIATz0Jqv8tmQJ0tgLhJyUmQA4Kjg6gBqjVsID4shD/1p0sXuY6kO9fDoa7y9F6JAIkz+Ppc0blf+yvd7+Fj0ERK503p/kJQMLcMI5Jqb2glroSIp7OA8hBq/5UHU1Cpk6VlUsaMUGvp6I9wvMHGf2OMxDCdSEBcJx5fkQ7+HX++efbsFzaavdrAPJQVPAUkdaA4eMlIe4kfx+R9xEEAsABiDPVIizHIC232d85oNxkuWW7xBSUKjJTAS61cdN/tt1hgCxDRLquFQXIB9c3jH+UmZ2XLaHB00aTckb+JAh6gx54v0mJZgL08ALtJD4CP0/WpiLMdjouoSyJgk0QIIG+C+cEkIc4k7yk5cz9IoC4w8Ot04hyA2OGk+pWJx0gA1s8x+8NRsBrsbbNB5E7T3RuuQNsR2UHgJHrCp3oWYA7h9v0WTgCQOAxN5DCVRVlZUfi9mX43N3MoFbKqHogvVgLR2GOUsLBs88oOBNQis7J0MO0ulgB5R3DV8Hw3FjRSnqNHUq5+hQgjQYQmRcBxXOtWQJIOUaI03CzQj+20ek+Hn2rnhgoj4ntBUBcV2qCJHqGPsZSG5l8y6hGD2RqEhcgQWJoqwwgnQgQYHnf7IKEn+yjEQmGXwGuXtSTMZkCJMyt+pBDm0htqwRHKVEgymDmZ93qpOiBIb6RpuBrACSTQKuyswdWkyBNt5AhqdMXmcgAIUz0vL4Mi7bHIJ5ohQHiTvc/dYvzirstrFE7DkDmR3pO3O3Tsx51aCZAgNVJj+EjPDrn+Y5cYRpqGDQ2QDhcITcLYNM8HvBgpODnL5fOcYeEn37sNkD6sPD5KSfUpNnLjbMEffhrAHFqNMSquFmEiLunllSSpKVdxBgtFEmHf8c7cEpFBIsE+DWnMH736Rsr+D7NFCCWBRCqEwFlNZ/FrgAudYQAYs5vZFZ5sJQkjUJetWiiiDOSYAvjZqc2qERYjuGv6/yjPfCP4X6/KTclgHCCrI+sx4FGIc6TScMDCM2JgLKar09p7kMc5In9+eHOVL7r2PlPRvgqtb1gsGM2OCsps4riTtI8tiLGaKZgqaGbs4pIicgJAMIhnPpchicyTO8ggYM/MECmy2aprUFHnFcDo+JB8OEnRyEMHYjJ7K5/RYv3FazaUtifT5GpdUjThRthBcbghZ/Xn6C9J050a07S0VHYfzFnziXwPXusZtI3sfsY9WsNsR/sRnYyDk1nOxnHQ31+4uwb+f0WPFB5cQe2EUXDXbcfNLPnmmHWiLCgOVhHObJl8sMNVWRw0wSIxtx7nAVsiwCZbWTG9kdMeHSOyFRzw7zQjHKucnoKjpAsIilmQG+6TjIx25m7vTYxm81KRg48pRPXZvdSiZmB40/TBV2dnOnHG5Z8Ew6Q2QHDmSVDVbJAe6B5aCjTkBvtjyemEgDeGeSXQEJ/3lf1BDd5WJvuIiMkwir3NQeSpp3yQAsFq2Dc1F27OvnLp+X5vQoBMkEIvlczME5mh15xOz+lbfiK0/nVjVegY+LMCb9lzvoul+4m0eKuzSgo8iaG1/Hz0kqAgrrMr2CtAGRdvrFPnk0F740CkCFjFN+rmThvz0Lqvv4MhTgMEtwplBzjAxO3ObS4/BJAtq8uqsm6A/1GsjnBETy6mwZfhmbS200fIOsI+TgLHbDNYoCMpKaMOIMcF+0yRMUoJ78ylCqup1xruNNtQ/egFILsT+Vz1ExRFaXsggzdEv62znGWSCRCu0nSbXe7GiGmdguhWQ6QLniNnkeCqJDnbyfY5EGA+kWNizA+sufhY9YGFHRR7aYpmVQE4XrA//b7Jj10EIpwDcACQHREshkdEjpAqiBjcryHdMzo69DupcmnP6KY72x8gzw9njqdUq4mB8ixDF4VJWGsZOV9xP/WAW8pUEJBf7sIICp7SIDvdVohVlhVa2q4bRtrfrR5h6Ucvf+wfe4ERTsvUowed1Gd3uKILnAgBEuXoJ+dKXmOk53xrLNo9i6f1nNKSXqQiXUjgQT4JelotwKa6PoiGgk+U1ALZ4b65fQZXnjOzNGdwIFkvGRWoJ8dCTD0wnItqchXvDY+kME3CkAOlaCsoIc5StQz9C3lYNZggdtc02AOoxBi2EHXpMlCgsmrbGqiFxs/UPe4Oe/Q55D66EGYR8Sr51bRYqsMIMfGMI2eb/nqQAmKNdrhlfIlinISro/2Er2daaUM2RRcl7RsMQphQH/aj4dDpiircBVyYgnHvSERaUsA8S3KmJVV0DAuAAPCHoICHp1jF4muUgWELHmjQKWsKS2OaPxG/LFxkoEnsCFvNQm3+XjpFqCrEpEMQw8AyM6hK2ITh2r8DxxRhb7otbbC2npSQjUC6UP0B09BCq5Gh7OQVaJi5qT1FXBLG/IBVmgajdikEieQ5CkIByC2h7JEqywSibkC/Hf/0qJ0jD0nVqScSzhxTQAP5N7LapE3MTBVwA6QYS/2pRz0TbyIlFmMvAuEKWkAMRXYmENdJ2RFpKMOhZr55wD6QIxWPWfQht2XSOP6gJSCwytidf6/92i+3BCodYKrs1G2jSHcWDFzU++CkABiW1ZJkKpMpTQC7zqt7pETrX0KrChnc37Nu7rFCCsxwt6zhXrJr50QILuFdeWaPqGKiieQlqEAwRXe7V0pBRn3xBpka5hjFl289om8P+kg+1SauS+2iJokdCY6kGOKHpAbSxBANuT2fH1w/3PZyAan3FSV2CSlDgQghN2r7q6Wgoy1JyKQzlWW+zAqO8sr2VFGejxnFaq8lc9T6egfR6yMQgbItA7auW0dmCdt1OYMZ+bpCSBZb5jHSjclNOgFxkvwAel1B6IBotbJ0Cftl9rojvOtGQWd0wDIISnxtCPj9hWy3qfUHYyb2Izi7ukOAqSLgqKVisQgTbfQ7Q5Dkxiqt6ln4VgeKCFJgFkvmQcO+PEY4ZpRArw86qwyGvB1ST36+b1tpNLF8yg3pj0VVyi+h6YmDBBNMmUQb8PNgAGzUEn1VVzCrHsP2frPtUmnuBSlOU7mM9qlhKZEXaQp3ShJUp+Os2GHacGLBDzAZQEk6SwmJlGBRqcFVM96afPRwmN3zTIH+ShAWUpBPOedBJJVOzyDLhqdd7yd1Cd9QUee2byvbZ6GAUJoTq8FAGxClkV+0FanD5Jwp5iwW7tfd6XVNkhgOxCD7rjbcMplkDye5w/2hLJOgtQ5p0dJ0Fw/dWxggOAyimWtBkk6DwkMCQv4e576wOjQIKagENP9yqssVgkz279XLJjruC75c1m7SCKsQuAmOyFNKzamGzkP/iAA2RPWqFPkpRZkI5y/OvglOuBBBiS22q7albWWpLkFqnthfaNjVOZz2+sg1tfXhK1y1HIZQyRW3iNxaQkfKEAe/zPt4k77FUYCT23x9UeXNwwgAfzPL1UHC/3r+aMyPb+nSSswIW5hMWFn+ax+XyUqEAczLG29dQdxTm4iFrIbGcVLlcfmtUeHspajf5CUXndXReRRWAg/Djw4k9dnEuo81gqHa40iJ68Twz3/U9TrV5keH57F87aXFoJNzYtd9s0R7mrCvKvceRATZDn6Bhjl52JTRN5jhw5WQ775TItKLePt2lzv77jyUh3dsWsKCSHVGtl2461kF0LrvabkrTEnc0T9eZmgFMwASYcl2QOdx4AMTD1P2G0Lm4nbcBUB1sx10MGq6LkroSdv/lGDSODY64X+/p4rLAGksGnWDSmP9JvSFWN2O2aCGxCxPMHBFSUaSglpJpQ+bX4JAPKcYhVxo/gwnu3mVi/Qk/i+UF/WLafrJSFBXCUMi7o0+Xb9lUkOzkqZSyNH8mkgvx+craHwmHDoTHpbL9IfGRvfdW2PmUYPnI3AHLVATU439/ddfgUgiU/iivBREEZYvKuSHFxYnKU6+7Ty4dN2lxYFN4gCxC5Tec8l00+EZNQ0NiCIKgozBW9PPZBWUZCOoztmUWCJKS21QLSUbBAsltt7LhFA7myAGPyl/0NIRQOPAlkTzxad+v3UA3npTpqjV15EV9bG+WRXJ+L3RBFtt78MEMvOIBOhuvLI1DMhdfVAbOKUHEh+a2y1wFefgysyo16nmruJu8ITCBxCjnr70wDpbIAEUvXRV0N48wHyT1UhTijN3n/lanKAGH4LN8PG3FMrQ3sbWRrvNMaoiqWt0v4LAHnqXQcBU7Etts/O7S8z3eKc2AFW/IXQCkRzFib4ltmZSoIs+NuU7NO0/UG1yLlE+iOHZgfsTG7DpBBvfxogJi+VWMLyIFGHHuZLu7xv3MQj3X/xSnKAJDaL1CM5ehCbEpvqx+x5XBmVGH39lvdPvb33SmyAvMxYNHaEle/LcXlBWomWNtvxC0XdXXfPhXlHdwEgnR1hdaSPXtYflq1gKzZI5seIl6vW9/ILADGvRrlx2f8bM7wfPqiENVn2uLiHwuwgXe48vgnDM+kNLx8oaOxJ9oB8zaZTATfTYwoOZzaN7q5xbwbIrq0YJWTeLl8IVCl567Kl2GV6zfXwQM+G5+kOQo42cplfmaMyzLIls2PqucuQqVnk07itex9A9o2sWdjFevq1WWcQ1av1yh+BbbgeHcdT0hitdCMhOjp265ZK5aAJ4eGVxL6u8IFPo5v4BoAY36ervh3vkHMDrFko6pR65Xu/4z01q+OKkcwAiJe0QTp3BNoRNykI8jRBe0ccExxVsfqlANm9BkJWyTwDEu5KYjhVw4HEt/U7TitGIgMgVTJN4Ln/wumIDFNLD7A6jPSVxqSd7AMASZQsl5BBPnE9NqMzIvHS7jDlzxMRO3GlQZIrRfC1gpBgOWlx04bvZ/NisXvIXFtNKh4IEE9tjprVZSRC0fOYlp5Aaf53G+SMYN0JAJLZrfeGPHp+6gG9nyrcFs4XDWiXAGTToCufqol8B+IgrXsr6XWQtnXcL5+D2hgAKQKmiVsGiFfipjm+U5TFBJNd3v0CgHBmUQIjughKU9rfjyQWSZFia+a38TE59F0uPcYOywyS1f8E7ys18LF8VVhZhsxcdXsFQIwSof94ewkYCB4O7l3CaXbvZSI2RnEjyMUr2Yk9BpD7PwX2uNQhskVQ2RGVZqeaTOGKiS7OB1gyPiJ4igZpm7mAlLb1++/zD2c2NPOXOE9emV8HyGMY1a752rHsICZv3FTnSsoFADFKIy9HSj/8h2eS7gUOZHszGdGyFpdHebNp4y41MgIUMsOrIJL/Fky4AXJc/fbbHoS+0DIgdtYpzQL+IXxMy9SJU7wpfICY3wfIRLW2WB2Rc9JbVRgcWfYgc2MV6K/AD/7AXEBsfnN8NbVfGy+2sGxp8PtvA2S2/zTau3qAVZkYXf5we3OMw+moFDWPva3XNMTd3qkxM/f4qe5MG4fIjjEV7R3PdnY21cSwsxZVgMxU2HD/kfSsns+XDkzZx+X9x9Kr/Yy0OWYqsb5n0Pf7HJ6GNw2/AKjdOWjP4wy5azmQySNbUQjPfwggU3gMW92MfdGcRoMNb58otBNm/4iBOKjvRUI7yV1a4Y2Md1zDYNSndJWFBpOO6Tyf3AwXIJadtagBBFiLjK+v4xv9cc7vw3bpyK1hmcTzPY6Cv0yxtUEVH9trI5GoD+L73FvyFknzjJ5bWXTr2L4yXrEbyEPz4k5rTR8SuNtWrk1b5wCZIOTgOWumPR476Jl/NHx0VCvKo8UVeEbSdhfV1iA41t5aqGiXuUersYOyoICOltcqJIKabMBHC0AuhhAglrkn6unUmEat5qfnL3c/UgBdR7XCnUqnCZ0OzUtNWyrTOvOtp2U27B0bvnk1skLeGmE9l0SiyS6kMR+iE8m9AyBfS7lMna/acOcI3WlNBQJfRz5lhhy2xrpAMpkBFaYNBKbxjfzbMxeiY06VWpJoGv7OzJ0dSVERIICZwyRZBgMHRUnHL++KTNskL9pow92mbYoUTTBFL4LwIhpmvuPZ9rpfhw6S1/WbjrYv8WOeJ4qyGkBWGvdh4OK9isb0x6f4MrbShWRVuS0lygu/seQEy1qL0yzTebNMx8hFHZfro1Jh0gmS76AEkKYR8zy/n6CAvS/j+mKB8sDjFs3qSbHSCGs8l19XRzczm55SJLPvr+U+ygIi2niBF0YQ0hwkUk82AyCLKkNuEBMUdPZjT6Tsyw7DvV9Xw9BXuGGYn3ETaSQt1+iFEVaQNQAqswLU+StbWM3A7qJAZUQbHyNPSYrVAtX20wHS400TIP1oAMqchvZsFNwhn3OjF15GH5t1J6ARhFhWQcovT/8fa2Z4c7GK1CH+qeMW3eG0tK3GD9xqWKVmD40IkFqUiVDteJ8BgG86Jt998EvD8FfhAJE2XaKV1PSdFKOV6RAcWxB+8zTXsdEVDs0aJUdYL2kS1lHnAMRT5SPlBYdw/HkVwlQ5QMYMDLIbWmkUIPICMzjZ4Dg1Xrs+u3l2Qp3N68DCRnibn3S942Lo7kX2plJhSgCIktz8NgIIXOj1Z5tgDi+3jJ6Ku9MBssKqh7hFkdMkdBpc/cx1CH0wb2kkBBxAVIOQfpQLKNpZspmkCwBCK+NJCg7hhP8AeM96fBrhfP/jbdAYQJZGzgK7yFvlMjdYL8RzZy/scKeBPaccPWT2Hgkj1dS7abClULOXqWcBAkhq7PJOdA9h9Tb0xGkEkAAoMZnzfwtHl+IHNvslP8UAEq8h0HdOnJTX9zENQ28sZMqTnVF1H0bfr33a4YGNVX3cC8Oruae0RRCZjd6amwCEk4uNpPcG0yp99Dbj/KyGQWGiHn5KRJdBIwBZpLhwK2MVKSdtFkgwN/a0KiJR0W9XXAwNraRCux26Vqwd4ojNq9FD9zcnufFDv/TkO9vw5czrWGnw1/bwS4YOKI09RLlCFSaxUpCCdUwamI84vsonOKJ0xSZ0hgCj2qpBJ5m98sSS5pFF9hgV0ukLeFA/vJ5tcJon9Sd/407ns42bpAhA4hXvZppVdMSOP2whRHtP/IE8cIJFe2yTpU9a9VSdmqD5mKmu/6dyYXwNRdGWHuLAPI5t/KRX6AbBw7Huu41+WX8nQCasnEqPBZ5pvBU5tZhkzrCLDIjY625TLhzJSp2Jcbj2EQVVwSGtIpZcyuJRCegMUByzLmd8rDqIBcLhX4/quWVG7rwGIIFBxOqk/Y12hUY8PhSzxdZVEx0cLpddSM5jbvUhmvC8DHBc0oZJJ5OUzl6zQ3f8bR0wtu5cc3qOiV9KUekIGns+cdvYmCAAWY7Cx5SOQE8mt1NYnFd0J2ZmM48OcFYLrXhL2aQT449tYHYYoYxL2jCFxSny7oWG9DULrGPw9GEA6EYG2RxifH9W6TiGlsgQ0XaNd6drmg9+cr8tBFmvW4iPx6t7o1tjFUi/Gxmrr7iXmz+GQ+MHUyWt13p7n6KOm4PIDix7mpw7dzz89Xzkt/GNXA6QQO0wDJ2sH3hYvzarhRSH9njahbY3q5yKpaQSStZIr3ksS/aMEMevZM+4Bldo3lLKJHlyiOooTTeDTKEdfrc7F0vdnJG3XQOQTE1tAmJBAi0xqu9qL6jn5TLSa3RtLjBvCtZHDRI2nb5oYaG0orYJTjP8//2c8jTnAn/+qFOYbxA9zu0a20AtvPuhg3GC3FJ3NoF2ctkbULnw2JcvWHrZrvIPe1CcEBJ1lCsQh/nB0UlDc2BPR7GdY6Y6lAawiCyBQpjhaSynTN9PV+UkYtmM/fWJx8fLzTyhgWp5VPlxxAQf9np/Tykrk8gMbdJBDmfQ1EHPJBwc5wH+3Q38vUdaFtsl0mKemKXV8d1ZUXNSsZJ6xX7gyoBHZKyuz1gpC6RlzdIQ7VIWDSBlEmP9RA1+eJjr6YgF6spDTBdru6SNW2lB6HOPMDO6M0SRN38NRGLgL5GYqqUN2xxJuoJlrBQAuoOZP67vSdIdFI348+mow+NUT0coUCdEE+JOt0uYQJ1WUgw3uGon5vVeBpH98EpU7qkpUWzVLi2BnCj1bgJro0zD8SQ2XJh5x3Y4GW5gJPwrZBp7mrNfA5BMebgjd+enDqarLTqxYfvVwOpjpigTu4Cy5cNmQ0MZsGG6zh8UAAAgAElEQVTY7+qTdOS4OMDHpB5tYp3cdD/9X97yd2i2awBSKG0Qh9xbZ47g1vt7IbI3GZNQznqj1XKTUdmTym+HvCkNqSROa58SvXeywlPl5XjPqdv1Nd3hGoBEiuQPMjld2BwQhrU1gsm3QyNCuhmYVrgqfWlZ6Ktqa2P3/maTl7rjMgMcDpKjvLKi7L6448ldA5AboU+IdRMru5e78eIR4UqALXf51mya1scWku6mu85OKfJb+oWneRCLz93OLGXWwofHPjWqD9sPYdfgeyuSKfXK306wsUizK2EPNbYS5h2gx07s0x7e0g05zHeO52Ey6SA0LYC0dwDE4xr4SIRVReu9u2SFB0GEY8sLQRW+Z+r5jaQrttFPGoaF/ygTJ0fbF1m6nHMkzQTMKxeeEkuobWsu7wBIRRvpoyaIBbOYoKHiMBPwCbnEOHrHO4exJrv81Ik6arHeL3nPk3YIlHTPEvUg6GsaUCdrlzUJ52UEQCIwtDRG6blZ9JjOf8qHuMc/eQ8DK6Z1NEBq8LMP0gRxbS3U/ru+97R6a7UOK3U/YFb7xlNbb5xUNL6mkomyKLE6tYBk5Pni/YoIa/ixccYDMvRmcEe7sB785rFwxtsPmc5FJlxlo/m1ZWnP9jzt7fiiP+3qesZzea0c/YD/jQaQ2XzqlADYsKda4PA2LMwCK4YpCs+brmpNo1zKN24H7hrImTcuiH7kBXzRhNYKgtJTC3jkFqkWASAANsyXBzhF90slRvdXnEji2MxN+a5PsVBn+4PO52Q5e8Vo8cl2+HjNUvtOiHQic83zSCmzYYe7gtlRXpxq+xNOxDMFnLv2DQRS2u34HfWysvBYRqkOGFUmXZCATD63cEP6QUTYkFh3xMNqWD/jVGEs8xWQv+1EPFstKqvfg13XWxwn6n1t27FI3ighCNEIT0fDaxUBCG0UKaMAiXjGGLFhTDPgCe6ckUJpQb31EvXok/59ZLE+NfwvZvKT7kKBvDNUq24Ra1KI7whAkkxwuSGN9A5Gth2vy8SKVuZL+h14yOqZ+YI76bS0m7WJGQjL3KUiYA78eYuPb76sHQEIFZjHynRAJtIt9NCnY19uEhy7dVFctdCqRb2NBctGcSN9TWfTd+oiPtBV7DSoOumXH4mrQJOy6QDkmB8EmGlSwJd5XP47eJOd2Lt6K0RMXdirdMmNOlraHblCQKMYi3tE+TrdAbiNJjZohU3NKKLcJ3AAEkCE1dcKwI9DqTOH2lWk1RdD2DXZD3mSafZNV7tObhn80koL5Ry7PeOoisyqyXqY30aXGbQiYWYUET/21YojAKkQwF4irD12y6de+rljEG+/CRHaosItbqwJGiofGTj75kaZRMB645n0L66cO55RDZoUIPuKMOI+k4DUPpyM+4MAxEKwzAfc1FPdMjAZdLleGlcVglDvl17WbCQ+KxReonSO07Pzo9O/2G6y2WNmnBXTBCCVw0zl7PrBALLRXX8mAySCNfvwyoQo5z/ybBJ2afbXPMdBsGe8gVCQZPpjahkJodA4Vt+4h8ScyBY34QPmxlkvmx4ds9pqK1td1mFWh579JOCRWuxL6zj+enEg359iSLEETctWeqVG2uU6UJBPKiTkwfDEhgJkNnMZ2RyY7fAX6WY123OQvfkZRn4iknlKrSrqKEs41i7y8WOGAdLAHN++fGQcHC0jnBPOzSt5DtKz3lwlN6I7sxMZaVPZmbTB1PDnNI9/sQAQfpz1CZHnbdleL6hiAoQR3TkqQBL0JuNrqNsGKDLyKS+xFMnn70iNuv/ZzZUQy9qw8KwTuSHl2ClrqC5qj4TH/7Hin7lxVmz7xM4LM7gCA6ObsjaRuwldSJv7CIuUNCdExf5iGr4LZXZNaeIxJ8gdEtxrqc3RB9hhUZK0ApB55WeDaxeBA1gOB/hRW1oDyP6B2wU6prvnWB6PJUi8tXPxpGOrWUCAFPIsYTiJq078qeX/8q3sC+xRCYZ9SeUDGYUnROq5JCkyQKA1DhsERICb1vgd/nJMUVYBAiw5ElWbHrpO20UKwg3J0R40ymE0a4kAcWAo0F7fQxtYraa43yXu7qT1OryaJHTNBJHpJKQqIrqpds7mKJ7jzzB89NOfLANkecXvE5PY9rxduMWkw2HYv0H/4XQPESCNLBe8PUmYNYD94C9ebKC+XKpIPAiazLbpewR46ODi244LLOa7EkBWl8T/G1yyK2upaQCpsHnLFMJ6h5K6BGLyWNaNHz2216rmgf1gyl+Bh5HybJPGkqwyC1naXRMfr4/bqwFkeRF2CU59S1KkuFw3Kgl6yC6CALFgXSCd/9VWNtCpub+BD8aQ1ontl1TMax1OtsYkxEcjZFDty5aN1yzs1Sb7zmSddfbLrtn1kO7aZYfcTLwrUnZBPbfTPfD0MgkgGxxxJFJ9vl2nyi993KzycWACxBeaUKH5V9rciDOhoMZ7I1CEv3z5CCBfojelvmldPGdV3rmMmYb14I1EPOiDAKxIAVJgSFZad6NfuYBVUu3g0XETDyCJbliP+ndQ2NcFGnxheN8DgDxNYrNm1+qlyyQ3YI/dcBs3qSzuRs/UCAECj4a9vJcMFlPbfgX3N/IPtzjLlMh7OZm2sjLcGB8f4T4FyOs5dwz7Ya5yIjs2kAL8IOIkkf3LqKDqAWRVACCBvjr+jyQXVwzMZgZA5IrRUHgFn0MSPn4GxU8AOfr1wmH5tCuiKtKOx04BSIAAEoDKZCQBpMM8kbzyoB5bZ6z5zA8fLb73+JdoVgVmEq+vqODXfBEouIZZnHsAyGCP7paWpunftZ07E+ZcOmROAtBhn79vD5X8/bQiwFsbOdwgTh+Nualu9WUmIemK0xKYamYYssychfQKkPEW3SpXFl7aIenF20Em1VoPDV26+UP080/10Df26Wu1SlUkWF18PfEzy4IKibazdmH3BifMdzR1pzQByKwv9XYfsg+qmVW5PcIi6Jd9XG4edydAKRIK6oBbcivrvER7M8+18keZ/KMeEGa18nzVhpuVgBwK+pGiMREf7j4GiMnCd6S7Undz1SgotlY0DntN9Ny8hVIBoUiosJznDstExSiHqef22E5uSELtYV3VLTFoKVxqErcbN65Yn8MmPwaIyezVhxes1I3N6KiJdcRmHaV53bxCDLAJE9SahLazVd0yUiIOB5RpsXw0/9bXJXkSXUB0Nf3AevKFiI98HwPEKVX7VoKslR2oB6PcwKj3/EV5His1EkAK4QR4dpBFDm8tQZ7HJV4N8lKApJtu1QDryU92UUU45/4BiNPab2TF8FhcYxfwO37oAezzEqP8nwSQDrzlTDgBmZuiZp7aATzNYblV+rb+GpJqCtKl1atZFDTAx2tp+xsgQU+C+HfgQRBQIIbO2/yVQwBxlIpeZ3pa5qz5/JREgm82KgBpxBJo0MQHSnkcdytGTJdwHwGkH2ku9rAzh7El6HfgQVnjSQNInP+nylke1REhjaxP9JimNjRGRNEAiCcCxCniwxbRIu2hPbEjgNTBcznEdO3KEEtHsDwIVUsggTe4kf4MkEaKIRwryLLLy2VYyYRTWa270QDCrk63BcFo+hKqfB8AJA1v5BWW5EVaAtfZLlDF35j/OE7NtoG2nQA/ImCmrF+w2XZknrYqpAo1IKSZJ/xEideiRi/pstpXoJzFD4C8cip/Pmz2/ysTNIuWxmxasb7b9Icm4DMTYKQCOh0XLlBGX5HesWSAOOC/Btp3b4vb/BgVc8foufszQOxUnjkIXEjW7PysWM8qjZzb1EBtZO12IMjuZISIGtlhxT9H4r9M0BPutLdwU1qjgI+UjLA16/rF+xkgeeonLP9Bt18Kr87vN0jPVZ8e9EJVFoUAksm936qyrrHIueoB/rsJ19eTfoO56TQIcU7CyLJPs/qzMqBtgDMq3BPH7f5s9bLddSz7+5wwJ3J28gKQRC3TeGJ7azMq68mqfDGNQ9xtx49+VarlJHnK2zlVr3q6zRcP5aATRwAIdwJGFx+v1pN1vvI8efHQo4YAkkk+c84/la2nqyswC7Q7aPB/fytANkGvFagK+9NtZshiMPdesnlYlSc7/tg3FGMsOxM1jEZFkjTFbUD5q0F5fwe+LhMViWZhVlMBiFuIzwpmTgx6ZrUA0sTFMM/Bx3a+zQK4CMt0IO6q/GMoO35eH2OETKIKWZxCVGu05HKFpQleeBWAtIX4LGJnO6GpoBZAirRq6lhdxXy+zQg8ysBz+O0SQQA7n5w7FYiLyHYmqJydQMQFYBwk08OFUUtXuLysrmT6FVe+ePmr/jaAzD+och1I4dQCngDSwHIEocjLnSaksFewibkOuDBq9H2wQZ2zxRkgyc8B0km9LqcDkLQi2mOQTCW8DyBF1ifMi5JA3wAZzTFmliFK3A5hkq1TgQxLExjP4337WUDtwAh4owOkkaLTrgOQpcFli4T9Dk0L1AACxIqdVcJyrIrXF0BG3ezKSkDYHXSHzgDd2LoblW+DT9GNIXNJXuAT1wByJmZNTnYN8LB+XaFz2U8p3q/xK4+UcfMbAQIY1M75R44VyP1L0ofixM8dGDwa8lF1w7whT5HWuQWMEuHtPE0yEviWCx0ggZLAbYAoQfZ0gLCaKebr/j7xmpCjXeQA8WyAFCtAiOdlAgMPsu/eGm+BDJyhdD4+oNNrwzZbyR2PS7kz0J5tgj2LdRpPGzBmL3RTFSgHJkN1QqB/VCk4u4P8j/j9wypypzhAuh5AIBXU6SBM4tVaBzmIJ+RrXbtBCFbFTNuG09TpW8fDTW1E5CBkJEofWYehQ4DnAcRQQu6Gq5XVpVrY9mkKMvDiPfbpVwEEiplnjPfGows6MkCee35oLG+L4vT9aF1EOcTdzwYjQCW8ALkpwgNt8Ec56KwE3k/f8Ag0ETQPq5yJ9f387JehaYjpfDNAoJM4iWEKr58dz7fpUdIBmoCIhEwauZmyOXAzzUv1NRNFXPfmykYhthU4mi7QWek8gET8YBcC9CvFUZvaEtAjtwb6koQFcHkFII/VcTV5u1+pnzbvAlHWxEonXsCTiB7EMzogJuvpU5yZSZPlX2X8mxxBhw/aLu0YunCvYVGkEzcbgTMR0IEwQ8pXh6MQHylen+c41kMAqVgnkQSQShMl2rUjN2KmPma1OR6n1tE8iCmMboJMqrzSsDbbbtTpO2f2XWcfh8lYP7BJ4F012BTD63PsCkAqSuxOJA8cpvo4Q8Pn/v2UCp2uitXISEvSzhsju51tt3h+NNEzqRyeV/ayJA/iGB0QoZRipXzWfFbdjEG/qi9vpoe2YY+ZTjRthFkMg75+ko7/aLHoBkA+/PsPDQJIVwFIOOhzG2q1EWKAj4pZmWfVHcWDdMaCIanUaCXEpdCwYR6G6111XYxHGm4NPtKZFV2iwyyFv0hs8F1PDd06eZvt+14uBMjzfWbP6lc1Xrmo8iZf0+EIWLApjiYg4hNZcbAFIjNST12+Tu8no4fRkm1HJ6+QhOLmTqJ/VyA/DZM3UD0YwDesT+8o77AsMB4CKyEeF5mmlaXXPx8BxBQ6X6qqrp7IjIGhsZRCuinK/T8H/R0904n8cCp+si3uqjtprDwBtxwmh798b+RGAeKxxxLwQSciK/O50wGEFoNU3fF6dw0DiKO3ohfOY8DOCKKYbcEFyyr3hGajET6wlkWyKUh7L5JgZuDv8oibaoSjFbBKAQUghq8n9dzpAIpZA8fdeeyPBAOk0RN0v+luv8scLm5kLljm08My4s0MhvhCqgQMAOAItiiR2gseAEATFYlfDmCVAsQLaP3PJzpyFo9MqFIz/vmzaT4DpNITdBsVC0anxO1OHiOvcrUlCLMes0EeQ3znTDZE+OgGGkASXLXMygBpGEDcXTZ0hf+4xkiMJ73uiqchJ4A8oapcio+R8WmcaRE3fMRgU/8h7N5rTfs1GsSap+jjZLSitMDM4Esgk9yFFqg1GCA3BYA4DYCE9f1lwKbbk29wvBnYH9dwBMiTF0N3fy2ukAywSfb0/kunVO9KT5YlIPWcojvK4/VEGxLw5MET8hlLqu+bO0vojEJUQYtUhQCQIht5dMTtjSeEBF6T4jvHOfRBng99X5NQ3PbFxd4Ym1qmz1AWBkA6uZX+2Jeb8D5enVsrL2o5TDpXFR0g2oR7rgepvAGCwi4DCOogCAAxwslJwpakyYPvPBrIF/jSy7t/LiK7JYJJfu6M7sxAYjGnMQBihtP0bbA4ZrJMOsOALWg+VCisn0g0/Qk+l44GkJOjgTKUjqYw6wDJKFXRrUwUO7pib2XGQf+GCF8A8owPLAFpvG0GoxGoBr7fSlc5C5ClbVSI9/kDrqR8eEwftJk2CNPhez7jjCYbB/mhjhbBUIDkdYBEsyLOPVc+76QVOkAm/XGKnwHyjA8sAYEahNvwTJ7FnyJ4aAciK6Fsz1y1PgJIIj2VAUCO7ymjQ0eBqEzTNq6vTwRyeCTdU4TMWkc9kOz8R7xkl8QCeZ0sztloq2iBXsXuRJ4AYhkiJlADZFpcqBEvVNb5lg53fKtDgFi6KhS4bdKze9bzmv+LbZgZzMhMQQopMSwQfjraZ7wMIPUmdSGWLncSaL67QqFQ+j4YL8c3yAtYgD0wBG3kOHmy3yy0dLYQbpp6Z3IW5YBZQUOc+MmE9ZoztrbnpiCZpFSboY+p6Aeg518KkCbX4S/0BRqOZsGgdKHc4j6lkg6lpixO0BE1aocG7WFsRn8gac9/6qbPr1Fv5phyWfxDKkvpa5/hc86FKau7ge/RbLRuikf+KqMAiQoA2fCj5OTaXY4RtGRaKQtkpEfqxlwK4rbEqg2DQbAdhhjb4LOcTG+7AClXwCOAxlH/u3Pk5SsFjIE2hQT5h4TaZ/T8F0zdnVQAzKtbGKMnhjqdX5KNbMnDtKDWbgqZj1WHbz4PXtQcIIa4jbcDMm6B4Ua7FB8vLsJSvquRDk+AHIyXEaojByCOtLTgPLXiSq4M8zyzzJZI8cwcgDQxw6Rz1V8hPccw/Gdu8El59oYjUWYzAAdySjYdPYbN3tfXK0YS67GTmrcNMm0ehb0IIIZANQf1Mb96XJ0xgTSL7dNqO+QMECfuoAe2h4TS9DJ86GHwKh2rSn9+0A56qoGlyiFUnH45l460z6+Ttsl0KFKzqA+6CiAeOTodMDedZ50DzdfM67KRqbk+T2iKYQcTYJpuR+c+DF524DLlgBTmlJ1YXiJW1yOsTgJjJZVAK3S2DPqzUIBEDCCBZtcPByETJ34Jkc+pvjFBSCUCRN4h3LygCtEA61JHsMmDtUydPc72/Jwds6IE04bj+mJBmpxsIr0cD4EaP34igBDWLlXkdG/kO8SNUyPmK4EGkEpfdilMUBvWD86jZ/uTmnjra5vXqhwtm2rzN2g3gjh6WtsKBJy5jeatEsmrWeAMRPx3uYsA0mFigIUg3Jj675ZoymdpSGTJPef1pd7oDo9KkKWev2gSQL5726O1KI5y3N2yoZiOOdC+zJPejuFm4JYHkA1LMAI1Pq0ja8hsWlAVfsYImRj/yDF/bWEdDt2Jx5H5K8QTSQDIjzr2iOrmKb/KbMuh5ozD3WjhnKUU1jbocxxegEXHPfQAMlI325hjFpn6ZZGhJhcZaXa6rTuQ423Y+S/qJMPhmPNuT6Sa0VqUlxdQJGpHhZuGBMTSmBsNIAbrXFKCwYwGFCyAOEmT1UEA2W6rSci0F+4QgKzMEFYpQMLczWcKOB1TteyHkzmWKwukcCnLNjtQap6GVlE2fHaYpwAk3DiaDDfMjDpqQhwH3xB5Rtox3tVQFmUYZEV69OzIilKcVcNm/pMMGmOdcwgEIN+TZqXidzctSVmN9b1jMcBCdFek2lCA/jshf7YSgFTcWAT4mUXSQATRc0+i4WKozobc56s6R4IggFZBtgksbQ2Xeev3hDqFx9+kequMx2HwucRABEhDiqeNAhDLo9RjECvE5/fSb9qAfxy5uV9j7KsyEQAIJvJjInuZAamBUOagd+D3jxcjgAD59KGlkkKneU8D1XNxRuY/hk9yFCNspGNXIeMb0Efg8EDCCOr4gzt97U0ARyNxebLzY+uICT2RwgsPoXvxpmFojdxr4BYoe0PgTvqHcXOJ6N36guBqsWw9zenTiMQVjw7J5DMJIBVLLAMCkIBPBjewqmSA+Cxwk+DGypsH8Vgklmk9VwWOfMOAjGCdpVClGYKDymeAxEDeEFukgi6fW0lED2PwJA11B2pmVXmnIXVGEsvGkP+bpDENjJTN/GTM/MF0DgmafXUkIxOJg8HwoZA1jefPsIx+QvvS8AE2hVv4FFnGIHFfXPGdkcreULyuEE0TKTzPYIG0YY+hEaosBjspHnvS28lk2fkdduYcEihuGEjBQaRllf2m40E67Ubb0IbtQ3ktWUYgyJt2jkQHQl6Jsm/mNbMBw/GOxNGzqESAGCRHt1Ty9WelrxgZQAqhgWw/jcPmOlDWTkQG3HTrFLxdtpICpUjrbUUlgBRauGCFvI1+E89yHo1Nv/O76KOT7D52UpqvJ7Qvgqshc6odjQiQhGSGiTyd8LFoeC6+gXj/jdYgs7sArAWj+XOIV3n5HoyPTOuFRxJPBNuTY2W6X/OH6ERDrHYTF9eOD3Newlpd83ZjVjsc8e02xPB0xviOr57Gp7BYANrud+n0WKbF+dPyKyIe7WkvN5LK9oX/Xhmf4+ZISgs9N04/wpA3rNvtQnxsVOdNoTQ6ODJ0CtOPA4AkwRTd+N03knmdVtR9ZC+oGR3QSJlh8DcdgFTi+vZJK4S9jzqKoTUfng1XOpBMLQlQAFLh/64CEI8FG5n/BW1mHAtnjBXBxzir6MM+CM6jQjcRVr52h6QVwjvkdAeUIv25X4kPym5CcougIMcrawAkYbdRxAovp1ffODJsCXb0k5mQYSc9i+r1kmC/Mtxd5bIgbZGOyY9+npUGm2tXohonQg5SeaJyMoB01AjyRyy38f0NiiNzGbYuOrBjLla6r3uQsORAILoJ9TXWKCVR2sz4Pf1SfOCNaeB0V/iZWi0VL4TOvt3klcQXADacwJUoW6Pp53WYXm5RJIgqiWcrqwrmWA848JVQB9PtaAnLxEvxUcgUH4e+pIQ9/aLhQU4WyC9PWDrC/lGk/ZFlx9WNF90I9R75D7uwPF5lOCc/fSJYAWL8L9uvZOjjF1dkfxnQW48aADkFcl0WXJxDh4IdwWn7I2FWbFIYnihukX4Aus/WLDmQ0fv+elB4lAQ6VBD/3vGmW67N0IfANDIqamAztYQACdg5zsIaVkBqFdP2R8fO6qQtPjnjWxNTRJjlosL0QJm6rL1wDx0MD2C5d7kYIImMSQfGf6OXui0wTIGjejQmcWGW7uVHVPj0OOIKAbL09KytuAUpC5cZ6zdug6zToqQgLCB4x0b7xQHWMBWrDMfovziengQ0oU5qZcj/snu9E93A0+lpInYJNFiVp8s273cFH5K5LJAbSgf2lDTPE4z6eSPcvjYxU3WESOPQOlek2xU34UCGEDpVsdOrAGQs8Cclm6RxTEHlEFWU5jCrfE2N30aOEcFQJi6m+Yaq0ssr533B9+XQgOiYe2/yXvjc9vW++4Jfa33qoazl6E5Jn85pyaSe9yAmuMqXRYxRBxzfaXmXQJOrXPO/0YsZ4HGUKwZNB0oc9iYT4/CV0HdWra+EE9sWEpCdv3004L4VeY4+8/xsgBS1ZQ11shB2Wl/1EkZcnwdu0+lahz/izu67b5xQ1DopG6vwSXrDpSC0m1GvJcErJNA5YO+kOfpszoALEHtbjH1AY/vvd/uwLXSRDwmEmUsFeHm03fjBwcZ7TtNXnZYcCCKDmUbu7DJWbZDiw1n+c0O8sFWSAK56uxrCOFIv0/dB3Zry3L+Ya6LN8FE3MT7Al7txO6oTO1GX6PII9/hUuTZte2s/21KiJDhkSVnSR69KAHHrAnfUprHQhZg2fZaZ0B4MwhwP/UUbPxYdzov2JS6XR3706yM2/VrGh2f3Zin7GUVqWk0JIBs1rr3fF4dLhVnIgUtnJxnIBB80By96LxtB8WdwQ4e9uEhAW2S3Xof4M+1aeJxvhzJDuBHK/Y3vgZwOQNLSHOgymwDzVaYXwJk6tH1OcvDZiKz3Jva1j83GP2vN/cIznY3Ej5Yxp7DdLr8ajejFrpl2tp0tOgBxa4r8y91SMMiyDRzHsThJhGIxZ4cMEznbhHOR3zDx+2XNCk/FkRQMd61pk0K5veVqT/obemE2/CRYWuZ5aVZ/oc5rZPZp+o73eB25s441wUkNqmnta6MARL6/eJ2mslWq4bssstqbF+lwdMujnWFSz7RPIK88qLw4xC7Rp7EXEFS6IMQnNFyqOHu+fuiyulzHj7wnegKQrV4JkMrvHLxlPOkTd80uE62qwrNorNWSq11CKeFd3nx6EdIz+x49V0gF70zPVNkVRkLysq3xDoinYgzy6KpRTwi5lu1LuWxFraSs9dnmKUhTEHsFjlBapPJyTdHOFGuPK+SN/jwjUlPo8gIWhR2xLRIPqFFwDe6nlbTFvOupWVl1UhUeVYWrW8UL15AUxC0rkYFFRHarsG3v8Okv92WxtItQ4p0QwmnsiKWIlJ3i2cdluAIXVwVXXSC5u7Y+aO4VMq8+7jUiXOashn9TiWSco5f7XZpHdDk+ngAy2Un2q9elExjHFYVOc1KWXQdqPA+aFBwIk471JvdxQL/DnSYi8TNzILT3vUFH5tevC2U+z+agKg4C8lFfeTlYV3mGGz1Sq+9yH7MUBLBDCELCSrnh9aNj/VsAuayMFataiZ9N2fDkpKIqbAqFiw/Ej7HtffCYtgmBVA9uFdaVctxJTsz+P/Ag23j/jntHhEWXuId/vheKDvNv3vR8e+tVJzYCoPOA9Vqz8q434uH5X8pBZryYfn0Na/qrHBexWanMgaxTnG4y+eC4OxcvHs1/eVYxseT/RMtyUYBAOy7ffpXLa1cEiZ3b6pSTUYIAACAASURBVO4HW1toTy2fSs0pyppOBu0gzGszZ0rqa4PVzCUt8bdQdnQF5wbdwjIHOOBD5jWpvBQrDM1DTn8DH1c0CiGLma/oEn7NSv3sdLdElJlVJRnaQdgGtRmbuiu05+iZqdu+Qq96MxsEOaQgx7yQTkyCAMKJpTf2cNwbL/2h2pLUiUaZXBz9zu82Gsow+4Dli5YK+C38LHuyu9uIrES6c7AxWkrX5yFr4piFQLRgrGPlfXd8ktR7LojDtXvonbfQa637Kq/ad/qCc8DYJ231rKiOBaa5r0ICX04k0yqRXWL7pWNlWywll7LxrYKhVkY8KZJ+6V42VgjQKfUMk8nqNGnnsNqp/MGvQ2RW3c5hvskTIsERNjMH7VmHPHy7gZboBwoReUxns91d2dZ7bb2V1dDWTQ1O5jnO8WB6kODjX6xhP2Pf8Qf/LkSGGqslVEqR7QGTAyGudKMgY89MBcJ4rq3REv1MVjR6ien3LaHljVwQ2n12sl2qqEuPnrEK6QBnKl3mE89fAJlZ69+DyGiOLzYeGWbPNFt4VEr2GpK/LOvpPO7rAyGdluhzfMC2B0j7VeL27lY3CSCN7LgjgTy8dR5frNOVrQ/3+w2QKY/5dyAyCvpc/Zt1s8qsUjvqjoA3kP1VHAhBxiHTzVIgBZnzgG0UQn2pLhjH/YE/AAECXvfuoq89z51/TjX9xcKZ5xYZ+hkgVXN47/LrQAI2y7ImZRZsFnazd/wK/4mhpMhOK58AAi1PemvRd6B5RcoffsuFWG5bJZpOGn8KfxUgjjtEleljP476MoAwK4xKaNYGQQniCSAw6/NtgdbIe/T7X2a38DN+50gHKP9VgCQuQ6fRjZKnU/HmYVZQE7V5BgjcBHgPRAYSF7GbP92djApU5KCl0EZTujepaCpPtlW2Wp09iSJtlDYtLtgzQNCT4S4eqBroI/0KdTJlHaZJXhsoMVfA4zMwMVlRoSKspiBhMp/ipVo+a3MS5T4BCA67CzlaQ32k7ZcmuCxDvLGst+XHk69X7Ov5UZYKemt+mkSVe+woOvOD5YvSiRnTC0DCbYX6uYiO0Wn6zT5lpXaiy7oASH7bsJiGRkXj/1CqUorjc7nD/S5Xe2cChBZ8q1e0ZhMHvz0iv+8ziysACYu9+HBx+SlrsExIB3kjjqw4Ca1huu02bdohVtFRIuYFMzPOUPwTjPuEY6Ssq6Cnd83C+GvGw76P4oPwOGjlR1oG4mSZ9rQjIkXIc8T7DBC/Tq1gm+hCVRz5uxhZX/m53d+Uo2eFRr0b52xuJ1d/seROy4IKaa9fkxY5ph0RH5fL2M8ACTdNmWZsfQI4jxPT/S9d4JbBbTlLdPc39dHrOgJpIsSNCpAfrf5zum3XCdVChLhnK2KZBsUpYAMjm7q/pj70oQxZeCUap56C7FOqJSr2LwQRCHWVTCAB5ImQOmhpcHoZs0REGGWlAUBoS1znKRE1XimLQ3+/C5KRpqxf7YJ4yr93n6N4PqrtXuR/UvQifdWCLU0eijCxbq9YzWrvj5/8BogtujL/dLL+XxZVmU2aPM/dzbTX4mIrfpskfkmLIcIHSPQyvaaC8NKHCWfTubeyJlzwBRCiXYpLJV5K5+wPyp9ORYa936d9/QzPiymInxZGpIP62+rZKVbI+SygoO74lbOnnyeCJ8JU7itS+gQIdYxkMfghuI/7/8plFweu+nwlnJaCcFmTMr6TV31GIIKcBdT8LtCkq56XxKEeADFk1dXVAm+WVrT/g5dfTEHC/GgXpQ445+hswcvf9TY17FMJNqum4Jfk20yqt/WWi+666wUh0WD+d/DBeC8Gi52PWU5QktgmA6SEZJY40Wb8n3PVFXwaR1lvmxrIy+cXjCu3ev9fuvoa2dEApl/YAjcyVbncRYmng51khH2SX+ViXkz6FCbo9usS2IX/pfCKVYTJWG554vp6pTVxDtWB2Jeb3jXmzs6f0mCflFW4ApfO1ZDA+fLanlocM1q8/y/1Bu/vGUws2D8POiSUwADIDg3ShjyqgejvEvUv79Hn5A+FmSMZI7NKClsXqPnsbeWv6yF540Kf11zvf1c1dcOCUauzubHSjuEDGl47xGQWJ/2mJLfyxtl+MEE3YaOtqJlUpakNqH+yieDTi3lXUftvyQp7+Hg5nZghI8uHY9nVw3oyl+RgzB11RfeUhvcrhdF21zViPEhsQFVyYLplmv7ilVdeKX480yyqSne4zYhxtfd6heft0jXZCyFRe5cEOisBASTEOwnNpOqY6cxT8dvKwnmFQR7gcVx2CvsLWv1VvNW9q8ZX70nSm5BfVQk1NxI+muBHRv8fAcgpMqhIdJKZDS/768r89Xp8TEck3qC/B8wQIpO/HoUzBR9WFpRu6T8CkAMd6YWjXddihvhbwWYVDklIC1jTON1cv34U2BWM8e0z9gBI+JD6yN9ECDPwfZJ3bVoLKPNqqVZxtwvxRoxT2nn/1hS9i/ExcCGWTw8OV0D7z2n8lo/q25EYl4UPRMIOuRIgNJqScN8hxAHvvxlgEUx7g09OungRW7//l/bz7v0cUvbXMHBU8+e2g3mZpP/aVta34AOoYGVJhQbLQDXJNMX8RarJVhal46HVdK7/jUX3lTuqbZqwPQiV6NrtNytYTTYNFNnzJV346LL9k/sVu1kU/p2QPrd9qd6fYRJU5ixRj/pbWc07SCZAgLXJABKogmLwuin80dW/OQ9SGMMOjmqadl6h/9tsZlCppgpLTeCAti23Xw2wnPANe0mD1YeNm/P+7gGZU/srI2ZsBHv74A3Y+/3v0/2nh1kKD7gBWt/C4m2LO8UbNAvEYiCYnjf6Pu506Tz6Po3uraFvTTq45L6oufhPH2bf2fwgaP49bEyz4yE9zzbpQYal1N+zfiivMspAJt7Gfeq+B2zJ/bVsRZOelkxvBfqqMD/zYXHa8P6fnYcpBy/im3jeL4IRtM0oX8+1Xver99b+kcO3VTFXQX0gw/PGSao8544Saruo2oONeinf3Y582LQQ0OdPxClQGH41fNxZ1SFUUZn7UUmwZheFSTszeyEIguezYYGpMolJH3MULm+rQqz0DH2GgKxzAB6P+XEZ85vyo46XpScODaX/PXDYH83YcFHzankLh4FcdEHXwT7ilLLaiA4rXrDpCv2+AR2AICRP3d2x6Op/Lb8w3VFF268ECLK0Esr5yaVNY1NDnUkTOpCHCv6T2jek6fJfUGqAV7Jllg2xHP51/tvPoVwxVU4ZZICjq6InzGOSu/U9oI/kVSmwA9nHNTte3nkxoduf12rwTjhraWcOxPwnI6xz3OKownGqFzzbAtNVmkC9yc4D7M4r8jPGDoKWmun9F9bukusYZfLHfml14W/hI/OO20XwgJXNEbpKvksB8lGOLvQx27Yo25OEiq9v9x55ZRi5TRL6envjJpbDg69Cku9oLS50Vs2vJB9RndudnvPAA+UpcZpgTFW5+F9ACIk+1xlzLJ8xa1uusd8XKMaxKpGTQQdnr8AHnHyg1izcFwHyKreSOUQ8rupi+MP7pEjZHumU1PH8lvstB9IWDCkzYPEXRFfy0q7c4LwC5OU5eEaFgp1IpD+vymuCwsKtPtRWLb807FXFqeqwJtXeC5C26uzzfR0gT8bNccwJv9yy/XHh0VRUFkI8dXS//aRROBCS67s0H3SoyemaGR7J1koSV77JSNl2EhR4Rg8o8h2A+9vS7m1tO9SZExR/SEj1l3ogTr5mMrCXnytLiYB5KlERqmt4EEN4P1bjZTYm+eytl83rXJynzYatvXAaw/W7vODXxvYgw3WB+RpxK/ZigXTpdpujB/kOfSq/erme9f2RZL1uOoHxQgvtgspekJ+Uxv/pqrOuwOQ0Y2hQxYN8vb3Ce8H8QehxIO78fyY7FzNl/FtWsUC9XS9NXhjnTXUao6sM6up4EPzDJtHlQxU8+Z1nQuTWxj8jkrkyp5kuSW/CpTPikQutIrANqtOufjW60gOIxZ8h7tP27RK47oz7QzqyzE3VS6V1fP33NW0hJ4ZfEDhP3Ua6UREqqBohVkKfoVEhBYCG9Bdlp/vq3r/lb7hqT3yUgrpJ3Jxqjh51shwVD9LRZ1iVNHfAUPy3cpGgu1aI70DwaZ7lGlbRigj7FRs+6HQ3d/m7GgGkoc8w3JTmReFS0W9ARCCj1DQVSWPwb1Ahcmomw71JDzfqtCJVABLQ1160AnQsgszd/DqhW72d1wBJxEv1QpMIIL5Ilqdb7WlCq+JCVADisElPq3ZH4Y/twEHLV3HX2DmNh5jlBOQhr1EvF++pAoBYkUh2CtvtLerp3DhOBSAFM4tdLSsifVJ+W3vdR5oIU1t46vXkNd62oKBxnZ6pjnrQy2N96i6e4R+iIPF9CwB9fD9ANizCcmolnkTdAPMWHX9kkcMTD8aJY6zDd2RWTOXbUv7uOEm6IegVvPXqGttwNQDiUS2NbTW3kgRruxZc+lV2yTOT8lCHSsLvyJ7tAVb4WZEolWJ9DX8LHJByQdreDJCKmZikWOFhljJcuy5Qb5zOXZe5kLbCzOxSztvAGrmBlOq+7HaXF3Tlz0ED4W5yoiwNgDTsYDfFAo8gWv3QiyTppiji41jNPLiQLiiROSPqnmSdX5h3acOPhCG4h0LlG0Cxxe9Lme/OYAZpAMRhLjirMJTXlZO2D6nR/dp1VfvX1f5d4eP/Zwj0gsWarSN4IgkXvdZLu7D9ni/KsK5ARHYPKdj6sGbm7LL++SyqgCHE6KUjRAMgGUviNo29DW/a704104FLjPLsddOvchhdOvGar+fvrwVCH1arcRTn93oAqkcOW1xy+0qDahKRCIs2abyvATOX7uZUmfInxVdjzx55CDkoEjYxVV0GEHv9nqVH6JsWAl+TQhQPp1EJEJD+zKOkXUbH9hUgBouw2rh/9uC5p4+r1kQ2H9fvj9v64gLEidVynO9Jbnm6rMonNxntDJENyqFVHUn95MTjm2EVIOU78IEB4rGX4ERn8D1jyzLuPEgeDHTZIz87n1lBLtHJsGV7KBe6DPXdPRbrwYclhHSKqT6axFeAVCwAiJqjoen2jmwRSYaaxP9YGhSH1ntlSDfcGdLkl/S0H/uJtFyGgNwyTSv7EkAAGcNXgHQk17eqo6H+9pYLnEyZbxIE55aGhtm9rO/x42a0xIFYFrj80w4sTUvzse/tclIDQo+cHri6AJAElMteARKQFKSuy/1cLt/KEzzuMtmEMI/JP4rPblqYcSuxaCeA4wK/Ed9HGUMlAaaFF7yS3YlRRJ4DJCNqR0Gjsvx+gAB6KUUmKyKPDtNKl68iq5+2KyKqt6+XbrKBZBQhnXqs2xQgG28pyrb4JN7ZuRpDxAuEZpDIbFFoDiyN1HmtKkT1NDxUb/7i3HMTIoQMkOfW7wtALBICGI23LSM+akCk0kMlNLeS9nBEbKoCux9111F+wW1wGgCzVB1BCBkgz0HWC0CwIlZS1ud/N//hPPAdpTQZq67wRKkd+lGHrWi7jfvvX05GzIIRQgfIk6t+AUhDYuSmrB+7EZgLO6FnJ1v922f9ya5bwdXrHG+Sy7YJXUhdytX8ha7jgY0/s6gF00baqqCW1ejvsowB4pAR0iymOwkAEucNDLNv2BbjZHMJceW02rVTlUCjeav00mOJ/5PYIFZtGz99YQDkx9u8ACQjOXoUyF9JAUKA3mMVPZHoNgm1siA/FymU3mQj7Ec/138G/v7HYiq+NsjkgAQdgHw3MF4AUuCgyWiPxG5LJKonPijfmH5IiPg1WfXKx+a2VtSJe7Sp1gbc3p6K70Mo/yh7hOpYFW4MAYIzFlGljQAS4SpL0jZDiqG672yQlKHziZ6zwzDqqA+8fSzgvdjYx3iP04q5+fVxusZzISyAfDEengFiELZPR6gIH/w1o9EolDUgx7usmWY1Mdd8xsurvEa5hvvKiLnSXTxC4DmnoLTlKsiIIlqVyMDtDBCLVHkDMcA3gjlppQZ9XY3N+VKiLFS6XyU9b7m9QyHGeGLQm83qzz7T0wGGHw8gn1nIM0A8UqenunqqIIG9Qvw/lWsWUYCDDGQbH34twnoLOPZlWizdBwghRMd54mZpAeTTVj4DpCInJCsr1iYRy+PKOawmXqheLgOIQoQV3dUZh69dJIniFI7bYbll56uIBODengHSkBBjWxVTJeU02/K7FEvvh0vn4YTfUFeJuJeqmtrUOcoLHJdNLqO/RixFCyDbZ5hjX3x50hnf6HJTH35hicFtUXXq3/MhaNK6ixdeHaMqf6XTaAoyWsAyeIade0rWg6CE+O/f7JyNcO5ZPXuQrLQYhHrKHfOJ3ZfW3t+0RFCAw4zWtPLFC6+e5HKv6/99ZBpahQOnM1LgEh5fwwBxp7qqPXqQDQZI0zbG+bL1fP7N8RW5jBZ1J1CnKYe9zG2IqAuiaJz5Esv+o2F2QwQBknCAIFyhoA2QTYuspCDauHW1M+SWkzNGzDZgY/rrsHEF/brpEaY34SxO+AqoLAgQg5xPp2yO7QV5gLQ0WjRPFRRpVXrXM0u4I/aSRHznhl42mlDeNnC6QQCJdxQgFgFIVk7S63UOhFnqjV35SJm+NpIuGPK4JiHf040cb9de9m3jQhBAygkg8X4dQEjSHY2axJoPGdf96uRFTJUFjwsaaKZtQr67YHq2XLG9zVwlHEQ2pvHtADHn+3oCiEfeX1FuuDlaBFLLMY5ICyyWt21dngl+Zthgc13HgydqLnAclynOMZyq+i0YLMS6n2/rCSBJDSC0YmahxKM2Dxn2VmmaN165KLRvDIMpUXqLF3iOx6xJvL3zileo/7OCuR+AlFPR/wgQjlhadPsE7KBXRJuoMJTDM20DYFuiSQ83NvP+tYfnadG9PiQIZi5IOn5p85r/OwBpJxLkE0AqUmWJEw6MPyjAEneLJULqAsn2w6q7+ffhMfN/t2//Zx7daMmRvGBl9C+uJezvmoPxAEC2p1UKzxHKESCd7EH6kXiw6wnu3fq6oO3uWKn2trRrtLxlC/tkd0XM+1XkTXLzH0456Bmro2/r2ZYA0p4yeL9vKnjNZu0L96VRARIuoINUJtcAYEtjujHv2i5tdM3gpt4k1xR/UM7SHZFk8o8zFeSqlg2Iv54A0pBzn1c1mLEUJPKkX0BmvPsby9dJv4KczQbtVb++5dvvX3kBIE8ZHU5bqxBAPA0gjhi15Lu+rksQzFwafg6S0/3+d3Q06YGV8m0LpvgX1CH29VO+F14Zi3AAXoQbUOJzhwCSUIAERFUgLKsPIL/eCrhqgQuQklRmIFzeNe1aumu2ZABMKwdWF0j5zt2eR+NNIwXIQWHj/6q7si3HdRAY7Xv+/3PH6W0SWwsgZCe5b/dMd8dLCSigSsQJgJQhQNRAl6ZMCzz126SBRPJ5VNNmuvbYUvaI05gT87uyvHzuEhV4YNhTmOLZoM3TBwjJPYCYIUDCYNzUs2yntuNDIQ2LRMSYwuzMVcUxarwf7OeKcs8NDkcxW3u4Octv72Z4z6by3etvcKIBpNJvk46SbuRO0fAEkDjwrxCcm7F5lGHFipZTdd5agyW3Jjei6tPnZqGXLzMVLUrgkZaD6CvZ+rSDR6Q+hqDQ5MkAadBOTwBxN1QjxHlekjf0S9sfukIeVDc9MPGf3PjwAb0zOjdy95y88xBWFpcddRdLUiQ2YQM89TEUTsoQ+OTSgc9/gIjhibj/2xNPUIyOkdg5Ll75PA87qObw7AN1sj+9QWqFrMmtgigEKVrkK/CT3fDSMyOA1H/jf4DoIX+b+JY70yDDSt3jQtpRilU43Xh78BhueMSrUytcrwOuEGRoXSbBBJAmPdIpQ8IAtLXpJfn3gpWhvLLgoyDV4Mur/i1UIxLdMLrx9ndoBwDJDC4/U0V5WNRrycQp0toJr/AA6WS2Cb28WDq7D/8BosYbPQrkSnMnraOXXgmxvx1mdPoEtvJcq5ntYn9l4YEqyh3KIqShjmDHewi1154AkEKJbW6c9tn9A/gDyJ+AXyHl00rTHC/qGVbuv4TP4xseUBmHZcb2A0su6S4rPFCxA9tqqZuZW1DsSxwA6T7RZjfEguqi+HIz/gCSAClDVwXTTLXRQzdx180LcmhneI69WaCQjwgXFR4odETs2H89fEC3BzQDQAY9Bm1xK4Wppkf5tzDld6hz1IxazbTRS+8exnbhmwFTMdScHmSQo1i0VTiDBzKz0uhTw00t1whwPDDkle6C25hKTYt4L+RtmylSL1s7njqaqrkyLNMPTn74Nx2DUIpUs+v3Bm5QLRin1y1m74pH0ggV/dw0QMYpQUClw71KMeJyJc+ijT7isFy/JaRGvIRmWFyB+qvJ2fjByVp5zJQVnlppMBYusfaIxwBJVGnNhNdwdB3/QmySpeiC/rmLQtH88TL8ko50OIMdOtUcPjhZK43pBlrCH24QekbwUvxDgEA4F4Phvnq1tcMCVM0DJA+ys9wvQcrw7Q+zQ5Ua2t5r6lOA6nPGxS2c0RVF66iecjp8/DOzANHkPbWM7747NIcWplOsOKCCBl3CMEqfxKxYfLKzjm0A0wLHmFpp1AA7pFtxDKmWS/eiTALE0N2ZDH6pzeEhaigdAQwRHrq/Vw/f/jSp9Zun9TjGBQwja4XrltPgUVcbNoJnzAgFEGjOrHg06iwhV2ocsJFLrSF2xYDU8HuqOdNBNS3HMSxgFGPlgVSF4INHIF6EnwMI9IlKi3ixPVIxXlP6y57u3SF7JJZq/rge77jqdTIkreVdGUaFueQjdZEbHo5Q9dQn2+nLZ3qqURhnEoGAzxosrdw+Lg+B1y3U8KJt78hQqIPHMglZIercQXrF2DDX6MXyILkmmY1gXHVQiFDuZ/RkAr5wsORy4kcqblMv2FqNcGMKPU5VbIfk1cMK+YWzC6vw0YoCgwDEWHoQLOHxM5stBW6NE7mQ/W1PAwdImCp2HJv6jFvmjKrGcIyd42XYJHwNlnkNPpqyp93wYRlzqxxPMLluzKKh8rSvMPc6O2WBX0xNBZBatWv57CDDKlMAwJBlZ2MQEEAcydAJ09xr8rM6nNMx9yRZksJTfGB6KPIXx4UJIGGy2hF8KphqDUAM5E/JZpwYB5BCtgQE4aMzVdjrvlu+jrkoNB3dxCK8PdAMbxFsqQuQAqY7/eSBN3gKOpzu+gqZeimd0vBVGkyP72q8kWt0MzWS3smuGHXkRSaKQCQWcxOEl+PLedFPsfzk0Bac6h3eheKuRUgAk8rfFM3ugYwDSKLf0DIzvtSZ/Y2FcdyKKvmWWMhuRBXz8qa5/jivhHbv0mzPN/NN4c3rSEHfQddWj90d2OMKZIdAM7flCKZnRfvFZdSR94rfBxBTryLCxy5XUX2ACKhj07R0uOFtvQZmLkvauWpn3APR5Peivx3bpWc7i4fhNFrXxgfbvhHvdp6/ypOtj8MpnLqCBBa6OJKnj+SAHm2zGHHgNe7lmVLAaNBfQHSVIpWebcODkda99yvzZ2OEypykYkivEKLfhzx+Nyxkgc1xC9+9gSb1Eb0XZgnyjsxGAJqALwULUZKBwOpX2O2imdMftK9jqHyfCMFR9vVBZHi6LYb7HtDSe7ZEr2XOaLJwWPQZvskIi5Ka6F1qa4g934i3I9MmQ2QzejBW5n14VMKUmWn61vGRp1ZpSn/URAEBkhh4IYmt+Sx5Jo/Lagl8LsTx84rEwTZPmu2TzdKc08KqC49qeNstyKG+SzXVRMi2Vtlh2QdIo3IW0yV6hddMSNUn626RPlgxz/AimKY8Xgnw1B1gRyAp2pwSp4UVHh77fFFNPyU4V9MoXwbDFAkGEGrbIc78km1wXY49Ti1Do0tNMvTajskpQ0wMFJrm7rSzGXc9+k3zdpGTySuVeaof1thUNgOANO0zibVqL4QE/GyU/HHJ7vqiTLP55ja3h/gc+iNUUKaQVxz7L0a7X2c5pUO7k4+dfYyXNElNKl4iXCM8bDTcA1MnicmXkwoK4riANp998gf58t4Lbskeg5pV0zIASHlicIo4fLQbSZxr5ncfqdyJISfdYar8AGpPJmAirBGBrOvQnCamuV4A8nP+6JJVzeTekmuR7pikxrZAFDhK6YkH2xIna5cevKYF3bZgdx/Dk+26jwEEoUppoGOvBZgIa/hp5/vZ5IzKjWwSX9+OdPnh5xqjsxPm6P0tC4882tpL+w413DwIIBrZzmb1Xu+rOva3XV9HAtRUnEcMGynwGFgGxnkN72mFfpBJM/OOYGZYSE/LrwcSOAVZO3rw+RcnKhBVZTTcKd7roj+S2O9M7XhaPzMJZFhyaDH4pxr0PB3gETbPzki3Z5a3ZbuDQAmcgKv+DPwZmQn62WNsphTnRM5AUn7QltpFazdDYfHgw41uuACdXAqSAyTAlZX3Aghgejhh8NHWLRaWNMKqQcdej3BlhoebGmxQE70DR57gUpgD0AHjfIFS9mn4lSU9x1oMEIgE9IhIfD5RO8YQ5UaabVOAY893ch5WeIz2dYacq5nYwEvkCS6DWiuCdnML9ErCOGAGcj9lKUCgG4yqbaoqXlvJBZEqBaKQ9i6CdMV1eOHhBw3bIedqqHNuxx9GNBgLqsaU0G5uBhIuAvDyF/JowUKACMz6W1Q5efl6F6Quu+0GhUmVykST8Pfc8yaeolICOk6GnGuemnMP1PHEG8qizkMzbAMkXDzgW0tyK2QdQApledq6GGN4EMu1TkwQiDAP46TbA1Umm2Bvp8FjbPc25FzT3IqqJfZP+mHv8IsyNBE2QKhnSNYQXt18i74YIFTpDbpYV6SdntQdb0YdBuBxkglzIoKsp6G4VvnDIBWOsLOrwM44K6DB1QaY5/UagJC1Bbpnkb6zZ1ieCI/CCw8f51XftJsTASnEZeWA+94SfI4F4FCEg6RPsvoY1VgQdBFAVuDD4/6i4FAyWb4MBeUyMmWRI9OfWaIr7fZLEAU+xwLsaWrYJTdnDAcOGlLeMQAAFQ1JREFU2ItSLEmVb2rjI+E2OtQqJMd0P7tTBLkaZp3OxCQGs3+zPXxeLsJq9AK7UT0TKdUBycIiPZ6Hj0p96pm9QFaMXIG5jDHnmhC24JBqzDABxA60YyzsCxnoGReJR2Ez21rZB6FJyJI6AIpoI4EEiPLctyjyyE5XdyLjhClBXAKQYx7YKZOANyDCvjfANmwDiT65ky5KYMHHqANwbPclfoCwLkPhpGAl6TrUjChsYqpBdH+s28BkzhP07ztsw2v/ppmdZ/3yYUW8ywth4zdRD0+D4HW5b5QEozPQuAYzs0wYedogzygVATOIp2GVigcK2Wj46/aSba2f5n1gZKocAexpKWp5WsDEFb8Mq2Uz9qgrVuJILE/96QyVeK6mkwL2hQT4zwtipDsEEnkeQB4YycRcy0KmnQ4ZVmAak/iFaGK/Ib3iw0aLS3cUgyBvwhMDfUmY5771Y/EuIGNjgpXyCoo49CH9XbefBBCALkT1KyZBeriaOuJde4aF/w5J1U/ldsmIp6E8LTFnpmb32CBXYPkeGCAk5e+Q062chZDvhfftMAHhxAWTqM3czOWJvqWjK5y22mNXvz1Ij8F7mPOtbyab5X5fw5WDrszAsApdv4LQWGgOeCFO/tbd3dPna1Bx0yjPSYsJW7fAoPf4hY60xIeunV39F3IUCID4Ob2YTjXmeaZNENuGDdgpOEcgWYcm4B33d/4UxMwWMEGwatX96DTOn7cFEW+6WgYQsKovNYYoIOoUPEeWzGN3Lxr6SXwkQMKUi0Syx6rSL7sR7ca5bfv4acrKFwNAOgvOPPlLBtLHhgEg+sbxierzQLJ7Q/Diks8M29pTotMZ3Nk3wVMdf1sHEPjerXfMJcjLEZEZACLZpjoWnp9LPmnKCPWHYdsqIpNLklcJWOwEoiX8kvKkqGufiwIjRBDSLAstLN8KIF/LJNl/DkDMenPe5WOJ+/dQw990tRQgCO2G5DhLEA/k08AAETfez/nUFvXzcovcu0a/7tzVYZu2wBsakce505M34UkGscPrKkSAiLMA0hxvfO8MK73nl+xvnKtBVCy0VqdhAcho04BoEDvae8/Ap3opQFYsX7N/1AckWP2pdjUKC5kGkHCfaCYReUEfeDKs18fq3xggCK7vko98/686SD0qWb5EhAJHqoFx8hUYG9AUOTisV+4eAZD7+QC5vXXBnsktwtNyQIfFB8rd2zE9uDAlyUWASIBXlvqtARLfGR8vyUh5w+832vqoBj2FsK8MLKnRaBTD4SixMBtABHRhzEGjplwGkPTO+PCzPtpXFufNoOcQy+XqxlOECDvYWkPuANkpHQpNBEi8r+2k35D7nln59ynR9YcV583TR2JypTzr3gUdqAo42l8kRQ8gO25SgFnucF83i0W5EsdlEs0wZvJ2DJYc9wXM/Hafn1ILwrxBtswM8eA46AwrLBAAKcsAUgaZIr+YAakJ8m4twrHYbvNxFkyu1E2NPNlQnCOIfGGk/lsdyucE/qXNfcE+CJ1t8EtcAGjUy5uVShAqpzVOnlEtNcNF0XuIfgYhkKajJsGQbwxQvjpCT3N1u4DmzWtM+ihNkPA5gyXDp2lQkUBadA43ofESSBn1HiQJNT+EAIjn3+qa6RRGnPjIwiaIlZ/TGRxi2uAI28zVxBKRRYSpuUb6vUQaALrqEtzxDBQlU+6PAsVka8R1TZB8/4yxXVCaYZCEbaB6RvQtpM9SJx6/WXDu7YJJk87rp7gOl+nb+EYVuoceVQaT6kjyex1QJiEwbAd94nxEv1EKlOZdyPK2EaIZ4+9kEyR9SuMcpDtlsKPr3p6MEFvO3KJ24AGAfAHL27QfrmesrojzS/TwOZ0PvGA55ArLjQshMl4kc9+hrh24o+hPtazpj72LcPWAvHmzAALpfICY24zXrz4fISuJSw+fRdPAfxhuyz+vOlG9LX1mf8vhMRM/qTYH8K+FMHiY2RAiAqNqMotDmYNONap5M8pQtBRHf2W46skm67Z9VBxZBcgTOd7yIY1B6ACPp8xUF7Zxdbj+wiri0iGu3YHaKxINebnEaPP5b+mzAsgbUFhYePRrCkkaqk69B+r0Go/JJfk0yoohgPLQRLBpknFxTra0jnsKIObj4DF63SNpdr3LQFm/yJthAUQUBiAG9O8gk1iHRFQsL1ziOkrrSVT32iY6zRJe4AuK8RsuA3HodIau5ofIYfwyAsONmRJHjYI2WjBLfsnVAeRSjpdooBUICgpqVgLUrPGHY+f2De7KHaSuC7TBBn+7nUB+6bUBJF0YPKgx2FAWxPVsHx85aIhRgmOMIgk5+msggcYRp0VOYIfXMOaZaHbM2vZwK4xj2pSUmm7G4Ep1VJ7FRlwepwIK0D+o3CcUGxqDDeV2zoe7GHkKIBdlWH6KBSwk5ys9/VpjR0R8ONslu+DZCTUmMz016RX2JIRsmZZfE0Cu4LBae3J8ACkzG7S98kEJrNn9mSVnpQUzHkf+mdVMU/YM+boO/JNuPFfBrq8UU/Q5rFeKqTtfJYaR4ogtCXF6oputn2QdRvDAqGPmHH7KVTNc+9YIR6ol4lUAkdQRBPSbnpETwHAfRPzuDBIiVAeiajspw3ivwS6wIwMk3E7+cEg8qGt07XQO52mN1RdpESMjnaYIfm0WC5GHgwaSEKhTHsCsUgqy8PDgnGU4D2NQmxO211JK8fWRUvsfk876U57ESL5AGXVTeXK3U5Ri+kcXJkFSnOschNGkhzmAAB49cakutCd3psS00Y7ES1pMeoaWmUE8osN7OE9KaZD8It7tXh+YMIgrCuWIcEHlbsIlvImrZaEzWR8xzeRKHvyC1drNkegGWDHd1AsDRzYUcLhvP+uvT66EUjNx/sP3XXt5kUtnNkZj2Ly9/Ws82YbK/WAsPK/qzYP/Fl0sCNc5bZw+G0iQepa+eq0Lhuu3p5ew2Hicll/JZrVv4lBGWD0xHyDVMZgkUoLEUkwS3Nb9fOyJxhuKugKtV+YI+w5vM1PNScOeTTsWM5T+4qtyepRO24EfLSZaqK/jcQjSZ7FaM+VG8CBT0zC/N7jp7sXjZ1fKpgfifBx9WDFwzNi8bmKZdkUndBquaH0BTaLgkMoDDUZtWk0xWkIF9oC2JA4AGoatvq+I5XcMzjdJsgEd8CuIkVckdKoVvUbHHnU/FSDHRzIx7p7WBDq75e6PncVS0vZ5vL/b2xsRB5Z9/Lx65P3b79h+fvuYn8/2/79UyzYwuCnuzj7AnDQt/P9ftIj7PEl8gbbD/nVi2O8He2XklUBcg/2HSCmWbelyBUD2CJlZmNKrSqX3/mxRyk+WO09bTfaxwGzUdwRz9qJLou4rY1ojv/O4qFdO3c8GyGuDyLvbyQBRn4wNG0xiOdK0fbMrIwslwjdGMiVp0etWFDu6qpIWIln4afep4ECTbRNeZx8FEYU28UNIptxXCTWOdEke+bhb3+j6gBcDOKCql8oGvgdEykqAZLT8L/OAhD/9hqqpFY5PgkdZL9rxJheKLtdfl0W2VmBWgCa1v8I69vS01r1Dib5xXr8N6y/WKiXvvd4+Uv7NhT1Iz+2jf0ndrSDGADXI1c68b1RlwTd2vnh3V7lR1SpcUfKdslQnZf3Hs3ZB8H2YA/2POvqKgcEkn6Fd/l4s3dbZqd3Yr6NGf4+gVlpSfz3vWgaVCCmm5d1A1bd3z7EkZ4LFMlmyDa/biwyw9e0DPg4xE/Ikq1DG2QbkXTCfXvdhER6OU6nyJ1aHMxgC/CDpSrmUj+Xzxq+zAJyl/lSO96K6r8wlFc/6GsgNb97yrd3vKue4u09uMT9GBa5tNe5Oywg4Z/KKUaY3CyFhCr97lUeMRkg4x3DTnGagNPEQxH75JlwRnHLXdkwQXlf/MdRhNI+xqBp/Jyeax5VJa7iCJ/+ObU0hLdyvBIiL5HFwuSeY1gcQ2QOIIpSk8Z2jdn3v77imlOkFumoNwNmzZxCax4tbuqbYt7v5ues6je6H7i1ynhdLVJfOL4QDPX8CM7JbsUhklB+4cdNZGoGEEbPSb/2URfcOQF7uel+80YzNyYM9fW3DwFBszuN4l4SQoxRiJh7kCHwArQT4PdHlmQVIL8U6mBR0LNdBtigL9ukHyTbQHaq7ML/kpku7eHotkuKgQP/YGCJqseXXcjlTgzDxaC5RFPh+09IdwNi9NkVIedKb92cbw52JkmMdpjchj3VYr6e16amVp/lXA25Omggg/y8Rpnhn7bRSvYE+qkw0T7l6xKc9+xzwORYJH+MoYlnr9EOQW26o4FBFhWFq0/hBIPnaccSHm9IFv8S3JuJJ5rnsqwEenWMd8JGZdMVZtbDMKSnw+EG1YkJVYTwSNekdYKspofRVZQ8gllAUrNPKDIuNPCLyQibwMdrpYqRh8yqxGWwJkjGpATnKJQVIWwV8uMF1X8JAIfOCfM8QMlwsKzgu9IAPw7j2zBZDCleb5UtyIe3l0RC5sESULDOnbNVApFCdSVX34hSpKFhlLz0VQgCrQfudJ6R0TWBVBnB+jeEXukklU9nWTJ4735sorcBLKUdMxCnMfI4iu+fkbn2ViTsQi7wtJ0IICLMG0dA54IOWurQhYv0Kw6+AVO1tjHX0nBkTlrw27K00Byt/IFHEd5f3PP1EV+kdiKyfywEabUpEIqy4qgZp1s3bHqZgEN9y1IJrHjkZ2xjP7J0gMLk4hojoyqHLmfkPxy8pm7DTJH5KDlkhqP6J874pvW+4CV5o7QsieoJAdUEwAEm8jZiIdH3q/KjGrWaPFrHZLZIj0rl3A4hNdABK8IXPnfYtiMwh5KhBY9jQ0Tk/HBYghkESoBtBum+AMJjBuIRLWMdSHvnEOdEKUaBxThH76ynQM296uKkBkSA4G9oB9EXc3Oa+R++5GPa76VB46ynEle6TH3/R8fyHuWSb8xcWGpn6GNBDLazvcRciTvO1IwAFCMo6vB5CMnqGNQCYaCHoszWZ5K5Z/yoGmTcA1KDy+RNZ/4sOjaT9PSTHSouMHqoQsYXtTo29Z83UoOsgC25diBgzbeKR9G3LbZIUOgE/1bpy2wezZ3lhWeksgPP5c78cC5B9hMoQ6pSv7V1ltEhOMJUHY+jnKOLwk+g9ujxGkkKZQHgKJVbPs0L//ZAsLyzv6J1B9cvRADHDh3pYsLWc20e1HIcymmDQS7aCsHETUFViAqpdy0HKFDf3TJjAPOqArlG+pm96ybTIxDr9Iwfy1bs9fyxAhjzWcd2YueFTafDiu+oGPd2lI48CpUJugh2p6ABJU1zTO3Mv6w9vOopxVqrxAzH+ZCXrLiIPnRc0QMQg3B+fJ/92XuUoww3v1ILBAGOZto+GY1EqXEZFzDODy5paKMkzL9/hlJDdtz2w8Ur5lGZhpSGIBsieUlGj1sKK7TyRp6xuaxr8/ScgqVNuAtc7PiCkJriiUUnDw9hE/vd5DHPzCLtiPfYTSMO3LM6YpguM8S8eILkboNXtnOHxSiliyCoSQxjTzbgEslEV/bAwdhQi6OFL3dA5QRZwPvbuuSFgD9S745SZcYjRIDxAdA/aZS0B0XxQmCBSs9bq3/4Jszr8wPWfxnrLKtPw7gGhu/LPQcRPs7zDqnlBJhI7jcF5gOy9FkyXcEmdgY2HamaZ8G5KpHInWexgMN0M+Pj2AUjNrb5+CIo2FXs87QVjrH//ziY3OJwFZ3M7L5Q3ib6NXj2Jv9iDpgH0FNp8C77BOx67NOix4Al8HI89zy0iMm3dRemz/gaR0k/vHatiFVMyIirdrA7FQwCIauawGRwWDzRSRLmcdxq8fTqrbpFn5i3Locez4RXCZdi0Ppb8KgajIXRWHFC2gVePh6OclRUpvm5q/guQTdHVPBTC3cOcvn+gl9YRpMFX1Ejqo8GHkkNF0ZWdcPjsVrG+zZFZpg2YtXXO5UaGEAcrfSKHStpeSNlbOcnXU9c27fedwQpAjuPbvX24Aw5C6+E3Y2LHgdoG7K7lMYg086xMOJgMoyrnbLlQf0kSr0p/mVnpM6SqIZ3gDCOTijdEcf7/idUVxXojgI1ObIZeThkceSHh4sihe2Wrm2iNVnifwMq8x31hFsKd15zNvYQtlimfpHTn3tGgMTqb1lcjcg93oTp7+e0RQFWlVQ4MlqG/cwg/yfqvPEKkOYjrFzorRnYBZXfnzbAOL7GYMr+1pOaeWeoNk9rv+GBXYmDJ0RTWydVDCKrRYKDeSXKqKbKDSHKUqO0tmzUZk/cad4Z1iHFpxghb0lqQcWo8emLE1PdF9iyRNUg1WBdg5mIQtsR66j7Y/zl0O1BmlPHP7PHsF7gLK96IZG6IJsEgVxILtHg0Iz4MXaa1uweoKzd5vxjWar3hcvqQ5jbF3MMfq3QMl9SMM6/79mjfRLEsUEs08zdBJoNSAJ3mUFUPuOwoGeJIrlcRan6E9KqCDXtJqK8Qvkh1GXxDEv68797tXoMw7rhwXZUAMuwkb+FW08ywQT4LewyOov6ILKIMn0RDqJsOTW7EVQ4tf+jeG76a1xpNZbPmBuCa4uP1bk1lsFCOSF4b1fYJYH/ayuqG4U3ZPGyKEpQrhTW2HZ7PMyc+H3MPryE732ety44F0K2h1rzQYgTZ9O7PLii0JKYaHVC5ycHrAjLeVOyW6wJ+hI3lbzKlr4c2forXGndCkpD9380FdDJr+sME0r44hCT0uTaQ9w6DfCh0OXiAx01i1yvHHOfDPCveVm2D0JgsfTvhYyCqohby5ompDBxYryum+1wo6vevh+EhH5JjGmNAnrBnWAp3/1zCvI+ZXwsU1y40ZwCk9rYb0shMuC12Z8AhxGCZx4x8IqZ/Oid87zawu4pl7EPq8VmGMBgiHdOhPaERN9u7FciyqqEUbkYrEMl7rfXmJdAslUAQUQw3udIAAelC+G4xGYF/X7ZyrcKeRKTBsg+mFDmwUWmZYYe8LwpOTM3bTJgo6yTL4aB3ts0W14tWACEPq0OwDRCY9pbspfcawfDUp4fk8EkoL6X0292LNA4GMkzZGtMzBLopc438X1Si/7zx/uWTUvcv2/T9efkZrYNztvbfdpZ+h46fz98faczB5PT7KX+f/P/z6NtBHnJ6+vj9J1ZiV/LP33L3bR/f9/F9zPdFuhhjyPt/aw6X8XKD/l/5z+UdhClcSvuvHfeWGun3d8BUWPaXVICsYuVzpChz5dbuXiVLNXv6uf7W83g8jczji/4PmUNmzfkLiCoAAAAASUVORK5CYII=\",",
            "    logo: \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAABelBMVEVMaXESDwwBAACzrKEAAAAEBAMAAAAJCAUWEhADAgImIRkQDgwWEhAAAADf18o0IxQMCgcAAAABAQAJCAbonkMBAABiQhsIBgS6s6h/Vinlm0EFAwIBAQCjnJPblkEDAgKYk4oAAAADAwICAgABAQAAAACqczLRjjzKiDoCAQCxeDRuSh+3fDV0TiAAAADEhTjWkT6QYiq9gDnMxbgAAABVORf98uMAAAD57+H78eL/s0v8q0j7qUf/sEr47t//tkz//O3/rUj/+ur//+////H///P+9uf5qEcTDQUKCAbz6twgFSP2p0Y6JxAaGRgrKSbt5NYWDiEgFQk+OzgrHQxrZ2FXOhjn39EUExJ9eHAzMS7tokRQTUjAua4jIR8ODQ3ypUU9KSguHiVjX1lbV1KaaSxKMhV0cGmrpJrY0MTSyr2Ef3dWU02KhX1LMirHwLThmUGhbi5IRkGRjIP69eb/vU+HXCb///1vSjCYZzdfQC3+vWKBfHT3yI5oF8IJAAAAcHRSTlMAJJT++FLuAgH+DysZ4v4INNmOPv60/kn//vxbm/7+c/7QZaSuv/7+/oX+/v79yP7+/v79fvv////////////////////////////////////////////////////////+///////////////////+xTIE9AAAAAlwSFlzAAALEwAACxMBAJqcGAAAEgtJREFUeJztWvdX28rWHcBYEmDTwYTee8/1kUa9WO69YhsbML0k1LSb73//1sgQMJDyXsi9663F/sELLHlma+bMPnuOBqFXvOIVr3jFK17xilf8DmiKotC/CMr6oP/F/uvGhjv/vSGg0JgNGJhoQv/OGFCojUktTC/CwD8+CzRFURRNoQFm5eZvOWNvQTQiX/1jvSMLrag7Ffq/LF5l2qjW6lf/AAeKdNHa1t8xsdS+5GCm//5b3geHo310sn6srZXQ++PdNzUOOBjwJ8L7zJIjNTfTA0tMOBNO+YBpr++k/iQFmqJRXYcD/Pt9a+chzCcmRoEBgHrbxY3Iv1n+EGZgdKz1j00ERaG6ARuE995gGYs8v5XobvelUikYt+3JIV7FsjjUl4D2fqoqUC/eP2p6a2MyM6IsCl5B8AqEQAaLM4QADnm9gpcX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\"",
            "  };",
            "",
            "  /* Habillage du panneau : valeurs par defaut, ecrasees par la configuration",
            "     definie dans le tableau de bord Angon (onglet « Panneau » du jeu). */",
            "  A.CONFIG = {",
            "    logoUrl: \"\",",
            "    titre: \"\",",
            "    slogan: \"\",",
            "    texteConnexion: \"Sign in to save your progress.\",",
            "    texteInscription: \"Join the adventure and save your progress.\",",
            "    couleurPrincipale: \"#b4653a\",",
            "    couleurFond: \"#0c0d10\",",
            "    couleurCarte: \"#16181d\",",
            "    couleurTexte: \"#f3f4f6\",",
            "    seSouvenir: true,",
            "    motDePasseOublie: true,",
            "    conditions: true,",
            "    googleActif: false,",
            "    fondImageUrl: A.ASSETS.pattern,",
            "    fondImageOpacite: 0.07,",
            "    fondImageTaille: 280,",
            "    police: \"Figtree\",",
            "    policeUrl: \"https://fonts.googleapis.com/css2?family=Figtree:wght@400;600;800&display=swap\",",
            "    langueParDefaut: \"en\",",
            "    langues: {},",
            "    textes: {}",
            "  };",
            "",
            "  A.rgb = function (hex) {",
            "    var h = String(hex || \"\").trim().replace(\"#\", \"\");",
            "    if (h.length === 3) { h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2]; }",
            "    if (!/^[0-9a-fA-F]{6}$/.test(h)) { return null; }",
            "    return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];",
            "  };",
            "  A.rgba = function (hex, alpha) {",
            "    var c = A.rgb(hex);",
            "    if (!c) { return hex; }",
            "    return \"rgba(\" + c[0] + \", \" + c[1] + \", \" + c[2] + \", \" + alpha + \")\";",
            "  };",
            "  A.lighten = function (hex, ratio) {",
            "    var c = A.rgb(hex);",
            "    if (!c) { return hex; }",
            "    var f = function (v) { return Math.round(v + (255 - v) * ratio); };",
            "    var hx = function (v) { var t = v.toString(16); return t.length < 2 ? \"0\" + t : t; };",
            "    return \"#\" + hx(f(c[0])) + hx(f(c[1])) + hx(f(c[2]));",
            "  };",
            "",
            "  A.theme = function () {",
            "    var c = A.CONFIG;",
            "    return {",
            "      veil: A.rgba(c.couleurFond, 0.86),",
            "      card: c.couleurCarte,",
            "      border: A.rgba(c.couleurTexte, 0.16),",
            "      text: c.couleurTexte,",
            "      muted: A.rgba(c.couleurTexte, 0.6),",
            "      primary: c.couleurPrincipale,",
            "      danger: \"#e06258\",",
            "      field: c.couleurFond,",
            "      onPrimary: \"#fdf7f0\"",
            "    };",
            "  };",
            "  A.PALETTE = A.theme();",
            "",
            "  /* Police du panneau : chargee depuis la feuille de style configuree. */",
            "  A.loadFont = function () {",
            "    try {",
            "      if (!document.querySelector(\"link[data-angon-heading-font]\")) {",
            "        var headingFont = document.createElement(\"link\");",
            "        headingFont.setAttribute(\"data-angon-heading-font\", \"1\");",
            "        headingFont.rel = \"stylesheet\";",
            "        headingFont.href = \"https://fonts.googleapis.com/css2?family=Fira+Sans:wght@700&display=swap\";",
            "        document.head.appendChild(headingFont);",
            "      }",
            "      var url = A.CONFIG.policeUrl;",
            "      if (!url) { return; }",
            "      var old = document.querySelector(\"link[data-angon-font]\");",
            "      if (old && old.getAttribute(\"href\") === url) { return; }",
            "      if (old && old.parentNode) { old.parentNode.removeChild(old); }",
            "      var link = document.createElement(\"link\");",
            "      link.setAttribute(\"data-angon-font\", \"1\");",
            "      link.rel = \"stylesheet\";",
            "      link.href = url;",
            "      document.head.appendChild(link);",
            "    } catch (e) {}",
            "  };",
            "  A.fontStack = function () {",
            "    var f = A.CONFIG.police;",
            "    return (f ? '\"' + f + '\", ' : \"\") + \"system-ui, -apple-system, sans-serif\";",
            "  };",
            "",
            "  A.headingStyle = function () {",
            "    return \"font-family:&quot;Fira Sans&quot;,system-ui,sans-serif;font-weight:700;letter-spacing:0;line-height:1.2;overflow-wrap:anywhere;\";",
            "  };",
            "  /* Clip the fade to the panel; overscan keeps rotated corners covered on tall and wide screens. */",
            "  A.panelMosaicHtml = function () {",
            "    var C = A.CONFIG;",
            "    if (!C.fondImageUrl) { return \"\"; }",
            "    var fade = \"linear-gradient(to bottom, rgba(255,255,255,1) 0%, rgba(255,255,255,0) 75%)\";",
            "    return '<div aria-hidden=\"true\" data-el=\"panel-mosaic\" style=\"position:absolute;inset:0;overflow:hidden;pointer-events:none;-webkit-mask-image:' + fade + ';mask-image:' + fade + ';\"><div style=\"position:absolute;inset:-100vmax;background-image:url(&quot;' + A.esc(C.fondImageUrl) + '&quot;);background-repeat:repeat;background-position:center;background-size:' + C.fondImageTaille + 'px ' + C.fondImageTaille + 'px;transform:rotate(30deg);opacity:' + C.fondImageOpacite + ';\"></div></div>';",
            "  };",
            "",
            "  /* Recupere l habillage configure dans le tableau de bord (une seule fois). */",
            "  A.ensurePanelConfig = function () {",
            "    if (S.configLoaded) { return Promise.resolve(true); }",
            "    if (A.panelConfigPromise) { return A.panelConfigPromise; }",
            "    A.syncConfig();",
            "    if (!S.apiKey) {",
            "      console.warn(\"[AngonCloud] API key not found. Set ApiKey in the AngonCloud extension parameters (or in the game's global variables). The default theme is used.\");",
            "      return Promise.resolve(false);",
            "    }",
            "    var ctrl = (typeof AbortController !== \"undefined\") ? new AbortController() : null;",
            "    var tid = setTimeout(function () {",
            "      try { if (ctrl) { ctrl.abort(); } } catch (e) {}",
            "    }, 8000);",
            "",
            "    var sysLang = A.detectSystemLang();",
            "    var panelUrl = S.baseUrl + \"/panel?lang=\" + encodeURIComponent(sysLang);",
            "",
            "    A.panelConfigPromise = fetch(panelUrl, {",
            "      headers: { \"x-api-key\": S.apiKey },",
            "      signal: ctrl ? ctrl.signal : undefined,",
            "      cache: \"no-store\"",
            "    })",
            "      .then(function (res) {",
            "        clearTimeout(tid);",
            "        if (!res.ok) {",
            "          console.warn(\"[AngonCloud] /panel returned HTTP \" + res.status + \". Check that the app is published and the API key is valid.\");",
            "          return null;",
            "        }",
            "        return res.json();",
            "      })",
            "      .then(function (json) {",
            "        clearTimeout(tid);",
            "        var p = json && json.panneau;",
            "        if (p) {",
            "          for (var k in A.CONFIG) {",
            "            if (Object.prototype.hasOwnProperty.call(A.CONFIG, k) && p[k] !== undefined && p[k] !== null) {",
            "              A.CONFIG[k] = p[k];",
            "            }",
            "          }",
            "          /* Normalisation motif et logo : anciens URLs ou URLs par defaut basculent sur les assets embarques */",
            "          var fUrl = String(A.CONFIG.fondImageUrl || \"\").trim();",
            "          if (fUrl === \"\" || fUrl === \"none\") {",
            "            A.CONFIG.fondImageUrl = \"\";",
            "          } else if (",
            "            fUrl.indexOf(\"vectorized-pattern\") !== -1 ||",
            "            fUrl.indexOf(\"Frame_3\") !== -1 ||",
            "            fUrl.indexOf(\"9bcd7e5b-bc3b-47c0-a0c2-54188fa274a4\") !== -1",
            "          ) {",
            "            A.CONFIG.fondImageUrl = A.ASSETS.pattern;",
            "          }",
            "          var lUrl = String(A.CONFIG.logoUrl || \"\").trim();",
            "          if (lUrl.indexOf(\"angon-cloud-logo\") !== -1 || lUrl.indexOf(\"Frame_8\") !== -1 || lUrl.indexOf(\"Frame%208\") !== -1) {",
            "            A.CONFIG.logoUrl = A.ASSETS.logo;",
            "          }",
            "          if (p.googleActif !== undefined) { A.CONFIG.googleActif = !!p.googleActif; }",
            "          if (p.langueParDefaut) { A.CONFIG.langueParDefaut = p.langueParDefaut; }",
            "          if (p.langues) { A.CONFIG.langues = p.langues; }",
            "          if (p.textes) { A.CONFIG.textes = p.textes; }",
            "          if (json.jeu && json.jeu.nom && !A.CONFIG.titre) {",
            "            A.CONFIG.titre = json.jeu.nom;",
            "          }",
            "          if ((!A.CONFIG.logoUrl || !A.CONFIG.logoUrl.trim()) && json.jeu && (json.jeu.icon_url || json.jeu.logo_url)) {",
            "            A.CONFIG.logoUrl = json.jeu.icon_url || json.jeu.logo_url;",
            "          }",
            "          S.configLoaded = true;",
            "        }",
            "        A.PALETTE = A.theme();",
            "        A.loadFont();",
            "        return S.configLoaded;",
            "      })",
            "      .catch(function (err) {",
            "        clearTimeout(tid);",
            "        console.warn(\"[AngonCloud] Panel styling unavailable. The default theme is used.\", err);",
            "        return false;",
            "      })",
            "      .then(function (ok) {",
            "        if (!ok) { A.panelConfigPromise = null; }",
            "        return !!ok;",
            "      });",
            "    return A.panelConfigPromise;",
            "  };",
            "",
            "  /* Icones Sharp Color embarquees, recolorees avec la palette Angon Ambre. */",
            "  A.panelIcon = function (name) {",
            "    var stroke = A.rgba(A.CONFIG.couleurTexte, 0.88);",
            "    var fill = A.rgba(A.CONFIG.couleurPrincipale, 0.28);",
            "    var common = 'width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\" style=\"display:block;flex:0 0 auto\"';",
            "    var start = '<svg ' + common + '><g fill=\"none\" stroke-width=\"1.5\">';",
            "    var end = '</g></svg>';",
            "    if (name === \"mail\") return start + '<path fill=\"' + fill + '\" d=\"m3 9.4 18.6-7.1-7 18.7-3.9-7.7z\"/><path stroke=\"' + stroke + '\" d=\"m3 9.4 18.6-7.1-7 18.7-3.9-7.7zm7.7 3.9 4.5-4.5\"/>' + end;",
            "    if (name === \"lock\") return start + '<path fill=\"' + fill + '\" d=\"M20 11H4v11h16z\"/><path stroke=\"' + stroke + '\" d=\"M20 11H4v11h16zM7 11V7a5 5 0 0 1 10 0v4m-5 4v3\"/>' + end;",
            "    if (name === \"user\") return start + '<path fill=\"' + fill + '\" d=\"M10.5 13.5c-3.1 0-6 .8-8.5 2.2v2.8h17v-2.8a17.4 17.4 0 0 0-8.5-2.2\"/><path stroke=\"' + stroke + '\" d=\"M12 18.5H2v-2.8a17.4 17.4 0 0 1 13.5-1.5M6.5 6.3a4.3 4.3 0 1 0 8.5 0 4.3 4.3 0 1 0-8.5 0M23 18.5h-9m4.5-4.5v9\"/>' + end;",
            "    if (name === \"offline\") {",
            "      return '<svg width=\"32\" height=\"32\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" style=\"display:block;\">'",
            "        + '<line x1=\"1\" y1=\"1\" x2=\"23\" y2=\"23\"></line>'",
            "        + '<path d=\"M16.72 11.06A10.94 10.94 0 0 1 19 12.55\"></path>'",
            "        + '<path d=\"M5 12.55a10.94 10.94 0 0 1 5.17-2.39\"></path>'",
            "        + '<path d=\"M10.71 5.05A16 16 0 0 1 22.58 9\"></path>'",
            "        + '<path d=\"M1.42 9a15.91 15.91 0 0 1 4.7-2.88\"></path>'",
            "        + '<path d=\"M8.53 16.11a6 6 0 0 1 6.95 0\"></path>'",
            "        + '<line x1=\"12\" y1=\"20\" x2=\"12.01\" y2=\"20\"></line>'",
            "        + '</svg>';",
            "    }",
            "    if (name === \"refresh\") {",
            "      return '<svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" style=\"display:block;\">'",
            "        + '<path d=\"M21.5 2v6h-6M21.34 15.57a10 10 0 1 1-.57-8.38l5.67-5.19\"/>'",
            "        + '</svg>';",
            "    }",
            "    if (name === \"google\") {",
            "      return '<svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" style=\"display:block;flex:0 0 auto;\">'",
            "        + '<path fill=\"#4285F4\" d=\"M23.745 12.27c0-.7-.06-1.4-.19-2.07H12v4.51h6.6c-.29 1.52-1.14 2.82-2.4 3.68v3.05h3.88c2.27-2.09 3.665-5.17 3.665-9.17z\"/>'",
            "        + '<path fill=\"#34A853\" d=\"M12 24c3.24 0 5.95-1.08 7.93-2.91l-3.88-3.05c-1.08.72-2.45 1.16-4.05 1.16-3.12 0-5.77-2.1-6.72-4.93H1.25v3.15C3.26 21.36 7.33 24 12 24z\"/>'",
            "        + '<path fill=\"#FBBC05\" d=\"M5.28 14.27c-.25-.72-.38-1.49-.38-2.27s.13-1.55.38-2.27V6.58H1.25C.45 8.18 0 10.03 0 12s.45 3.82 1.25 5.42l4.03-3.15z\"/>'",
            "        + '<path fill=\"#EA4335\" d=\"M12 4.75c1.77 0 3.35.61 4.6 1.8l3.42-3.42C17.95 1.19 15.24 0 12 0 7.33 0 3.26 2.64 1.25 6.58l4.03 3.15c.95-2.83 3.6-4.98 6.72-4.98z\"/>'",
            "        + '</svg>';",
            "    }",
            "    return start + '<path fill=\"' + fill + '\" d=\"M2.5 3.5V17l9.5 5 9.5-5V3.5l-.5.1a10 10 0 0 1-8.4-1.2L12 2l-1.1.6a10 10 0 0 1-8.3.9z\"/><path stroke=\"' + stroke + '\" d=\"M2.5 3.5V17l9.5 5 9.5-5V3.5l-.5.1a10 10 0 0 1-8.4-1.2L12 2l-1.1.6a10 10 0 0 1-8.3.9z\"/>' + end;",
            "  };",
            "",
            "  /* Detecte la langue du systeme de l appareil (ex. \"fr\", \"en\", \"es\"). */",
            "  A.detectSystemLang = function () {",
            "    try {",
            "      var loc = (typeof navigator !== \"undefined\" && ((navigator.languages && navigator.languages[0]) || navigator.language)) || \"\";",
            "      if (loc) {",
            "        var code = String(loc).toLowerCase().split(/[-_]/)[0];",
            "        if (code) { return code; }",
            "      }",
            "    } catch (e) {}",
            "    return \"en\";",
            "  };",
            "",
            "  /* Obtient un texte selon la langue systeme avec cascade de repli. */",
            "  A.getText = function (key, fallback) {",
            "    var c = A.CONFIG;",
            "    if (!c) { return fallback !== undefined ? fallback : \"\"; }",
            "    var lang = A.detectSystemLang();",
            "    if (c.langues && c.langues[lang] && c.langues[lang][key]) {",
            "      return c.langues[lang][key];",
            "    }",
            "    if (c.textes && c.textes[key]) {",
            "      return c.textes[key];",
            "    }",
            "    var defLang = c.langueParDefaut || \"en\";",
            "    if (c.langues && c.langues[defLang] && c.langues[defLang][key]) {",
            "      return c.langues[defLang][key];",
            "    }",
            "    if (c.langues && c.langues[\"en\"] && c.langues[\"en\"][key]) {",
            "      return c.langues[\"en\"][key];",
            "    }",
            "    return fallback !== undefined ? fallback : \"\";",
            "  };",
            "",
            "  A.isFrench = function () {",
            "    var lang = A.detectSystemLang();",
            "    if (lang === \"fr\") { return true; }",
            "    var c = A.CONFIG;",
            "    if (c && /connexion|inscri|compte|sauvegard/i.test(c.texteConnexion || \"\")) { return true; }",
            "    return false;",
            "  };",
            "",
            "  /* --- stockage local (tolerant aux environnements sans localStorage) --- */",
            "  A.storageGet = function (k) {",
            "    try { return window.localStorage.getItem(k) || \"\"; } catch (e) { return \"\"; }",
            "  };",
            "  A.storageSet = function (k, v) {",
            "    try { window.localStorage.setItem(k, v); } catch (e) {}",
            "  };",
            "  A.storageDel = function (k) {",
            "    try { window.localStorage.removeItem(k); } catch (e) {}",
            "  };",
            "",
            "  /* --- Gestion du Cache Local des Tables (Offline-First) --- */",
            "  A.cacheKey = function (tableName) {",
            "    var safe = String(tableName || \"\").toLowerCase().replace(/[^a-z0-9_-]/g, \"_\");",
            "    var scope = S.profileId || S.accountId || \"shared\";",
            "    return A.CACHE_PREFIX + scope + \"_\" + safe;",
            "  };",
            "",
            "  A.cacheGet = function (tableName) {",
            "    if (A.getTablePolicy && A.getTablePolicy(tableName) === \"desactive\") {",
            "      return [];",
            "    }",
            "    var raw = A.storageGet(A.cacheKey(tableName));",
            "    if (!raw) { return []; }",
            "    try {",
            "      var arr = JSON.parse(raw);",
            "      return Array.isArray(arr) ? arr : [];",
            "    } catch (e) { return []; }",
            "  };",
            "",
            "  A.cacheSet = function (tableName, rows) {",
            "    if (A.getTablePolicy && A.getTablePolicy(tableName) === \"desactive\") {",
            "      A.storageDel(A.cacheKey(tableName));",
            "      return;",
            "    }",
            "    if (!Array.isArray(rows)) { return; }",
            "    try {",
            "      A.storageSet(A.cacheKey(tableName), JSON.stringify(rows));",
            "    } catch (e) {}",
            "  };",
            "",
            "  A.cacheUpsertRow = function (tableName, row) {",
            "    if (!row || typeof row !== \"object\") { return; }",
            "    var rows = A.cacheGet(tableName);",
            "    var id = String(row.id || \"\");",
            "    var found = false;",
            "    for (var i = 0; i < rows.length; i++) {",
            "      if (String(rows[i].id) === id) {",
            "        for (var k in row) {",
            "          if (Object.prototype.hasOwnProperty.call(row, k)) {",
            "            rows[i][k] = row[k];",
            "          }",
            "        }",
            "        found = true;",
            "        break;",
            "      }",
            "    }",
            "    if (!found) {",
            "      rows.unshift(row);",
            "    }",
            "    A.cacheSet(tableName, rows);",
            "  };",
            "",
            "  A.cacheRemoveRow = function (tableName, rowId) {",
            "    var id = String(rowId || \"\");",
            "    var rows = A.cacheGet(tableName);",
            "    var filtered = [];",
            "    for (var i = 0; i < rows.length; i++) {",
            "      if (String(rows[i].id) !== id) {",
            "        filtered.push(rows[i]);",
            "      }",
            "    }",
            "    A.cacheSet(tableName, filtered);",
            "  };",
            "",
            "  /* Fusionne les lignes serveur avec les éventuelles lignes locales temporaires */",
            "  A.cacheMergeServerRows = function (tableName, serverRows) {",
            "    if (!Array.isArray(serverRows)) { return; }",
            "    var localRows = A.cacheGet(tableName);",
            "    var pendingLocals = [];",
            "    for (var i = 0; i < localRows.length; i++) {",
            "      if (String(localRows[i].id || \"\").indexOf(\"loc_\") === 0) {",
            "        pendingLocals.push(localRows[i]);",
            "      }",
            "    }",
            "    A.cacheSet(tableName, pendingLocals.concat(serverRows));",
            "  };",
            "",
            "  /* --- Politiques Hors-Ligne des Tables (Offline Policy) --- */",
            "  A.getTablePolicies = function () {",
            "    var raw = A.storageGet(A.POLICIES_KEY);",
            "    if (!raw) { return {}; }",
            "    try {",
            "      var obj = JSON.parse(raw);",
            "      return (obj && typeof obj === \"object\") ? obj : {};",
            "    } catch (e) { return {}; }",
            "  };",
            "",
            "  A.saveTablePolicies = function (policies) {",
            "    if (!policies || typeof policies !== \"object\") { return; }",
            "    try {",
            "      A.storageSet(A.POLICIES_KEY, JSON.stringify(policies));",
            "    } catch (e) {}",
            "  };",
            "",
            "  A.getTablePolicy = function (tableName) {",
            "    var t = String(tableName || \"\").trim();",
            "    if (!t) { return \"autorise\"; }",
            "    var policies = A.getTablePolicies();",
            "    var p = policies[t];",
            "    if (p === \"desactive\" || p === \"lecture_seule\" || p === \"autorise\") {",
            "      return p;",
            "    }",
            "    return \"autorise\";",
            "  };",
            "",
            "  A.setTablePolicy = function (tableName, policy) {",
            "    var t = String(tableName || \"\").trim();",
            "    if (!t) { return; }",
            "    var p = String(policy || \"\").trim().toLowerCase();",
            "    if (p === \"en_ligne_strict\" || p === \"desactive\" || p === \"bloque\" || p === \"disabled\") {",
            "      p = \"desactive\";",
            "    } else if (p === \"lecture_seule\" || p === \"readonly\" || p === \"read_only\") {",
            "      p = \"lecture_seule\";",
            "    } else {",
            "      p = \"autorise\";",
            "    }",
            "    var policies = A.getTablePolicies();",
            "    policies[t] = p;",
            "    A.saveTablePolicies(policies);",
            "    if (p === \"desactive\") {",
            "      A.storageDel(A.cacheKey(t));",
            "    }",
            "  };",
            "",
            "  A.fetchTablePolicies = function () {",
            "    if (typeof navigator !== \"undefined\" && navigator.onLine === false) {",
            "      return Promise.resolve(A.getTablePolicies());",
            "    }",
            "    return A.requestTable(\"/tables\", \"GET\", null).then(function (r) {",
            "      if (r.ok && r.json && Array.isArray(r.json.tables)) {",
            "        var policies = A.getTablePolicies();",
            "        for (var i = 0; i < r.json.tables.length; i++) {",
            "          var tbl = r.json.tables[i];",
            "          if (tbl && tbl.nom && tbl.regles && tbl.regles.mode_hors_ligne) {",
            "            policies[tbl.nom] = tbl.regles.mode_hors_ligne;",
            "          }",
            "        }",
            "        A.saveTablePolicies(policies);",
            "        return policies;",
            "      }",
            "      return A.getTablePolicies();",
            "    }, function () {",
            "      return A.getTablePolicies();",
            "    });",
            "  };",
            "",
            "  /* --- File d'attente de synchronisation (Sync Queue) --- */",
            "  A.getSyncQueue = function () {",
            "    var raw = A.storageGet(A.SYNC_QUEUE_KEY);",
            "    if (!raw) { return []; }",
            "    try {",
            "      var arr = JSON.parse(raw);",
            "      return Array.isArray(arr) ? arr : [];",
            "    } catch (e) { return []; }",
            "  };",
            "",
            "  A.saveSyncQueue = function (q) {",
            "    if (!Array.isArray(q)) { return; }",
            "    try {",
            "      A.storageSet(A.SYNC_QUEUE_KEY, JSON.stringify(q));",
            "      S.syncCount = q.length;",
            "    } catch (e) {}",
            "  };",
            "",
            "  A.enqueueSyncOp = function (action, tableName, data, rowId, tempId) {",
            "    var q = A.getSyncQueue();",
            "    var op = {",
            "      id: \"op_\" + Date.now() + \"_\" + Math.random().toString(36).substring(2, 7),",
            "      action: action,",
            "      table: String(tableName || \"\").trim(),",
            "      data: (data && typeof data === \"object\") ? JSON.parse(JSON.stringify(data)) : {},",
            "      rowId: rowId ? String(rowId) : \"\",",
            "      tempId: tempId ? String(tempId) : \"\",",
            "      timestamp: Date.now()",
            "    };",
            "    q.push(op);",
            "    A.saveSyncQueue(q);",
            "    return op;",
            "  };",
            "",
            "  A.remapSyncQueueId = function (oldId, newId, tableName) {",
            "    if (!oldId || !newId || oldId === newId) { return; }",
            "    var q = A.getSyncQueue();",
            "    var modified = false;",
            "    for (var i = 0; i < q.length; i++) {",
            "      if (q[i].rowId === oldId) {",
            "        q[i].rowId = newId;",
            "        modified = true;",
            "      }",
            "      if (q[i].tempId === oldId) {",
            "        q[i].tempId = newId;",
            "        modified = true;",
            "      }",
            "    }",
            "    if (modified) { A.saveSyncQueue(q); }",
            "",
            "    if (tableName) {",
            "      var rows = A.cacheGet(tableName);",
            "      for (var j = 0; j < rows.length; j++) {",
            "        if (String(rows[j].id) === String(oldId)) {",
            "          rows[j].id = newId;",
            "          break;",
            "        }",
            "      }",
            "      A.cacheSet(tableName, rows);",
            "    }",
            "  };",
            "",
            "  A.syncPendingQueue = function () {",
            "    if (S.syncing) { return Promise.resolve(false); }",
            "    var q = A.getSyncQueue();",
            "    if (!q || q.length === 0) {",
            "      S.syncCount = 0;",
            "      return Promise.resolve(true);",
            "    }",
            "    if (typeof navigator !== \"undefined\" && navigator.onLine === false) {",
            "      return Promise.resolve(false);",
            "    }",
            "    A.syncConfig();",
            "    if (!S.apiKey) { return Promise.resolve(false); }",
            "",
            "    S.syncing = true;",
            "    var tablesToRefresh = {};",
            "",
            "    var processNext = function () {",
            "      var queue = A.getSyncQueue();",
            "      if (queue.length === 0) {",
            "        S.syncing = false;",
            "        S.syncCount = 0;",
            "        for (var t in tablesToRefresh) {",
            "          if (Object.prototype.hasOwnProperty.call(tablesToRefresh, t)) {",
            "            A.listRows(t, { limit: 100 });",
            "          }",
            "        }",
            "        return Promise.resolve(true);",
            "      }",
            "      var op = queue[0];",
            "      tablesToRefresh[op.table] = true;",
            "",
            "      var p;",
            "      if (op.action === \"create\") {",
            "        p = A.requestTable(\"/tables/\" + encodeURIComponent(op.table), \"POST\", op.data);",
            "      } else if (op.action === \"update\") {",
            "        p = A.requestTable(\"/tables/\" + encodeURIComponent(op.table) + \"/\" + encodeURIComponent(op.rowId), \"PATCH\", op.data);",
            "      } else if (op.action === \"delete\") {",
            "        p = A.requestTable(\"/tables/\" + encodeURIComponent(op.table) + \"/\" + encodeURIComponent(op.rowId), \"DELETE\", null);",
            "      } else {",
            "        p = Promise.resolve({ ok: true });",
            "      }",
            "",
            "      return p.then(function (r) {",
            "        if (r.ok) {",
            "          if (op.action === \"create\" && r.json && r.json.data && r.json.data.id) {",
            "            var newServerId = String(r.json.data.id);",
            "            if (op.tempId) {",
            "              A.remapSyncQueueId(op.tempId, newServerId, op.table);",
            "            }",
            "          }",
            "          var current = A.getSyncQueue();",
            "          current.shift();",
            "          A.saveSyncQueue(current);",
            "          return processNext();",
            "        } else {",
            "          if (S.statusCode >= 400 && S.statusCode < 500) {",
            "            console.warn(\"[AngonCloud Sync] Opération rejetée (\" + S.statusCode + \"), retirée :\", op);",
            "            var current2 = A.getSyncQueue();",
            "            current2.shift();",
            "            A.saveSyncQueue(current2);",
            "            return processNext();",
            "          }",
            "          S.syncing = false;",
            "          return false;",
            "        }",
            "      }, function () {",
            "        S.syncing = false;",
            "        return false;",
            "      });",
            "    };",
            "",
            "    return processNext();",
            "  };",
            "",
            "  /* --- preference « Remember me » (persistee sur l appareil) --- */",
            "  A.loadRemember = function () {",
            "    var v = A.storageGet(A.REMEMBER_KEY);",
            "    S.remember = (v !== \"0\");",
            "    return S.remember;",
            "  };",
            "  A.setRemember = function (on) {",
            "    S.remember = !!on;",
            "    A.storageSet(A.REMEMBER_KEY, S.remember ? \"1\" : \"0\");",
            "    if (!S.remember) { A.storageDel(A.TOKEN_KEY); }",
            "    else if (S.token) { A.storageSet(A.TOKEN_KEY, S.token); }",
            "    return S.remember;",
            "  };",
            "",
            "  /* --- pays deduit de la locale du navigateur --- */",
            "  A.detectPays = function () {",
            "    try {",
            "      var loc = (navigator.languages && navigator.languages[0]) || navigator.language || \"\";",
            "      var region = \"\";",
            "      if (typeof Intl !== \"undefined\" && Intl.Locale) {",
            "        try { region = new Intl.Locale(loc).region || \"\"; } catch (e) { region = \"\"; }",
            "      }",
            "      if (!region) {",
            "        var parts = String(loc).split(\"-\");",
            "        if (parts.length > 1) { region = parts[parts.length - 1].toUpperCase(); }",
            "      }",
            "      if (!region || region.length !== 2) { return \"\"; }",
            "      if (typeof Intl !== \"undefined\" && Intl.DisplayNames) {",
            "        try {",
            "          var dn = new Intl.DisplayNames([\"en\"], { type: \"region\" });",
            "          return dn.of(region) || region;",
            "        } catch (e) { return region; }",
            "      }",
            "      return region;",
            "    } catch (e) { return \"\"; }",
            "  };",
            "",
            "  /* Code pays brut, transmis au serveur qui peut aussi utiliser la localisation reseau. */",
            "  A.detectCountryCode = function () {",
            "    try {",
            "      var loc = (navigator.languages && navigator.languages[0]) || navigator.language || \"\";",
            "      var region = \"\";",
            "      if (typeof Intl !== \"undefined\" && Intl.Locale) {",
            "        try { region = new Intl.Locale(loc).region || \"\"; } catch (e) { region = \"\"; }",
            "      }",
            "      if (!region) {",
            "        var match = String(loc).match(/[-_]([A-Za-z]{2})$/);",
            "        if (match) { region = match[1]; }",
            "      }",
            "      return String(region || \"\").toUpperCase();",
            "    } catch (e) { return \"\"; }",
            "  };",
            "",
            "  A.clearError = function () { S.errorCode = \"\"; S.errorMessage = \"\"; };",
            "",
            "  /* --- appel HTTP vers l API publique Angon --- */",
            "  A.request = function (pathname, method, body, withToken) {",
            "    A.syncConfig();",
            "    if (!S.apiKey) {",
            "      S.statusCode = 0;",
            "      S.errorCode = \"missing_api_key\";",
            "      S.errorMessage = \"Missing Angon API key. Set the ApiKey variable in the extension parameters.\";",
            "      return Promise.resolve({ ok: false, json: null });",
            "    }",
            "    if (withToken && !S.token) {",
            "      S.statusCode = 0;",
            "      S.errorCode = \"missing_token\";",
            "      S.errorMessage = \"No player is signed in.\";",
            "      return Promise.resolve({ ok: false, json: null });",
            "    }",
            "    var headers = { \"Content-Type\": \"application/json\", \"x-api-key\": S.apiKey };",
            "    if (withToken) { headers[\"x-player-token\"] = S.token; }",
            "    var opts = { method: method, headers: headers };",
            "    if (body) { opts.body = JSON.stringify(body); }",
            "    S.pending = true;",
            "    A.clearError();",
            "    return fetch(S.baseUrl + pathname, opts).then(function (res) {",
            "      return res.text().then(function (txt) {",
            "        var json = null;",
            "        try { json = txt ? JSON.parse(txt) : null; } catch (e) { json = null; }",
            "        S.pending = false;",
            "        S.statusCode = res.status;",
            "        S.lastResponse = txt || \"\";",
            "        if (res.ok) {",
            "          A.clearError();",
            "        } else {",
            "          S.errorCode = (json && json.error) ? String(json.error) : \"http_\" + res.status;",
            "          S.errorMessage = (json && json.message) ? String(json.message) : \"The Angon server rejected the request.\";",
            "        }",
            "        return { ok: res.ok, json: json };",
            "      });",
            "    }, function (err) {",
            "      S.pending = false;",
            "      S.statusCode = 0;",
            "      S.errorCode = \"network_error\";",
            "      S.errorMessage = \"The Angon server is unreachable. Check your internet connection.\";",
            "      console.error(\"[AngonCloud] Network error:\", err);",
            "      return { ok: false, json: null };",
            "    });",
            "  };",
            "",
            "  /* --- recopie de la reponse { token, compte, profil } dans l etat --- */",
            "  A.applyAccount = function (json, markLogin) {",
            "    if (!json) { return; }",
            "    var c = json.compte || {};",
            "    var p = json.profil || {};",
            "    if (json.token) {",
            "      S.token = String(json.token);",
            "      if (S.remember) { A.storageSet(A.TOKEN_KEY, S.token); }",
            "      else { A.storageDel(A.TOKEN_KEY); }",
            "    }",
            "    if (c.id) { S.accountId = String(c.id); }",
            "    if (c.email) { S.email = String(c.email); }",
            "    if (c.pseudo) { S.pseudo = String(c.pseudo); }",
            "    if (c.pays) { S.pays = String(c.pays); }",
            "    if (c.avatar_url) { S.avatarUrl = String(c.avatar_url); }",
            "    else if (p.avatar_url) { S.avatarUrl = String(p.avatar_url); }",
            "    if (p.id) { S.profileId = String(p.id); }",
            "    if (p.niveau !== undefined && p.niveau !== null) { S.niveau = Number(p.niveau); }",
            "    if (p.score !== undefined && p.score !== null) { S.score = Number(p.score); }",
            "    if (p.statut) { S.statut = String(p.statut); }",
            "    S.donnees = p.donnees ? JSON.stringify(p.donnees) : \"{}\";",
            "    S.loggedIn = !!S.token;",
            "    if (S.loggedIn && markLogin) { S.justLoggedIn = true; }",
            "  };",
            "",
            "  A.reset = function () {",
            "    S.token = \"\";",
            "    S.loggedIn = false;",
            "    S.justLoggedIn = false;",
            "    S.accountId = \"\";",
            "    S.profileId = \"\";",
            "    S.pseudo = \"\";",
            "    S.email = \"\";",
            "    S.pays = \"\";",
            "    S.avatarUrl = \"\";",
            "    S.statut = \"hors_ligne\";",
            "    S.niveau = 1;",
            "    S.score = 0;",
            "    S.donnees = \"{}\";",
            "    S.lastTableData = [];",
            "    S.lastTableRow = null;",
            "    S.lastTableCount = 0;",
            "    S.lastCreatedId = \"\";",
            "    S.lastTableResponse = \"\";",
            "    S.tableSuccess = false;",
            "    S.lastFileUrl = \"\";",
            "    S.lastFileId = \"\";",
            "    S.lastFileName = \"\";",
            "    S.lastFileSize = 0;",
            "    S.lastUploadSuccess = false;",
            "    S.playerFilesJson = \"[]\";",
            "    S.shopArticles = [];",
            "    A.storageDel(A.TOKEN_KEY);",
            "  };",
            "",
            "  /* --- resolution et conversion des variables GDevelop (scene / globale) --- */",
            "  A.resolveVariable = function (v) {",
            "    if (!v) { return null; }",
            "    if (typeof v === \"object\" && (v.toJSObject || v.fromJSObject || v.getAsString || v.setString)) {",
            "      return v;",
            "    }",
            "    if (typeof v === \"string\" && v) {",
            "      if (runtimeScene && runtimeScene.getVariables && runtimeScene.getVariables().has(v)) {",
            "        return runtimeScene.getVariables().get(v);",
            "      }",
            "      if (A.game && A.game.getVariables && A.game.getVariables().has(v)) {",
            "        return A.game.getVariables().get(v);",
            "      }",
            "    }",
            "    return null;",
            "  };",
            "",
            "  A.setVariableData = function (v, data) {",
            "    var target = A.resolveVariable(v);",
            "    if (!target) { return; }",
            "    try {",
            "      if (typeof target.fromJSObject === \"function\") {",
            "        target.fromJSObject(data);",
            "        return;",
            "      }",
            "    } catch (e) {}",
            "    try {",
            "      if (typeof gdjs !== \"undefined\" && gdjs.evtTools && gdjs.evtTools.network && gdjs.evtTools.network.jsonToVariableStructure) {",
            "        gdjs.evtTools.network.jsonToVariableStructure(JSON.stringify(data), target);",
            "        return;",
            "      }",
            "    } catch (e) {}",
            "    try {",
            "      if (typeof data === \"object\" && data !== null) {",
            "        target.setString(JSON.stringify(data));",
            "      } else if (typeof data === \"number\") {",
            "        target.setNumber(data);",
            "      } else {",
            "        target.setString(String(data !== undefined && data !== null ? data : \"\"));",
            "      }",
            "    } catch (e) {}",
            "  };",
            "",
            "  A.getVariableData = function (v) {",
            "    var target = A.resolveVariable(v);",
            "    if (!target) {",
            "      if (typeof v === \"object\" && v !== null) { return v; }",
            "      if (typeof v === \"string\") {",
            "        try { return JSON.parse(v); } catch (e) { return {}; }",
            "      }",
            "      return {};",
            "    }",
            "    try {",
            "      if (typeof target.toJSObject === \"function\") {",
            "        var res = target.toJSObject();",
            "        if (res && typeof res === \"object\") { return res; }",
            "      }",
            "    } catch (e) {}",
            "    try {",
            "      var str = target.getAsString();",
            "      if (str) {",
            "        try { return JSON.parse(str); } catch (e) {}",
            "      }",
            "    } catch (e) {}",
            "    return {};",
            "  };",
            "",
            "  /* --- requete HTTP dediee aux tables (attache x-player-token si present) --- */",
            "  A.requestTable = function (pathname, method, body) {",
            "    A.syncConfig();",
            "    if (!S.apiKey) {",
            "      S.statusCode = 0;",
            "      S.errorCode = \"missing_api_key\";",
            "      S.errorMessage = \"Missing Angon API key. Set the ApiKey variable in the extension parameters.\";",
            "      S.tableSuccess = false;",
            "      return Promise.resolve({ ok: false, json: null });",
            "    }",
            "    var headers = { \"Content-Type\": \"application/json\", \"x-api-key\": S.apiKey };",
            "    if (S.token) {",
            "      headers[\"x-player-token\"] = S.token;",
            "    }",
            "    var opts = { method: method, headers: headers };",
            "    if (body) {",
            "      opts.body = typeof body === \"string\" ? body : JSON.stringify(body);",
            "    }",
            "    S.pending = true;",
            "    A.clearError();",
            "    return fetch(S.baseUrl + pathname, opts).then(function (res) {",
            "      return res.text().then(function (txt) {",
            "        var json = null;",
            "        try { json = txt ? JSON.parse(txt) : null; } catch (e) { json = null; }",
            "        S.pending = false;",
            "        S.statusCode = res.status;",
            "        S.lastResponse = txt || \"\";",
            "        S.lastTableResponse = txt || \"\";",
            "        if (res.ok) {",
            "          A.clearError();",
            "          S.tableSuccess = true;",
            "        } else {",
            "          S.tableSuccess = false;",
            "          S.errorCode = (json && json.error) ? String(json.error) : \"http_\" + res.status;",
            "          S.errorMessage = (json && json.message) ? String(json.message) : \"The Angon server rejected the table request.\";",
            "          console.error(\"[AngonCloud] Erreur table (\" + res.status + \" \" + S.errorCode + \"):\", S.errorMessage, json);",
            "        }",
            "        return { ok: res.ok, json: json };",
            "      });",
            "    }, function (err) {",
            "      S.pending = false;",
            "      S.statusCode = 0;",
            "      S.errorCode = \"network_error\";",
            "      S.errorMessage = \"The Angon server is unreachable. Check your internet connection.\";",
            "      S.tableSuccess = false;",
            "      console.error(\"[AngonCloud] Network error on table request:\", err);",
            "      return { ok: false, json: null };",
            "    });",
            "  };",
            "",
            "  /* --- methodes de manipulation de tables (avec mode Hors-Ligne) --- */",
            "  A.listRows = function (tableName, queryParams) {",
            "    var table = String(tableName || \"\").trim();",
            "    if (!table) {",
            "      S.errorCode = \"invalid_table\";",
            "      S.errorMessage = \"Table name is required.\";",
            "      S.tableSuccess = false;",
            "      return Promise.resolve({ ok: false, data: [] });",
            "    }",
            "    var policy = A.getTablePolicy(table);",
            "    var qs = \"\";",
            "    if (queryParams && typeof queryParams === \"object\") {",
            "      var parts = [];",
            "      for (var k in queryParams) {",
            "        if (Object.prototype.hasOwnProperty.call(queryParams, k) && queryParams[k] !== undefined && queryParams[k] !== null && queryParams[k] !== \"\") {",
            "          parts.push(encodeURIComponent(k) + \"=\" + encodeURIComponent(queryParams[k]));",
            "        }",
            "      }",
            "      if (parts.length > 0) { qs = \"?\" + parts.join(\"&\"); }",
            "    }",
            "",
            "    var fallbackLocal = function () {",
            "      if (policy === \"desactive\") {",
            "        S.errorCode = \"offline_not_allowed\";",
            "        S.errorMessage = \"Offline access is disabled for table '\" + table + \"'.\";",
            "        S.tableSuccess = false;",
            "        S.lastTableData = [];",
            "        S.lastTableCount = 0;",
            "        S.lastTableRow = null;",
            "        return { ok: false, data: [], count: 0, offline_forbidden: true };",
            "      }",
            "      var cached = A.cacheGet(table);",
            "      if (queryParams && typeof queryParams === \"object\") {",
            "        for (var key in queryParams) {",
            "          if (key !== \"limit\" && key !== \"offset\" && key !== \"order\" && Object.prototype.hasOwnProperty.call(queryParams, key)) {",
            "            var filterVal = String(queryParams[key]);",
            "            cached = cached.filter(function (row) {",
            "              return row && String(row[key]) === filterVal;",
            "            });",
            "          }",
            "        }",
            "      }",
            "      S.lastTableData = cached;",
            "      S.lastTableCount = cached.length;",
            "      S.lastTableRow = cached.length > 0 ? cached[0] : null;",
            "      S.tableSuccess = true;",
            "      A.clearError();",
            "      return { ok: true, data: cached, count: cached.length, offline: true };",
            "    };",
            "",
            "    if (typeof navigator !== \"undefined\" && navigator.onLine === false) {",
            "      return Promise.resolve(fallbackLocal());",
            "    }",
            "",
            "    return A.requestTable(\"/tables/\" + encodeURIComponent(table) + qs, \"GET\", null)",
            "      .then(function (r) {",
            "        if (r.ok && r.json && Array.isArray(r.json.data)) {",
            "          if (policy !== \"desactive\") {",
            "            A.cacheMergeServerRows(table, r.json.data);",
            "          }",
            "          var localPending = (policy !== \"desactive\") ? A.cacheGet(table).filter(function (row) {",
            "            return String(row.id || \"\").indexOf(\"loc_\") === 0;",
            "          }) : [];",
            "          var finalData = localPending.concat(r.json.data);",
            "          S.lastTableData = finalData;",
            "          S.lastTableCount = finalData.length;",
            "          S.lastTableRow = finalData.length > 0 ? finalData[0] : null;",
            "          return { ok: true, data: finalData, count: finalData.length };",
            "        }",
            "        if (S.errorCode === \"network_error\") {",
            "          return fallbackLocal();",
            "        }",
            "        S.lastTableData = [];",
            "        S.lastTableCount = 0;",
            "        S.lastTableRow = null;",
            "        return { ok: false, data: [] };",
            "      }, function () {",
            "        return fallbackLocal();",
            "      });",
            "  };",
            "",
            "  A.getRow = function (tableName, rowId) {",
            "    var table = String(tableName || \"\").trim();",
            "    var id = String(rowId || \"\").trim();",
            "    if (!table || !id) {",
            "      S.errorCode = \"invalid_arguments\";",
            "      S.errorMessage = \"Table name and row ID are required.\";",
            "      S.tableSuccess = false;",
            "      return Promise.resolve({ ok: false, data: null });",
            "    }",
            "    var policy = A.getTablePolicy(table);",
            "",
            "    var fallbackLocal = function () {",
            "      if (policy === \"desactive\") {",
            "        S.errorCode = \"offline_not_allowed\";",
            "        S.errorMessage = \"Offline access is disabled for table '\" + table + \"'.\";",
            "        S.tableSuccess = false;",
            "        S.lastTableRow = null;",
            "        return { ok: false, data: null, offline_forbidden: true };",
            "      }",
            "      var rows = A.cacheGet(table);",
            "      var match = null;",
            "      for (var i = 0; i < rows.length; i++) {",
            "        if (String(rows[i].id) === id) { match = rows[i]; break; }",
            "      }",
            "      if (match) {",
            "        S.lastTableRow = match;",
            "        S.lastCreatedId = String(match.id || \"\");",
            "        S.tableSuccess = true;",
            "        A.clearError();",
            "        return { ok: true, data: match, offline: true };",
            "      }",
            "      return { ok: false, data: null };",
            "    };",
            "",
            "    if ((typeof navigator !== \"undefined\" && navigator.onLine === false) || id.indexOf(\"loc_\") === 0) {",
            "      return Promise.resolve(fallbackLocal());",
            "    }",
            "",
            "    return A.requestTable(\"/tables/\" + encodeURIComponent(table) + \"/\" + encodeURIComponent(id), \"GET\", null)",
            "      .then(function (r) {",
            "        if (r.ok && r.json && r.json.data) {",
            "          if (policy !== \"desactive\") {",
            "            A.cacheUpsertRow(table, r.json.data);",
            "          }",
            "          S.lastTableRow = r.json.data;",
            "          S.lastCreatedId = String(r.json.data.id || \"\");",
            "          return { ok: true, data: r.json.data };",
            "        }",
            "        if (S.errorCode === \"network_error\") {",
            "          return fallbackLocal();",
            "        }",
            "        S.lastTableRow = null;",
            "        return { ok: false, data: null };",
            "      }, function () {",
            "        return fallbackLocal();",
            "      });",
            "  };",
            "",
            "  A.createRow = function (tableName, data) {",
            "    var table = String(tableName || \"\").trim();",
            "    if (!table) {",
            "      S.errorCode = \"invalid_table\";",
            "      S.errorMessage = \"Table name is required.\";",
            "      S.tableSuccess = false;",
            "      var errLog = { ok: false, status: 0, code: \"invalid_table\", message: S.errorMessage, data: null, raw: \"\" };",
            "      return Promise.resolve({ ok: false, data: null, log: errLog });",
            "    }",
            "    var policy = A.getTablePolicy(table);",
            "",
            "    var doOffline = function () {",
            "      if (policy === \"desactive\" || policy === \"lecture_seule\") {",
            "        var c = (policy === \"desactive\") ? \"offline_not_allowed\" : \"offline_write_forbidden\";",
            "        var m = (policy === \"desactive\")",
            "          ? \"Offline access is disabled for table '\" + table + \"'.\"",
            "          : \"Offline write operations are disabled for table '\" + table + \"'.\";",
            "        S.errorCode = c;",
            "        S.errorMessage = m;",
            "        S.tableSuccess = false;",
            "        var offErrLog = { ok: false, status: 0, code: c, message: m, data: null, raw: \"\" };",
            "        return { ok: false, data: null, log: offErrLog, offline_forbidden: true };",
            "      }",
            "      var tempId = \"loc_\" + Date.now() + \"_\" + Math.random().toString(36).substring(2, 6);",
            "      var localRow = Object.assign({ id: tempId, created_at: new Date().toISOString() }, data || {});",
            "      A.cacheUpsertRow(table, localRow);",
            "      A.enqueueSyncOp(\"create\", table, data, null, tempId);",
            "      S.lastTableRow = localRow;",
            "      S.lastCreatedId = tempId;",
            "      S.tableSuccess = true;",
            "      A.clearError();",
            "      var offLog = {",
            "        ok: true,",
            "        status: 0,",
            "        code: \"offline_queued\",",
            "        message: \"Enregistré en local (en attente de synchronisation)\",",
            "        id: tempId,",
            "        data: localRow,",
            "        raw: JSON.stringify(localRow)",
            "      };",
            "      return { ok: true, data: localRow, log: offLog, offline: true };",
            "    };",
            "",
            "    if (typeof navigator !== \"undefined\" && navigator.onLine === false) {",
            "      return Promise.resolve(doOffline());",
            "    }",
            "",
            "    return A.requestTable(\"/tables/\" + encodeURIComponent(table), \"POST\", data)",
            "      .then(function (r) {",
            "        if (r.ok && r.json && r.json.data) {",
            "          if (policy !== \"desactive\") {",
            "            A.cacheUpsertRow(table, r.json.data);",
            "          }",
            "          S.lastTableRow = r.json.data;",
            "          S.lastCreatedId = String(r.json.data.id || \"\");",
            "          var log = {",
            "            ok: true,",
            "            status: S.statusCode,",
            "            code: \"\",",
            "            message: \"\",",
            "            id: S.lastCreatedId,",
            "            data: r.json.data,",
            "            raw: S.lastTableResponse || \"\"",
            "          };",
            "          return { ok: true, data: r.json.data, log: log };",
            "        }",
            "        if (S.errorCode === \"network_error\") {",
            "          return doOffline();",
            "        }",
            "        var failLog = {",
            "          ok: false,",
            "          status: S.statusCode,",
            "          code: S.errorCode || \"\",",
            "          message: S.errorMessage || \"\",",
            "          data: null,",
            "          raw: S.lastTableResponse || \"\"",
            "        };",
            "        return { ok: false, data: null, log: failLog };",
            "      }, function () {",
            "        return doOffline();",
            "      });",
            "  };",
            "",
            "  A.updateRow = function (tableName, rowId, data) {",
            "    var table = String(tableName || \"\").trim();",
            "    var id = String(rowId || \"\").trim();",
            "    if (!table || !id) {",
            "      S.errorCode = \"invalid_arguments\";",
            "      S.errorMessage = \"Table name and row ID are required.\";",
            "      S.tableSuccess = false;",
            "      var errLog = { ok: false, status: 0, code: \"invalid_arguments\", message: S.errorMessage, data: null, raw: \"\" };",
            "      return Promise.resolve({ ok: false, data: null, log: errLog });",
            "    }",
            "    var policy = A.getTablePolicy(table);",
            "",
            "    var doOffline = function () {",
            "      if (policy === \"desactive\" || policy === \"lecture_seule\") {",
            "        var c = (policy === \"desactive\") ? \"offline_not_allowed\" : \"offline_write_forbidden\";",
            "        var m = (policy === \"desactive\")",
            "          ? \"Offline access is disabled for table '\" + table + \"'.\"",
            "          : \"Offline write operations are disabled for table '\" + table + \"'.\";",
            "        S.errorCode = c;",
            "        S.errorMessage = m;",
            "        S.tableSuccess = false;",
            "        var offErrLog = { ok: false, status: 0, code: c, message: m, data: null, raw: \"\" };",
            "        return { ok: false, data: null, log: offErrLog, offline_forbidden: true };",
            "      }",
            "      var existingRows = A.cacheGet(table);",
            "      var current = null;",
            "      for (var i = 0; i < existingRows.length; i++) {",
            "        if (String(existingRows[i].id) === id) { current = existingRows[i]; break; }",
            "      }",
            "      var updated = Object.assign({}, current || {}, data, { id: id, updated_at: new Date().toISOString() });",
            "      A.cacheUpsertRow(table, updated);",
            "      A.enqueueSyncOp(\"update\", table, data, id, null);",
            "      S.lastTableRow = updated;",
            "      S.tableSuccess = true;",
            "      A.clearError();",
            "      var offLog = {",
            "        ok: true,",
            "        status: 0,",
            "        code: \"offline_queued\",",
            "        message: \"Modifié en local (en attente de synchronisation)\",",
            "        id: id,",
            "        data: updated,",
            "        raw: JSON.stringify(updated)",
            "      };",
            "      return { ok: true, data: updated, log: offLog, offline: true };",
            "    };",
            "",
            "    if ((typeof navigator !== \"undefined\" && navigator.onLine === false) || id.indexOf(\"loc_\") === 0) {",
            "      return Promise.resolve(doOffline());",
            "    }",
            "",
            "    return A.requestTable(\"/tables/\" + encodeURIComponent(table) + \"/\" + encodeURIComponent(id), \"PATCH\", data)",
            "      .then(function (r) {",
            "        if (r.ok && r.json && r.json.data) {",
            "          if (policy !== \"desactive\") {",
            "            A.cacheUpsertRow(table, r.json.data);",
            "          }",
            "          S.lastTableRow = r.json.data;",
            "          S.lastCreatedId = String(r.json.data.id || \"\");",
            "          var log = {",
            "            ok: true,",
            "            status: S.statusCode,",
            "            code: \"\",",
            "            message: \"\",",
            "            id: S.lastCreatedId,",
            "            data: r.json.data,",
            "            raw: S.lastTableResponse || \"\"",
            "          };",
            "          return { ok: true, data: r.json.data, log: log };",
            "        }",
            "        if (S.errorCode === \"network_error\") {",
            "          return doOffline();",
            "        }",
            "        var failLog = {",
            "          ok: false,",
            "          status: S.statusCode,",
            "          code: S.errorCode || \"\",",
            "          message: S.errorMessage || \"\",",
            "          data: null,",
            "          raw: S.lastTableResponse || \"\"",
            "        };",
            "        return { ok: false, data: null, log: failLog };",
            "      }, function () {",
            "        return doOffline();",
            "      });",
            "  };",
            "",
            "  A.deleteRow = function (tableName, rowId) {",
            "    var table = String(tableName || \"\").trim();",
            "    var id = String(rowId || \"\").trim();",
            "    if (!table || !id) {",
            "      S.errorCode = \"invalid_arguments\";",
            "      S.errorMessage = \"Table name and row ID are required.\";",
            "      S.tableSuccess = false;",
            "      var errLog = { ok: false, status: 0, code: \"invalid_arguments\", message: S.errorMessage, raw: \"\" };",
            "      return Promise.resolve({ ok: false, log: errLog });",
            "    }",
            "    var policy = A.getTablePolicy(table);",
            "",
            "    var doOffline = function () {",
            "      if (policy === \"desactive\" || policy === \"lecture_seule\") {",
            "        var c = (policy === \"desactive\") ? \"offline_not_allowed\" : \"offline_write_forbidden\";",
            "        var m = (policy === \"desactive\")",
            "          ? \"Offline access is disabled for table '\" + table + \"'.\"",
            "          : \"Offline write operations are disabled for table '\" + table + \"'.\";",
            "        S.errorCode = c;",
            "        S.errorMessage = m;",
            "        S.tableSuccess = false;",
            "        var offErrLog = { ok: false, status: 0, code: c, message: m, raw: \"\" };",
            "        return { ok: false, log: offErrLog, offline_forbidden: true };",
            "      }",
            "      A.cacheRemoveRow(table, id);",
            "      if (id.indexOf(\"loc_\") === 0) {",
            "        var q = A.getSyncQueue().filter(function (op) {",
            "          return op.tempId !== id && op.rowId !== id;",
            "        });",
            "        A.saveSyncQueue(q);",
            "      } else {",
            "        A.enqueueSyncOp(\"delete\", table, null, id, null);",
            "      }",
            "      S.tableSuccess = true;",
            "      A.clearError();",
            "      var offLog = {",
            "        ok: true,",
            "        status: 0,",
            "        code: \"offline_queued\",",
            "        message: \"Supprimé en local (en attente de synchronisation)\",",
            "        raw: \"\"",
            "      };",
            "      return { ok: true, log: offLog, offline: true };",
            "    };",
            "",
            "    if ((typeof navigator !== \"undefined\" && navigator.onLine === false) || id.indexOf(\"loc_\") === 0) {",
            "      return Promise.resolve(doOffline());",
            "    }",
            "",
            "    return A.requestTable(\"/tables/\" + encodeURIComponent(table) + \"/\" + encodeURIComponent(id), \"DELETE\", null)",
            "      .then(function (r) {",
            "        if (r.ok) {",
            "          A.cacheRemoveRow(table, id);",
            "          S.tableSuccess = true;",
            "          A.clearError();",
            "          var log = {",
            "            ok: true,",
            "            status: S.statusCode,",
            "            code: \"\",",
            "            message: \"\",",
            "            raw: S.lastTableResponse || \"\"",
            "          };",
            "          return { ok: true, log: log };",
            "        }",
            "        if (S.errorCode === \"network_error\") {",
            "          return doOffline();",
            "        }",
            "        var failLog = {",
            "          ok: false,",
            "          status: S.statusCode,",
            "          code: S.errorCode || \"\",",
            "          message: S.errorMessage || \"\",",
            "          raw: S.lastTableResponse || \"\"",
            "        };",
            "        return { ok: false, log: failLog };",
            "      }, function () {",
            "        return doOffline();",
            "      });",
            "  };",
            "",
            "  /* --- methodes specialisees Boutique Angon Cloud --- */",
            "  A.listShopArticles = function () {",
            "    return A.listRows(\"boutique\", { limit: 100 }).then(function (r) {",
            "      if (r.ok && Array.isArray(r.data)) {",
            "        var active = r.data.filter(function (item) {",
            "          return item && item.actif !== false && item.actif !== \"false\" && item.actif !== 0;",
            "        });",
            "        S.shopArticles = active;",
            "        return { ok: true, data: active, count: active.length };",
            "      }",
            "      S.shopArticles = [];",
            "      return { ok: false, data: [], count: 0 };",
            "    }, function () {",
            "      S.shopArticles = [];",
            "      return { ok: false, data: [], count: 0 };",
            "    });",
            "  };",
            "",
            "  /* --- methodes specialisees Classements (Leaderboards) --- */",
            "  A.submitScore = function (codeOrId, valeur) {",
            "    var raw = String(codeOrId || \"\").trim();",
            "    var val = Number(valeur);",
            "    if (!raw) {",
            "      S.errorCode = \"invalid_arguments\";",
            "      S.errorMessage = \"Leaderboard code or ID is required.\";",
            "      S.leaderboardSuccess = false;",
            "      return Promise.resolve({ ok: false, data: null, log: { ok: false, code: S.errorCode, message: S.errorMessage } });",
            "    }",
            "    if (isNaN(val)) {",
            "      S.errorCode = \"invalid_value\";",
            "      S.errorMessage = \"Score value must be a valid number.\";",
            "      S.leaderboardSuccess = false;",
            "      return Promise.resolve({ ok: false, data: null, log: { ok: false, code: S.errorCode, message: S.errorMessage } });",
            "    }",
            "    return A.request(\"/leaderboards/\" + encodeURIComponent(raw), \"POST\", { valeur: val }, true).then(function (r) {",
            "      if (r.ok && r.json && r.json.ok) {",
            "        S.leaderboardSuccess = true;",
            "        S.lastLeaderboardCode = String(r.json.classement || raw);",
            "        S.lastLeaderboardId = String(r.json.classement_id || \"\");",
            "        S.lastLeaderboardScore = Number(r.json.meilleur != null ? r.json.meilleur : val);",
            "        S.lastLeaderboardRank = Number(r.json.rang || 0);",
            "        return { ok: true, data: r.json, log: r.json };",
            "      }",
            "      S.leaderboardSuccess = false;",
            "      return { ok: false, data: null, log: { ok: false, code: S.errorCode, message: S.errorMessage } };",
            "    });",
            "  };",
            "",
            "  A.fetchLeaderboard = function (codeOrId, limit) {",
            "    var raw = String(codeOrId || \"\").trim();",
            "    var lim = Number(limit) || 50;",
            "    if (!raw) {",
            "      S.errorCode = \"invalid_arguments\";",
            "      S.errorMessage = \"Leaderboard code or ID is required.\";",
            "      S.leaderboardSuccess = false;",
            "      return Promise.resolve({ ok: false, data: [], log: { ok: false, code: S.errorCode, message: S.errorMessage } });",
            "    }",
            "    return A.request(\"/leaderboards/\" + encodeURIComponent(raw) + \"?limit=\" + lim, \"GET\", null, false).then(function (r) {",
            "      if (r.ok && r.json && r.json.ok) {",
            "        S.leaderboardSuccess = true;",
            "        S.lastLeaderboardCode = String(r.json.code || raw);",
            "        S.lastLeaderboardId = String(r.json.id || \"\");",
            "        S.leaderboardEntries = Array.isArray(r.json.entrees) ? r.json.entrees : [];",
            "        return { ok: true, data: S.leaderboardEntries, log: r.json };",
            "      }",
            "      S.leaderboardSuccess = false;",
            "      return { ok: false, data: [], log: { ok: false, code: S.errorCode, message: S.errorMessage } };",
            "    });",
            "  };",
            "",
            "  /* --- matchmaking atomique (routes dediees /matchmaking/*) --- */",
            "  A.applyMatchmaking = function (json) {",
            "    if (!json || !json.ok) { return; }",
            "    var status = String(json.status || \"idle\");",
            "    if (status === \"waiting\") {",
            "      S.mmStatus = \"waiting\";",
            "      S.mmPosition = Number(json.position) || 0;",
            "    } else if (status === \"matched\") {",
            "      S.mmStatus = \"matched\";",
            "      S.mmPartieId = String(json.partie_id || \"\");",
            "      var partie = json.partie && typeof json.partie === \"object\" ? json.partie : {};",
            "      S.mmCodeSalon = String(json.code_salon || partie.code_salon || \"\");",
            "      S.mmEquipe = Number(json.equipe) || 0;",
            "      var parts = json.participants !== undefined ? json.participants : partie.participants;",
            "      var arr = [];",
            "      try {",
            "        arr = typeof parts === \"string\" ? JSON.parse(parts) : (Array.isArray(parts) ? parts : []);",
            "      } catch (e) { arr = []; }",
            "      S.mmParticipantsJson = JSON.stringify(arr);",
            "    } else {",
            "      S.mmStatus = \"idle\";",
            "      S.mmPosition = 0;",
            "    }",
            "  };",
            "",
            "  A.matchmakingJoin = function (mode) {",
            "    return A.request(\"/matchmaking/join\", \"POST\", { mode_jeu: String(mode || \"1v1\") }, true)",
            "      .then(function (r) {",
            "        if (r.ok && r.json && r.json.ok) {",
            "          A.applyMatchmaking(r.json);",
            "          return { ok: true, log: r.json };",
            "        }",
            "        return { ok: false, log: { ok: false, code: S.errorCode, message: S.errorMessage } };",
            "      });",
            "  };",
            "",
            "  A.matchmakingLeave = function () {",
            "    return A.request(\"/matchmaking/leave\", \"POST\", {}, true).then(function (r) {",
            "      if (r.ok && r.json && r.json.ok) {",
            "        S.mmStatus = \"idle\";",
            "        S.mmPosition = 0;",
            "        return { ok: true, log: r.json };",
            "      }",
            "      return { ok: false, log: { ok: false, code: S.errorCode, message: S.errorMessage } };",
            "    });",
            "  };",
            "",
            "  A.matchmakingStatus = function () {",
            "    return A.request(\"/matchmaking/status\", \"GET\", null, true).then(function (r) {",
            "      if (r.ok && r.json && r.json.ok) {",
            "        A.applyMatchmaking(r.json);",
            "        return { ok: true, log: r.json };",
            "      }",
            "      return { ok: false, log: { ok: false, code: S.errorCode, message: S.errorMessage } };",
            "    });",
            "  };",
            "",
            "  /* --- canal temps-reel du salon (Supabase Realtime Broadcast) --- */",
            "  A.rtConfigPromise = null;",
            "  A.rtConfig = function () {",
            "    if (A.rtConfigPromise) { return A.rtConfigPromise; }",
            "    A.rtConfigPromise = A.request(\"/realtime/config\", \"GET\", null, false).then(function (r) {",
            "      if (r.ok && r.json && r.json.ok && r.json.url && r.json.key) {",
            "        S.rtUrl = String(r.json.url);",
            "        S.rtKey = String(r.json.key);",
            "        return true;",
            "      }",
            "      return false;",
            "    });",
            "    return A.rtConfigPromise;",
            "  };",
            "",
            "  A.rtSend = function (msg) {",
            "    try {",
            "      if (S.rtSocket && S.rtSocket.readyState === 1) { S.rtSocket.send(JSON.stringify(msg)); }",
            "    } catch (e) {}",
            "  };",
            "",
            "  A.rtHeartbeat = function () {",
            "    if (S.rtHbTimer) { clearInterval(S.rtHbTimer); }",
            "    S.rtHbTimer = setInterval(function () {",
            "      A.rtSend({ topic: \"phoenix\", event: \"heartbeat\", payload: {}, ref: String(Date.now()) });",
            "    }, 20000);",
            "  };",
            "",
            "  /* Rejoint le canal Broadcast du salon (partie_id = uuid unique, non devinable). */",
            "  A.roomJoin = function (partieId) {",
            "    var pid = String(partieId || S.mmPartieId || \"\").trim();",
            "    if (!pid) { return Promise.resolve(false); }",
            "    if (typeof WebSocket === \"undefined\") {",
            "      console.warn(\"[AngonCloud] WebSocket non disponible dans cet environnement.\");",
            "      return Promise.resolve(false);",
            "    }",
            "    return A.rtConfig().then(function (ok) {",
            "      if (!ok) { return false; }",
            "      A.roomLeave();",
            "      var channel = \"realtime:angon_partie_\" + pid;",
            "      var ws;",
            "      try {",
            "        ws = new WebSocket(",
            "          S.rtUrl + \"?apikey=\" + encodeURIComponent(S.rtKey) + \"&vsn=1.0.0\"",
            "        );",
            "      } catch (e) { return false; }",
            "      S.rtSocket = ws;",
            "      S.rtChannel = channel;",
            "      S.rtJoined = false;",
            "      ws.onopen = function () {",
            "        A.matchPresence(pid, false);",
            "        if (S.mpPresTimer) { clearInterval(S.mpPresTimer); }",
            "        S.mpPresTimer = setInterval(function () { A.matchPresence(pid, false); }, 30000);",
            "        A.rtSend({",
            "          topic: channel,",
            "          event: \"phx_join\",",
            "          payload: { config: { broadcast: { ack: false, self: false }, presence: { key: \"\" } } },",
            "          ref: \"1\",",
            "          join_ref: \"1\"",
            "        });",
            "        A.rtHeartbeat();",
            "      };",
            "      ws.onmessage = function (ev) {",
            "        var m;",
            "        try { m = JSON.parse(ev.data); } catch (e) { return; }",
            "        if (!m) { return; }",
            "        if (m.event === \"phx_reply\" && m.topic === channel && m.payload && m.payload.status === \"ok\") {",
            "          S.rtJoined = true;",
            "          if (S.mmPanelOpen) { A.closeMatchmakingPanel(false); }",
            "          return;",
            "        }",
            "        if (m.event === \"broadcast\" && m.topic === channel && m.payload) {",
            "          var evName = String(m.payload.event || \"\");",
            "          var pl = m.payload.payload !== undefined ? m.payload.payload : {};",
            "          S.rtLastEvent = evName;",
            "          if (evName === \"etat\") {",
            "            try { S.partieJson = JSON.stringify(pl); } catch (e) {}",
            "            S.rtMsgCount++;",
            "            return;",
            "          }",
            "          try {",
            "            S.rtLastMsg = JSON.stringify(pl);",
            "          } catch (e) { S.rtLastMsg = \"{}\"; }",
            "          S.rtMsgCount++;",
            "        }",
            "      };",
            "      var reopen = function () {",
            "        S.rtJoined = false;",
            "        if (S.rtHbTimer) { clearInterval(S.rtHbTimer); S.rtHbTimer = null; }",
            "        if (S.rtSocket === ws && S.rtChannel === channel && S.mmPartieId) {",
            "          if (S.rtRetryTimer) { clearTimeout(S.rtRetryTimer); }",
            "          S.rtRetryTimer = setTimeout(function () { A.roomJoin(pid); }, 2000);",
            "        }",
            "      };",
            "      ws.onclose = reopen;",
            "      ws.onerror = function () { try { ws.close(); } catch (e) {} };",
            "      return true;",
            "    });",
            "  };",
            "",
            "  /* Presence serveur : sans signe de vie de tous les joueurs pendant 90 s,",
            "     le salon est ferme automatiquement. */",
            "  A.matchPresence = function (pid, quitter) {",
            "    if (!pid) { return; }",
            "    try { A.request(\"/match/heartbeat\", \"POST\", { partie_id: pid, quitter: !!quitter }, true); } catch (e) {}",
            "  };",
            "",
            "  A.roomLeave = function () {",
            "    if (S.mpPresTimer) { clearInterval(S.mpPresTimer); S.mpPresTimer = null; }",
            "    if (S.rtChannel) { A.matchPresence(S.rtChannel.replace(\"realtime:angon_partie_\", \"\"), true); }",
            "    if (S.rtRetryTimer) { clearTimeout(S.rtRetryTimer); S.rtRetryTimer = null; }",
            "    if (S.rtHbTimer) { clearInterval(S.rtHbTimer); S.rtHbTimer = null; }",
            "    var ws = S.rtSocket;",
            "    S.rtSocket = null;",
            "    S.rtJoined = false;",
            "    S.rtChannel = \"\";",
            "    if (ws) {",
            "      try { ws.onclose = null; ws.onerror = null; ws.onmessage = null; ws.onopen = null; ws.close(); } catch (e) {}",
            "    }",
            "  };",
            "",
            "  /* Diffuse une action (coup joue) aux autres joueurs du salon. */",
            "  A.roomSend = function (variable) {",
            "    if (!S.rtSocket || !S.rtJoined || !S.rtChannel) { return false; }",
            "    var payload = A.getVariableData(variable);",
            "    A.rtSend({",
            "      topic: S.rtChannel,",
            "      event: \"broadcast\",",
            "      payload: { type: \"broadcast\", event: \"action\", payload: payload },",
            "      ref: null",
            "    });",
            "    return true;",
            "  };",
            "",
            "  /* Envoie une action de match via le relais serveur : les regles du jeu",
            "     (onglet Regles) la valident, maj_partie persiste l'etat, puis le serveur",
            "     rediffuse l'action aux autres joueurs du salon. */",
            "  A.matchAction = function (partieId, nom, donnees) {",
            "    var pid = String(partieId || S.mmPartieId || \"\").trim();",
            "    if (!pid || !nom) { return Promise.resolve({ ok: false, reason: \"missing_fields\" }); }",
            "    return A.request(\"/match/action\", \"POST\", { partie_id: pid, action: String(nom), donnees: donnees || {} }, true)",
            "      .then(function (r) {",
            "        var j = r.json || {};",
            "        if (r.ok && j.ok && j.partie) { S.partieJson = JSON.stringify(j.partie); }",
            "        return { ok: !!(r.ok && j.ok), partie: j.partie, reason: j.reason || j.error || S.errorCode || \"\" };",
            "      });",
            "  };",
            "",
            "  /* Recupere l'etat autoritaire de la partie (reconnexion / rafraichissement). */",
            "  A.matchState = function (partieId) {",
            "    var pid = String(partieId || S.mmPartieId || \"\").trim();",
            "    if (!pid) { return Promise.resolve({ ok: false }); }",
            "    return A.request(\"/match/state?partie_id=\" + encodeURIComponent(pid), \"GET\", null, true)",
            "      .then(function (r) {",
            "        var j = r.json || {};",
            "        if (r.ok && j.ok && j.partie) { S.partieJson = JSON.stringify(j.partie); }",
            "        return { ok: !!(r.ok && j.ok), partie: j.partie };",
            "      });",
            "  };",
            "",
            "  /* --- panneau de matchmaking integre (meme habillage que le panneau de connexion) --- */",
            "  A.esc = function (s) {",
            "    return String(s === undefined || s === null ? \"\" : s)",
            "      .replace(/&/g, \"&amp;\").replace(/</g, \"&lt;\").replace(/>/g, \"&gt;\")",
            "      .replace(/\"/g, \"&quot;\").replace(/'/g, \"&#39;\");",
            "  };",
            "",
            "  A.mmEnsureMotion = function () {",
            "    if (document.getElementById(\"angon-mm-motion\")) { return; }",
            "    var st = document.createElement(\"style\");",
            "    st.id = \"angon-mm-motion\";",
            "    st.textContent = \"@keyframes angon-spin{to{transform:rotate(360deg)}}@keyframes angon-pulse{0%,100%{transform:scale(0.8);opacity:0.5}50%{transform:scale(1.2);opacity:1}}@keyframes angon-mm-ring{0%{transform:scale(0.85);opacity:0.9}70%{transform:scale(1.3);opacity:0}100%{transform:scale(1.3);opacity:0}}[data-el=mm-cancel]:hover,[data-el=mm-close]:hover{opacity:0.88}@media(prefers-reduced-motion:reduce){[data-angon=mm-panel],[data-el=mm-card]{transition:none!important}}\";",
            "    document.head.appendChild(st);",
            "  };",
            "",
            "  /* Avatar rond : image si disponible, sinon initiale sur fond primaire. */",
            "  A.mmAvatarHtml = function (url, pseudo, size, highlight) {",
            "    var P = A.PALETTE;",
            "    var s = size || 56;",
            "    var inner;",
            "    if (url) {",
            "      inner = '<img src=\"' + A.esc(url) + '\" alt=\"\" style=\"width:100%;height:100%;object-fit:cover;display:block;\">';",
            "    } else {",
            "      var init = String(pseudo || \"?\").trim().charAt(0).toUpperCase() || \"?\";",
            "      inner = '<div style=\"width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-weight:800;font-size:' + Math.round(s * 0.42) + 'px;color:' + P.onPrimary + ';background:' + P.primary + ';\">' + A.esc(init) + '</div>';",
            "    }",
            "    return '<div style=\"width:' + s + 'px;height:' + s + 'px;border-radius:50%;overflow:hidden;flex:0 0 auto;border:2px solid ' + (highlight ? P.primary : P.border) + ';background:' + A.rgba(A.CONFIG.couleurTexte, 0.08) + ';box-sizing:border-box;\">' + inner + '</div>';",
            "  };",
            "",
            "  /* HTML de la carte selon l etat : recherche en cours ou match trouve. */",
            "  A.mmCardHtml = function () {",
            "    var P = A.PALETTE;",
            "    var C = A.CONFIG;",
            "    var isFr = A.isFrench();",
            "    var titre = A.getText(\"titre\", C.titre || \"\");",
            "    var h = \"\";",
            "    h += A.panelMosaicHtml();",
            "    h += '<div style=\"position:relative;flex:1 1 auto;min-height:0;box-sizing:border-box;overflow-y:auto;overflow-x:hidden;padding:22px;display:flex;flex-direction:column;align-items:center;text-align:center;\">';",
            "    h += '<div style=\"display:flex;align-items:center;justify-content:space-between;gap:10px;align-self:stretch;margin-bottom:16px;\">'",
            "      + '<div style=\"display:flex;align-items:center;gap:10px;min-width:0;\">';",
            "    if (C.logoUrl) {",
            "      h += '<img src=\"' + A.esc(C.logoUrl) + '\" alt=\"\" style=\"width:34px;height:34px;border-radius:9px;object-fit:cover;flex:0 0 auto;box-shadow:0 2px 8px rgba(0,0,0,0.25);\">';",
            "    } else {",
            "      h += '<div style=\"width:34px;height:34px;border-radius:9px;flex:0 0 auto;display:flex;align-items:center;justify-content:center;background:' + A.rgba(P.primary, 0.15) + ';border:1px solid ' + A.rgba(P.primary, 0.3) + ';\">' + A.panelIcon(\"shield\") + '</div>';",
            "    }",
            "    if (titre) {",
            "      h += '<div style=\"font-size:14px;font-weight:700;letter-spacing:0.02em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;\">' + A.esc(titre) + '</div>';",
            "    }",
            "    h += '</div>'",
            "      + '<span style=\"flex:0 0 auto;font-size:11px;font-weight:700;padding:4px 10px;border-radius:999px;background:' + A.rgba(P.primary, 0.14) + ';color:' + P.primary + ';border:1px solid ' + A.rgba(P.primary, 0.3) + ';\">' + A.esc(S.mmPanelMode) + '</span>'",
            "      + '</div>';",
            "",
            "    if (S.mmStatus === \"matched\") {",
            "      h += '<div style=\"' + A.headingStyle() + 'font-size:20px;color:' + P.primary + ';\">' + A.esc(A.getText(\"mmTitreTrouvee\", isFr ? \"Partie trouvée !\" : \"Match found!\")) + '</div>';",
            "      var parts = [];",
            "      try { parts = JSON.parse(S.mmParticipantsJson || \"[]\"); } catch (e) { parts = []; }",
            "      var teams = {};",
            "      var order = [];",
            "      parts.forEach(function (p) {",
            "        var t = Number(p && p.equipe) || 0;",
            "        if (!teams[t]) { teams[t] = []; order.push(t); }",
            "        teams[t].push(p);",
            "      });",
            "      if (order.length === 0) { order = [S.mmEquipe || 1]; teams[order[0]] = []; }",
            "      order.sort(function (a, b) {",
            "        if (a === S.mmEquipe) { return -1; }",
            "        if (b === S.mmEquipe) { return 1; }",
            "        return a - b;",
            "      });",
            "      var vous = A.getText(\"mmVous\", isFr ? \"Vous\" : \"You\");",
            "      h += '<div style=\"display:flex;align-items:stretch;justify-content:center;gap:12px;margin:16px 0 6px;\">';",
            "      for (var ti = 0; ti < order.length; ti++) {",
            "        var tnum = order[ti];",
            "        var own = tnum === S.mmEquipe;",
            "        if (ti > 0) {",
            "          h += '<div style=\"align-self:center;flex:0 0 auto;width:38px;height:38px;border-radius:50%;background:' + P.primary + ';color:' + P.onPrimary + ';display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:800;letter-spacing:0.03em;box-shadow:0 0 0 4px ' + A.rgba(P.primary, 0.15) + ',0 4px 14px ' + A.rgba(P.primary, 0.4) + ';\">' + A.esc(A.getText(\"mmContre\", \"VS\")) + '</div>';",
            "        }",
            "        h += '<div style=\"flex:1 1 0;min-width:0;display:flex;flex-direction:column;gap:8px;padding:12px;border-radius:14px;border:1px solid ' + (own ? A.rgba(P.primary, 0.55) : P.border) + ';background:' + (own ? A.rgba(P.primary, 0.08) : A.rgba(P.text, 0.03)) + ';\">';",
            "        var members = teams[tnum] || [];",
            "        for (var mi = 0; mi < members.length; mi++) {",
            "          var m = members[mi] || {};",
            "          var isMe = own && (String(m.joueur_id || \"\") === String(S.accountId || \"\") || String(m.pseudo || \"\") === String(S.pseudo || \"\"));",
            "          h += '<div style=\"display:flex;align-items:center;gap:8px;text-align:left;padding:3px 6px;margin:0 -6px;border-radius:9px;' + (isMe ? 'background:' + A.rgba(P.primary, 0.14) + ';' : '') + '\">'",
            "            + A.mmAvatarHtml(m.avatar || m.avatar_url || \"\", m.pseudo, 36, isMe)",
            "            + '<div style=\"font-size:12px;font-weight:600;min-width:0;flex:1 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:' + (isMe ? P.primary : P.text) + ';\">' + A.esc(String(m.pseudo || \"?\")) + '</div>'",
            "            + (isMe ? '<span style=\"flex:0 0 auto;font-size:9px;font-weight:800;text-transform:uppercase;letter-spacing:0.06em;padding:2px 6px;border-radius:999px;background:' + P.primary + ';color:' + P.onPrimary + ';\">' + A.esc(vous) + '</span>' : '')",
            "            + '</div>';",
            "        }",
            "        if (members.length === 0) { h += '<div style=\"font-size:12px;color:' + P.muted + ';\">...</div>'; }",
            "        h += '</div>';",
            "      }",
            "      h += '</div>';",
            "      h += '<div style=\"display:flex;align-items:center;gap:8px;margin:14px 0 4px;color:' + P.muted + ';font-size:13px;\">'",
            "        + '<div style=\"width:15px;height:15px;box-sizing:border-box;border-radius:50%;border:2px solid ' + A.rgba(P.primary, 0.25) + ';border-top-color:' + P.primary + ';animation:angon-spin 0.9s linear infinite;flex:0 0 auto;\"></div>'",
            "        + '<span>' + A.esc(A.getText(\"mmTexteLancement\", isFr ? \"En attente du lancement de la partie...\" : \"Waiting for the game to start...\")) + '</span>'",
            "        + '</div>';",
            "      h += '<button type=\"button\" data-el=\"mm-close\" style=\"margin-top:10px;padding:9px 18px;border-radius:10px;border:1px solid ' + P.border + ';background:transparent;color:' + P.muted + ';font-family:inherit;font-size:12px;font-weight:600;cursor:pointer;\">' + A.esc(A.getText(\"boutonAnnuler\", isFr ? \"Fermer\" : \"Close\")) + '</button>';",
            "    } else {",
            "      h += '<div style=\"' + A.headingStyle() + 'font-size:20px;margin-bottom:14px;\">' + A.esc(A.getText(\"mmTitreRecherche\", isFr ? \"Recherche de partie...\" : \"Searching for a match...\")) + '</div>';",
            "      h += '<div style=\"position:relative;width:96px;height:96px;margin:2px 0 16px;\">'",
            "        + '<div style=\"position:absolute;inset:-7px;border-radius:50%;border:2px solid ' + A.rgba(P.primary, 0.6) + ';animation:angon-mm-ring 1.8s ease-out infinite;pointer-events:none;\"></div>'",
            "        + A.mmAvatarHtml(S.avatarUrl, S.pseudo || \"?\", 96, true)",
            "        + '<div style=\"position:absolute;right:-2px;bottom:-2px;width:26px;height:26px;border-radius:50%;background:' + P.card + ';border:2px solid ' + P.border + ';display:flex;align-items:center;justify-content:center;box-sizing:border-box;\">'",
            "        + '<div style=\"width:14px;height:14px;box-sizing:border-box;border-radius:50%;border:2px solid ' + A.rgba(P.primary, 0.25) + ';border-top-color:' + P.primary + ';animation:angon-spin 0.8s linear infinite;\"></div>'",
            "        + '</div></div>';",
            "      h += '<div style=\"font-size:15px;font-weight:700;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;\">' + A.esc(S.pseudo || (isFr ? \"Joueur\" : \"Player\")) + '</div>';",
            "      h += '<div style=\"font-size:13px;color:' + P.muted + ';line-height:1.5;margin:8px 0 4px;max-width:280px;\">' + A.esc(A.getText(\"mmTexteRecherche\", isFr ? \"Un instant, nous cherchons des joueurs pour vous.\" : \"Hang tight, we're finding players for you.\")) + '</div>';",
            "      if (S.mmPosition > 0) {",
            "        h += '<div style=\"font-size:11px;color:' + P.muted + ';margin-bottom:6px;\">' + (isFr ? \"Position dans la file : \" : \"Queue position: \") + S.mmPosition + '</div>';",
            "      }",
            "      h += '<button type=\"button\" data-el=\"mm-cancel\" style=\"margin-top:14px;min-height:40px;padding:10px 20px;border-radius:10px;background:' + A.rgba(P.text, 0.07) + ';color:' + P.text + ';font-family:inherit;font-size:13px;font-weight:600;cursor:pointer;border:1px solid ' + P.border + ';\">' + A.esc(A.getText(\"mmBoutonAnnuler\", isFr ? \"Annuler la recherche\" : \"Cancel search\")) + '</button>';",
            "    }",
            "    h += '</div>';",
            "    return h;",
            "  };",
            "",
            "  A.mmBind = function () {",
            "    var root = S.mmPanelRoot;",
            "    if (!root) { return; }",
            "    var cancel = root.querySelector('[data-el=\"mm-cancel\"]');",
            "    if (cancel) { cancel.onclick = function () { A.closeMatchmakingPanel(true); }; }",
            "    var close = root.querySelector('[data-el=\"mm-close\"]');",
            "    if (close) { close.onclick = function () { A.closeMatchmakingPanel(false); }; }",
            "  };",
            "",
            "  A.mmRender = function () {",
            "    var root = S.mmPanelRoot;",
            "    if (!root) { return; }",
            "    var card = root.querySelector('[data-el=\"mm-card\"]');",
            "    if (!card) { return; }",
            "    card.style.maxWidth = S.mmStatus === \"matched\" ? \"620px\" : \"420px\";",
            "    card.innerHTML = A.mmCardHtml();",
            "    A.mmBind();",
            "  };",
            "",
            "  A.mmStopPoll = function () {",
            "    if (S.mmPanelTimer) { clearInterval(S.mmPanelTimer); S.mmPanelTimer = null; }",
            "  };",
            "",
            "  /* Poll le statut tant que le panneau cherche un match. */",
            "  A.mmStartPoll = function () {",
            "    A.mmStopPoll();",
            "    S.mmPanelTimer = setInterval(function () {",
            "      if (!S.mmPanelOpen || S.mmStatus !== \"waiting\") { A.mmStopPoll(); return; }",
            "      A.matchmakingStatus().then(function () {",
            "        if (S.mmStatus === \"matched\") { A.mmStopPoll(); }",
            "        if (S.mmPanelOpen) { A.mmRender(); }",
            "      });",
            "    }, 2500);",
            "  };",
            "",
            "  A.buildMmPanel = function (runtimeScene) {",
            "    A.PALETTE = A.theme();",
            "    A.loadFont();",
            "    var P = A.PALETTE;",
            "    var root = document.createElement(\"div\");",
            "    root.setAttribute(\"data-angon\", \"mm-panel\");",
            "    root.style.cssText = \"position:absolute;left:0;top:0;width:100%;height:100%;box-sizing:border-box;display:flex;align-items:center;justify-content:center;overflow:hidden;padding:12px;background:\" + P.veil + \";z-index:1000;pointer-events:auto;font-family:\" + A.fontStack() + \";opacity:0;transition:opacity 220ms ease;\";",
            "    root.innerHTML = '<div data-el=\"mm-card\" style=\"position:relative;margin:auto;width:100%;max-width:420px;max-height:min(100%,620px);display:flex;flex-direction:column;box-sizing:border-box;overflow:hidden;background:' + P.card + ';border:0;border-radius:16px;color:' + P.text + ';box-shadow:0 20px 60px rgba(0,0,0,0.6);transition:opacity 240ms ease,transform 240ms ease,max-width 260ms ease;\"></div>';",
            "    var card = root.firstChild;",
            "    if (card) {",
            "      card.style.maxWidth = S.mmStatus === \"matched\" ? \"620px\" : \"420px\";",
            "      card.innerHTML = A.mmCardHtml();",
            "    }",
            "    S.mmPanelRoot = root;",
            "    A.mmBind();",
            "    S.mmPanelKeyHandler = function (ev) {",
            "      if (!S.mmPanelOpen) { return; }",
            "      if (ev.key === \"Escape\") {",
            "        ev.preventDefault();",
            "        A.closeMatchmakingPanel(true);",
            "      }",
            "    };",
            "    document.addEventListener(\"keydown\", S.mmPanelKeyHandler);",
            "    A.mmEnsureMotion();",
            "    A.domContainer(runtimeScene).appendChild(root);",
            "    var show = function () { root.style.opacity = \"1\"; };",
            "    if (typeof requestAnimationFrame !== \"undefined\") { requestAnimationFrame(show); } else { show(); }",
            "  };",
            "",
            "  /* Ouvre le panneau, rejoint la file et lance la recherche automatiquement. */",
            "  A.openMatchmakingPanel = function (runtimeScene, mode) {",
            "    S.mmPanelMode = String(mode || \"1v1\").trim() || \"1v1\";",
            "    return A.ensurePanelConfig().then(function () {",
            "      if (!S.loggedIn) {",
            "        A.openPanel(runtimeScene, \"login\", function () {",
            "          if (S.loggedIn) { A.openMatchmakingPanel(runtimeScene, S.mmPanelMode); }",
            "        });",
            "        return false;",
            "      }",
            "      S.mmPanelOpen = true;",
            "      if (!S.mmPanelRoot) {",
            "        A.buildMmPanel(runtimeScene);",
            "      } else {",
            "        A.mmRender();",
            "      }",
            "      if (S.mmStatus === \"matched\") {",
            "        A.mmRender();",
            "      } else {",
            "        A.matchmakingJoin(S.mmPanelMode).then(function () {",
            "          if (S.mmPanelOpen) { A.mmRender(); }",
            "        });",
            "        A.mmStartPoll();",
            "      }",
            "      return true;",
            "    });",
            "  };",
            "",
            "  /* Ferme le panneau ; cancelQueue retire le joueur de la file si la recherche tourne encore. */",
            "  A.closeMatchmakingPanel = function (cancelQueue) {",
            "    A.mmStopPoll();",
            "    if (S.mmPanelKeyHandler) {",
            "      document.removeEventListener(\"keydown\", S.mmPanelKeyHandler);",
            "      S.mmPanelKeyHandler = null;",
            "    }",
            "    var root = S.mmPanelRoot;",
            "    S.mmPanelOpen = false;",
            "    if (cancelQueue && S.mmStatus === \"waiting\") { A.matchmakingLeave(); }",
            "    if (!root) { return; }",
            "    root.style.opacity = \"0\";",
            "    var card = root.querySelector('[data-el=\"mm-card\"]');",
            "    if (card) { card.style.opacity = \"0\"; card.style.transform = \"translateY(10px) scale(0.98)\"; }",
            "    setTimeout(function () {",
            "      if (root.parentNode) { root.parentNode.removeChild(root); }",
            "      if (S.mmPanelRoot === root) { S.mmPanelRoot = null; }",
            "    }, 220);",
            "  };",
            "",
            "  /* --- operations d authentification --- */",
            "  A.login = function (email, password) {",
            "    var body = { email: email, password: password };",
            "    var pays = A.detectPays();",
            "    var countryCode = A.detectCountryCode();",
            "    if (pays) { body.pays = pays; }",
            "    if (countryCode) { body.country_code = countryCode; }",
            "    return A.request(\"/players/login\", \"POST\", body, false)",
            "      .then(function (r) {",
            "        if (r.ok && r.json && r.json.ok) { A.applyAccount(r.json, true); return true; }",
            "        return false;",
            "      });",
            "  };",
            "",
            "  A.register = function (pseudo, email, password) {",
            "    var body = { email: email, password: password, pseudo: pseudo };",
            "    var pays = A.detectPays();",
            "    var countryCode = A.detectCountryCode();",
            "    if (pays) { body.pays = pays; }",
            "    if (countryCode) { body.country_code = countryCode; }",
            "    return A.request(\"/players/register\", \"POST\", body, false)",
            "      .then(function (r) {",
            "        if (r.ok && r.json && r.json.ok) { A.applyAccount(r.json, true); return true; }",
            "        return false;",
            "      });",
            "  };",
            "",
            "  A.loginWithGoogle = function () {",
            "    return A.request(\"/players/oauth/start\", \"POST\", { provider: \"google\" }, false)",
            "      .then(function (r) {",
            "        if (!r.ok || !r.json || !r.json.ok || !r.json.code) { return false; }",
            "        var code = r.json.code;",
            "        var authUrl = r.json.auth_url;",
            "        try {",
            "          if (typeof window !== \"undefined\" && window.open) { window.open(authUrl, \"_blank\"); }",
            "        } catch (e) {}",
            "        return new Promise(function (resolve) {",
            "          var attempts = 0;",
            "          var pollTid = setInterval(function () {",
            "            attempts++;",
            "            if (attempts > 240) {",
            "              clearInterval(pollTid);",
            "              resolve(false);",
            "              return;",
            "            }",
            "            A.request(\"/players/oauth/poll?code=\" + encodeURIComponent(code), \"GET\", null, false)",
            "              .then(function (pr) {",
            "                if (pr.ok && pr.json && pr.json.ok && pr.json.status === \"done\") {",
            "                  clearInterval(pollTid);",
            "                  A.applyAccount(pr.json, true);",
            "                  resolve(true);",
            "                } else if (pr.json && pr.json.status === \"expired\") {",
            "                  clearInterval(pollTid);",
            "                  resolve(false);",
            "                }",
            "              })",
            "              .catch(function () {});",
            "          }, 1500);",
            "        });",
            "      });",
            "  };",
            "",
            "  A.loadProfile = function (markLogin) {",
            "    return A.request(\"/players/me\", \"GET\", null, true).then(function (r) {",
            "      if (r.ok && r.json && r.json.ok) {",
            "        A.applyAccount(r.json, markLogin);",
            "        S.loggedIn = true;",
            "        return true;",
            "      }",
            "      if (S.statusCode === 401 || S.statusCode === 403) { A.reset(); }",
            "      return false;",
            "    });",
            "  };",
            "",
            "  A.restore = function () {",
            "    var saved = A.storageGet(A.TOKEN_KEY);",
            "    if (!saved) {",
            "      S.statusCode = 0;",
            "      S.errorCode = \"no_saved_session\";",
            "      S.errorMessage = \"No saved session on this device.\";",
            "      return Promise.resolve(false);",
            "    }",
            "    S.token = saved;",
            "    return A.loadProfile(true);",
            "  };",
            "",
            "  /* --- restauration automatique au demarrage du jeu --- */",
            "  A.autoRestore = function () {",
            "    if (typeof navigator !== \"undefined\" && navigator.onLine !== false) {",
            "      A.fetchTablePolicies();",
            "    }",
            "    if (S.restoring || S.restoreDone) { return Promise.resolve(S.loggedIn); }",
            "    A.loadRemember();",
            "    var saved = S.remember ? A.storageGet(A.TOKEN_KEY) : \"\";",
            "    if (!saved) {",
            "      S.restoreDone = true;",
            "      return Promise.resolve(false);",
            "    }",
            "    /* Session consideree active immediatement : les conditions du jeu",
            "       n ouvrent donc pas le panneau pendant la verification serveur. */",
            "    S.token = saved;",
            "    S.loggedIn = true;",
            "    S.restoring = true;",
            "    A.syncPendingQueue();",
            "    return A.loadProfile(true).then(function (ok) {",
            "      S.restoring = false;",
            "      S.restoreDone = true;",
            "      if (!ok && S.statusCode !== 0) { S.loggedIn = false; }",
            "      return ok;",
            "    }, function () {",
            "      S.restoring = false;",
            "      S.restoreDone = true;",
            "      return false;",
            "    });",
            "  };",
            "",
            "  /* --- televersement de photo de profil vers FreeImage.host via l API Angon --- */",
            "  A.uploadAvatar = function (base64Image) {",
            "    return A.request(\"/players/avatar\", \"POST\", { image: base64Image }, true)",
            "      .then(function (r) {",
            "        var log = {",
            "          ok: r.ok,",
            "          status: S.statusCode,",
            "          code: S.errorCode || \"\",",
            "          message: S.errorMessage || \"\",",
            "          avatar_url: (r.json && r.json.avatar_url) ? r.json.avatar_url : S.avatarUrl,",
            "          raw: S.lastResponse || \"\"",
            "        };",
            "        if (r.ok && r.json) {",
            "          if (r.json.avatar_url) {",
            "            S.avatarUrl = String(r.json.avatar_url);",
            "          }",
            "          A.applyAccount(r.json);",
            "          return { ok: true, avatar_url: S.avatarUrl, log: log };",
            "        }",
            "        return { ok: false, avatar_url: S.avatarUrl, log: log };",
            "      });",
            "  };",
            "",
            "  /* --- Stockage de fichiers via l API Angon Cloud --- */",
            "  A.uploadFile = function (fileName, content, mimeType, ecraser) {",
            "    var payload = {",
            "      nom: fileName,",
            "      contenu: content,",
            "      mime_type: mimeType || undefined,",
            "      ecraser: ecraser !== false",
            "    };",
            "    return A.request(\"/storage/upload\", \"POST\", payload, true)",
            "      .then(function (r) {",
            "        var f = (r.json && r.json.fichier) ? r.json.fichier : null;",
            "        var log = {",
            "          ok: r.ok,",
            "          status: S.statusCode,",
            "          code: S.errorCode || \"\",",
            "          message: S.errorMessage || \"\",",
            "          fichier: f,",
            "          url: f ? (f.url_visualisation || f.url_telechargement || \"\") : \"\",",
            "          raw: S.lastResponse || \"\"",
            "        };",
            "        if (r.ok && f) {",
            "          S.lastFileUrl = f.url_visualisation || f.url_telechargement || \"\";",
            "          S.lastFileId = f.drive_file_id || f.id || \"\";",
            "          S.lastFileName = f.nom || fileName;",
            "          S.lastFileSize = f.taille_octets || 0;",
            "          S.lastUploadSuccess = true;",
            "          return { ok: true, fichier: f, log: log };",
            "        }",
            "        S.lastUploadSuccess = false;",
            "        return { ok: false, fichier: null, log: log };",
            "      });",
            "  };",
            "",
            "  A.deleteFile = function (fileIdOrName) {",
            "    return A.request(\"/storage/files/\" + encodeURIComponent(fileIdOrName), \"DELETE\", null, true)",
            "      .then(function (r) {",
            "        var log = {",
            "          ok: r.ok,",
            "          status: S.statusCode,",
            "          code: S.errorCode || \"\",",
            "          message: S.errorMessage || \"\",",
            "          raw: S.lastResponse || \"\"",
            "        };",
            "        return { ok: r.ok, log: log };",
            "      });",
            "  };",
            "",
            "  A.listFiles = function () {",
            "    return A.request(\"/storage/files\", \"GET\", null, true)",
            "      .then(function (r) {",
            "        var list = (r.json && Array.isArray(r.json.fichiers)) ? r.json.fichiers : [];",
            "        S.playerFilesJson = JSON.stringify(list);",
            "        var log = {",
            "          ok: r.ok,",
            "          status: S.statusCode,",
            "          code: S.errorCode || \"\",",
            "          message: S.errorMessage || \"\",",
            "          count: list.length,",
            "          fichiers: list,",
            "          raw: S.lastResponse || \"\"",
            "        };",
            "        return { ok: r.ok, fichiers: list, log: log };",
            "      });",
            "  };",
            "",
            "  /* --- panneau HTML de connexion --- */",
            "  A.domContainer = function (runtimeScene) {",
            "    try {",
            "      var renderer = runtimeScene.getGame().getRenderer();",
            "      if (renderer && renderer.getDomElementContainer) {",
            "        var c = renderer.getDomElementContainer();",
            "        if (c) { return c; }",
            "      }",
            "    } catch (e) {}",
            "    return document.body;",
            "  };",
            "",
            "  A.panelError = function (msg) {",
            "    var e = S.panelEls;",
            "    if (!e) { return; }",
            "    e.error.textContent = msg;",
            "    e.error.style.display = \"block\";",
            "  };",
            "",
            "  A.panelClearError = function () {",
            "    var e = S.panelEls;",
            "    if (!e) { return; }",
            "    e.error.textContent = \"\";",
            "    e.error.style.display = \"none\";",
            "  };",
            "",
            "  A.setMode = function (mode) {",
            "    var e = S.panelEls;",
            "    S.panelMode = (mode === \"signup\") ? \"signup\" : \"login\";",
            "    if (!e) { return; }",
            "    var P = A.PALETTE;",
            "    var C = A.CONFIG;",
            "    var signup = S.panelMode === \"signup\";",
            "    var outgoing = signup ? e.paneLogin : e.paneSignup;",
            "    var incoming = signup ? e.paneSignup : e.paneLogin;",
            "    if (S.panelModeTimer) { clearTimeout(S.panelModeTimer); S.panelModeTimer = null; }",
            "    if (outgoing && incoming && outgoing.isConnected && outgoing.style.display !== \"none\") {",
            "      outgoing.style.opacity = \"0\";",
            "      outgoing.style.transform = \"translateY(-6px)\";",
            "      S.panelModeTimer = setTimeout(function () {",
            "        outgoing.style.display = \"none\";",
            "        incoming.style.display = \"block\";",
            "        incoming.style.opacity = \"0\";",
            "        incoming.style.transform = \"translateY(6px)\";",
            "        var animateIn = function () {",
            "          incoming.style.opacity = \"1\";",
            "          incoming.style.transform = \"translateY(0)\";",
            "        };",
            "        if (typeof requestAnimationFrame !== \"undefined\") {",
            "          requestAnimationFrame(animateIn);",
            "        } else {",
            "          animateIn();",
            "        }",
            "        S.panelModeTimer = null;",
            "      }, 150);",
            "    } else {",
            "      if (e.paneLogin) { e.paneLogin.style.display = signup ? \"none\" : \"block\"; }",
            "      if (e.paneSignup) { e.paneSignup.style.display = signup ? \"block\" : \"none\"; }",
            "      if (incoming) {",
            "        incoming.style.opacity = \"1\";",
            "        incoming.style.transform = \"translateY(0)\";",
            "      }",
            "    }",
            "    if (e.heading) {",
            "      e.heading.textContent = signup",
            "        ? A.getText(\"enteteInscription\", \"Create an account\")",
            "        : A.getText(\"enteteConnexion\", \"Sign in\");",
            "    }",
            "    if (e.subtitle) {",
            "      e.subtitle.textContent = signup",
            "        ? A.getText(\"texteInscription\", C.texteInscription)",
            "        : A.getText(\"texteConnexion\", C.texteConnexion);",
            "    }",
            "    var submitTxt = signup",
            "      ? A.getText(\"boutonInscription\", \"Create my account\")",
            "      : A.getText(\"boutonConnexion\", \"Sign in\");",
            "    if (e.submitLabel) {",
            "      e.submitLabel.textContent = submitTxt;",
            "    } else if (e.submit) {",
            "      e.submit.textContent = submitTxt;",
            "    }",
            "    if (e.switchBtn) {",
            "      e.switchBtn.textContent = signup",
            "        ? A.getText(\"boutonPasserConnexion\", \"← Sign in\")",
            "        : A.getText(\"boutonPasserInscription\", \"Create an account →\");",
            "      e.switchBtn.style.color = P.primary;",
            "    }",
            "    if (e.optionsLogin) { e.optionsLogin.style.display = (!signup && (C.seSouvenir || C.motDePasseOublie)) ? \"flex\" : \"none\"; }",
            "    if (e.optionsSignup) { e.optionsSignup.style.display = (signup && C.conditions) ? \"flex\" : \"none\"; }",
            "    if (e.remember) { e.remember.style.display = C.seSouvenir ? \"flex\" : \"none\"; }",
            "    if (e.forgot) { e.forgot.style.display = C.motDePasseOublie ? \"inline\" : \"none\"; }",
            "    if (e.oauthView && e.oauthView.style.display !== \"none\") { A.cancelOAuth(); }",
            "    A.panelClearError();",
            "    try { (signup ? e.inPseudo : e.inLoginEmail).focus(); } catch (err) {}",
            "  };",
            "",
            "  A.startGoogleOAuth = function () {",
            "    var e = S.panelEls;",
            "    if (!e || S.pending) { return; }",
            "    A.panelClearError();",
            "    var isFr = A.isFrench();",
            "    var P = A.PALETTE;",
            "    if (typeof navigator !== \"undefined\" && navigator.onLine === false) {",
            "      A.panelError(isFr ? \"Connexion internet requise pour Google.\" : \"Internet connection required for Google sign-in.\");",
            "      return;",
            "    }",
            "    if (e.googleBtn) {",
            "      e.googleBtn.disabled = true;",
            "      e.googleBtn.style.opacity = \"0.7\";",
            "    }",
            "    A.request(\"/players/oauth/start\", \"POST\", { provider: \"google\" }, false)",
            "      .then(function (r) {",
            "        if (e.googleBtn) {",
            "          e.googleBtn.disabled = false;",
            "          e.googleBtn.style.opacity = \"1\";",
            "        }",
            "        if (!r.ok || !r.json || !r.json.ok || !r.json.code) {",
            "          A.panelError(S.errorMessage || (isFr ? \"Impossible de démarrer la connexion Google.\" : \"Could not start Google sign-in.\"));",
            "          return;",
            "        }",
            "        var code = r.json.code;",
            "        var authUrl = r.json.auth_url;",
            "        S.oauthCode = code;",
            "",
            "        if (e.formView) { e.formView.style.display = \"none\"; }",
            "        if (e.oauthView) { e.oauthView.style.display = \"flex\"; }",
            "        if (e.oauthTitle) {",
            "          e.oauthTitle.textContent = A.getText(\"attenteOAuthTitre\", isFr ? \"Connexion avec Google...\" : \"Connecting with Google...\");",
            "          e.oauthTitle.style.color = P.text;",
            "        }",
            "        if (e.oauthMsg) {",
            "          e.oauthMsg.textContent = A.getText(\"attenteOAuthDescription\", isFr ? \"Veuillez finaliser la connexion dans la fenêtre ouverte dans votre navigateur.\" : \"Please complete sign-in in the browser window that just opened.\");",
            "        }",
            "        if (e.oauthLink) {",
            "          e.oauthLink.href = authUrl;",
            "          e.oauthLink.style.display = \"inline-block\";",
            "        }",
            "",
            "        try {",
            "          if (typeof window !== \"undefined\" && window.open) {",
            "            window.open(authUrl, \"_blank\");",
            "          }",
            "        } catch (err) {}",
            "",
            "        if (S.oauthPollInterval) { clearInterval(S.oauthPollInterval); }",
            "        S.oauthPollInterval = setInterval(function () {",
            "          if (!S.panelOpen) {",
            "            clearInterval(S.oauthPollInterval);",
            "            S.oauthPollInterval = null;",
            "            return;",
            "          }",
            "          A.request(\"/players/oauth/poll?code=\" + encodeURIComponent(code), \"GET\", null, false)",
            "            .then(function (pr) {",
            "              if (!S.panelOpen || !S.oauthPollInterval) { return; }",
            "              if (pr.ok && pr.json && pr.json.ok && pr.json.status === \"done\") {",
            "                clearInterval(S.oauthPollInterval);",
            "                S.oauthPollInterval = null;",
            "                A.applyAccount(pr.json, true);",
            "                if (e.oauthTitle) {",
            "                  e.oauthTitle.textContent = isFr ? \"Connexion réussie !\" : \"Sign-in successful!\";",
            "                  e.oauthTitle.style.color = P.primary;",
            "                }",
            "                if (e.oauthMsg) {",
            "                  e.oauthMsg.textContent = isFr ? \"Bienvenue \" + (S.pseudo || \"\") + \" !\" : \"Welcome \" + (S.pseudo || \"\") + \"!\";",
            "                }",
            "                setTimeout(function () {",
            "                  A.closePanel();",
            "                }, 700);",
            "              } else if (pr.json && pr.json.status === \"expired\") {",
            "                clearInterval(S.oauthPollInterval);",
            "                S.oauthPollInterval = null;",
            "                A.cancelOAuth(isFr ? \"La session de connexion Google a expiré.\" : \"Google sign-in session expired.\");",
            "              }",
            "            })",
            "            .catch(function () {});",
            "        }, 1500);",
            "      });",
            "  };",
            "",
            "  A.cancelOAuth = function (msg) {",
            "    if (S.oauthPollInterval) {",
            "      clearInterval(S.oauthPollInterval);",
            "      S.oauthPollInterval = null;",
            "    }",
            "    var e = S.panelEls;",
            "    if (!e) { return; }",
            "    if (e.oauthView) { e.oauthView.style.display = \"none\"; }",
            "    if (e.formView) { e.formView.style.display = \"block\"; }",
            "    if (msg) {",
            "      A.panelError(msg);",
            "    }",
            "  };",
            "",
            "  A.submitPanel = function () {",
            "    var e = S.panelEls;",
            "    if (!e || S.pending) { return; }",
            "    A.panelClearError();",
            "    var isFr = A.isFrench();",
            "    var busy = function (on) {",
            "      e.submit.disabled = on;",
            "      e.submit.style.opacity = on ? \"0.82\" : \"1\";",
            "      e.submit.style.cursor = on ? \"wait\" : \"pointer\";",
            "      e.submit.setAttribute(\"aria-busy\", on ? \"true\" : \"false\");",
            "      if (e.submitLabel) {",
            "        var isSignup = S.panelMode === \"signup\";",
            "        e.submitLabel.textContent = on",
            "          ? (isSignup ? (isFr ? \"Création du compte...\" : \"Creating account...\") : (isFr ? \"Connexion en cours...\" : \"Signing in...\"))",
            "          : (isSignup ? A.getText(\"boutonInscription\", \"Create my account\") : A.getText(\"boutonConnexion\", \"Sign in\"));",
            "      }",
            "      if (e.spinner) { e.spinner.style.display = on ? \"inline-block\" : \"none\"; }",
            "    };",
            "    var done = function (ok) {",
            "      busy(false);",
            "      if (ok) { A.closePanel(); }",
            "      else { A.panelError(S.errorMessage || (isFr ? \"La requête a échoué.\" : \"The request failed.\")); }",
            "    };",
            "    if (S.panelMode === \"signup\") {",
            "      var pseudo = e.inPseudo.value.trim();",
            "      var mail = e.inSignupEmail.value.trim();",
            "      var pw = e.inSignupPassword.value;",
            "      var confirm = e.inSignupConfirm.value;",
            "      if (pseudo.length < 2) { A.panelError(isFr ? \"Le pseudo doit comporter au moins 2 caractères.\" : \"Username must be at least 2 characters.\"); return; }",
            "      if (!mail) { A.panelError(isFr ? \"Saisissez votre adresse e-mail.\" : \"Enter your email address.\"); return; }",
            "      if (pw.length < 8) { A.panelError(isFr ? \"Le mot de passe doit comporter au moins 8 caractères.\" : \"Password must be at least 8 characters.\"); return; }",
            "      if (pw !== confirm) { A.panelError(isFr ? \"Les mots de passe ne correspondent pas.\" : \"Passwords do not match.\"); return; }",
            "      busy(true);",
            "      A.register(pseudo, mail, pw).then(done);",
            "      return;",
            "    }",
            "    var lmail = e.inLoginEmail.value.trim();",
            "    var lpw = e.inLoginPassword.value;",
            "    if (!lmail || !lpw) { A.panelError(isFr ? \"Saisissez votre e-mail et votre mot de passe.\" : \"Enter your email and password.\"); return; }",
            "    busy(true);",
            "    A.login(lmail, lpw).then(done);",
            "  };",
            "",
            "  A.closePanel = function () {",
            "    if (S.panelClosing) { return; }",
            "    S.panelClosing = true;",
            "    if (S.oauthPollInterval) {",
            "      clearInterval(S.oauthPollInterval);",
            "      S.oauthPollInterval = null;",
            "    }",
            "    if (S.panelKeyHandler) {",
            "      document.removeEventListener(\"keydown\", S.panelKeyHandler);",
            "      S.panelKeyHandler = null;",
            "    }",
            "    if (S.panelOnlineHandler) {",
            "      window.removeEventListener(\"online\", S.panelOnlineHandler);",
            "      S.panelOnlineHandler = null;",
            "    }",
            "    if (S.panelOfflineHandler) {",
            "      window.removeEventListener(\"offline\", S.panelOfflineHandler);",
            "      S.panelOfflineHandler = null;",
            "    }",
            "    var root = S.panelRoot;",
            "    var finish = function () {",
            "      if (root && root.parentNode) { root.parentNode.removeChild(root); }",
            "      S.panelRoot = null;",
            "      S.panelEls = null;",
            "      S.panelOpen = false;",
            "      S.panelClosing = false;",
            "      var cb = S.panelOnClose;",
            "      S.panelOnClose = null;",
            "      if (cb) {",
            "        try { cb(); } catch (e) { console.error(\"[AngonCloud] \", e); }",
            "      }",
            "    };",
            "    if (!root) { finish(); return; }",
            "    root.style.opacity = \"0\";",
            "    var card = root.querySelector('[data-el=\"panel-card\"]');",
            "    if (card) { card.style.opacity = \"0\"; card.style.transform = \"translateY(10px) scale(0.98)\"; }",
            "    setTimeout(finish, 220);",
            "  };",
            "",
            "  A.showNetChecking = function () {",
            "    var e = S.panelEls;",
            "    if (!e) { return; }",
            "    if (e.formView) { e.formView.style.display = \"none\"; }",
            "    if (e.switchBtn) { e.switchBtn.style.visibility = \"hidden\"; }",
            "    if (e.netStatus) { e.netStatus.style.display = \"flex\"; }",
            "    if (e.netChecking) { e.netChecking.style.display = \"flex\"; }",
            "    if (e.netOffline) { e.netOffline.style.display = \"none\"; }",
            "  };",
            "",
            "  A.showNetOffline = function () {",
            "    var e = S.panelEls;",
            "    if (!e) { return; }",
            "    if (e.formView) { e.formView.style.display = \"none\"; }",
            "    if (e.switchBtn) { e.switchBtn.style.visibility = \"hidden\"; }",
            "    if (e.netStatus) { e.netStatus.style.display = \"flex\"; }",
            "    if (e.netChecking) { e.netChecking.style.display = \"none\"; }",
            "    if (e.netOffline) { e.netOffline.style.display = \"flex\"; }",
            "  };",
            "",
            "  A.showNetOnline = function () {",
            "    var e = S.panelEls;",
            "    if (!e) { return; }",
            "    if (e.netStatus) { e.netStatus.style.display = \"none\"; }",
            "    if (e.netChecking) { e.netChecking.style.display = \"none\"; }",
            "    if (e.netOffline) { e.netOffline.style.display = \"none\"; }",
            "    if (e.switchBtn) { e.switchBtn.style.visibility = \"visible\"; }",
            "    if (e.formView) {",
            "      e.formView.style.display = \"block\";",
            "      e.formView.style.opacity = \"0\";",
            "      e.formView.style.transform = \"translateY(6px)\";",
            "      e.formView.style.transition = \"opacity 200ms ease, transform 200ms ease\";",
            "      var reveal = function () {",
            "        if (e.formView) {",
            "          e.formView.style.opacity = \"1\";",
            "          e.formView.style.transform = \"translateY(0)\";",
            "        }",
            "      };",
            "      if (typeof requestAnimationFrame !== \"undefined\") {",
            "        requestAnimationFrame(reveal);",
            "      } else {",
            "        reveal();",
            "      }",
            "    }",
            "    A.setMode(S.panelMode);",
            "  };",
            "",
            "  A.loadAndApplyConfig = function (runtimeScene) {",
            "    if (!S.panelOpen) { return; }",
            "    if (typeof navigator !== \"undefined\" && navigator.onLine === false) {",
            "      A.showNetOffline();",
            "      return;",
            "    }",
            "    A.showNetChecking();",
            "    A.panelConfigPromise = null;",
            "    A.ensurePanelConfig().then(function (ok) {",
            "      if (!S.panelOpen) { return; }",
            "      if (ok) {",
            "        if (S.panelRoot) {",
            "          S.panelRoot.innerHTML = A.buildCardHTML();",
            "          A.bindCardEvents(runtimeScene);",
            "          S.panelRoot.style.fontFamily = A.fontStack();",
            "        }",
            "        A.showNetOnline();",
            "      } else {",
            "        A.showNetOffline();",
            "      }",
            "    });",
            "  };",
            "",
            "  A.checkNetwork = function (runtimeScene) {",
            "    return A.loadAndApplyConfig(runtimeScene);",
            "  };",
            "",
            "  A.openPanel = function (runtimeScene, mode, onClose) {",
            "    if (S.panelOpen) {",
            "      A.setMode(mode);",
            "      if (onClose) { S.panelOnClose = onClose; }",
            "      return;",
            "    }",
            "    S.panelOpen = true;",
            "    S.panelMode = (mode === \"signup\") ? \"signup\" : \"login\";",
            "    S.panelOnClose = onClose || null;",
            "",
            "    if (!S.panelRoot) {",
            "      A.buildPanel(runtimeScene);",
            "    }",
            "",
            "    if (S.configLoaded) {",
            "      if (typeof navigator !== \"undefined\" && navigator.onLine === false) {",
            "        A.showNetOffline();",
            "      } else {",
            "        A.showNetOnline();",
            "      }",
            "      return;",
            "    }",
            "",
            "    A.loadAndApplyConfig(runtimeScene);",
            "  };",
            "",
            "  A.renderRemember = function () {",
            "    var e = S.panelEls;",
            "    if (!e || !e.rememberBox) { return; }",
            "    var P = A.PALETTE;",
            "    e.rememberBox.textContent = S.remember ? \"✓\" : \"\";",
            "    e.rememberBox.style.background = S.remember ? P.primary : \"transparent\";",
            "    e.rememberBox.style.color = P.onPrimary;",
            "  };",
            "",
            "  A.buildCardHTML = function () {",
            "    A.PALETTE = A.theme();",
            "    A.loadFont();",
            "    var P = A.PALETTE;",
            "    var C = A.CONFIG;",
            "    var isFr = A.isFrench();",
            "    var field = \"width:100%;box-sizing:border-box;margin:0;padding:11px 13px 11px 42px;border-radius:10px;border:1px solid \" + P.border + \";background:\" + P.field + \";color:\" + P.text + \";font-size:14px;font-family:inherit;outline:none;user-select:text;-webkit-user-select:text;\";",
            "    var input = function (name, type, autocomplete, label, placeholder, icon) {",
            "      return '<div style=\"margin-bottom:12px;\"><div style=\"font-size:13px;font-weight:600;margin-bottom:6px;\">' + label + '</div>'",
            "        + '<label style=\"display:block;position:relative;\"><span style=\"position:absolute;left:13px;top:50%;transform:translateY(-50%);pointer-events:none;\">' + A.panelIcon(icon) + '</span>'",
            "        + '<input data-in=\"' + name + '\" type=\"' + type + '\" autocomplete=\"' + autocomplete + '\" placeholder=\"' + placeholder + '\" style=\"' + field + '\"></label></div>';",
            "    };",
            "    var box = 'display:inline-block;width:14px;height:14px;border-radius:4px;border:1px solid ' + P.primary + ';flex:0 0 auto;';",
            "",
            "    var titre = A.getText(\"titre\", C.titre || \"My game\");",
            "    var slogan = A.getText(\"slogan\", C.slogan || \"\");",
            "    var h = \"\";",
            "    h += '<div data-el=\"panel-card\" style=\"position:relative;margin:auto;width:min(100%,380px);max-height:min(100%,640px);display:flex;flex-direction:column;box-sizing:border-box;overflow:hidden;background:' + P.card + ';border:0;border-radius:16px;color:' + P.text + ';box-shadow:0 20px 60px rgba(0,0,0,0.6);opacity:1;transform:translateY(0) scale(1);transition:opacity 240ms ease,transform 240ms ease;\">';",
            "    h += A.panelMosaicHtml();",
            "    h += '<div data-el=\"panel-scroll\" style=\"position:relative;flex:1 1 auto;min-height:0;max-height:100%;box-sizing:border-box;overflow-y:auto;overflow-x:hidden;-webkit-overflow-scrolling:touch;padding:24px;\">';",
            "    h += '<div style=\"display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:12px;margin-bottom:18px;\">'",
            "      + '<div style=\"display:flex;align-items:center;gap:10px;\"><img src=\"' + A.ASSETS.logo + '\" alt=\"\" style=\"width:38px;height:38px;border-radius:10px;object-fit:cover;box-shadow:0 2px 8px rgba(0,0,0,0.25);\"><div style=\"font-size:14px;font-weight:700;letter-spacing:0.02em;\">Angon Cloud</div></div>'",
            "      + '<button type=\"button\" data-el=\"switch\" style=\"border:0;background:transparent;color:' + P.primary + ';font-family:inherit;font-size:13px;font-weight:600;cursor:pointer;padding:0;\">' + A.getText(\"boutonPasserInscription\", \"Create an account →\") + '</button>'",
            "      + '</div>';",
            "",
            "    // Vue de statut reseau (recherche de connexion & message hors ligne)",
            "    h += '<div data-el=\"net-status\" style=\"display:none;flex-direction:column;align-items:center;justify-content:center;padding:16px 8px 8px;text-align:center;\">';",
            "    h += '<div data-el=\"net-checking\" style=\"display:flex;flex-direction:column;align-items:center;justify-content:center;padding:26px 10px 18px;\">'",
            "      + '<div style=\"position:relative;width:52px;height:52px;margin-bottom:18px;\">'",
            "      + '<div style=\"width:100%;height:100%;box-sizing:border-box;border-radius:50%;border:3px solid ' + A.rgba(P.primary, 0.2) + ';border-top-color:' + P.primary + ';animation:angon-spin 0.8s cubic-bezier(0.4, 0, 0.2, 1) infinite;\"></div>'",
            "      + '<div style=\"position:absolute;inset:0;margin:auto;width:10px;height:10px;border-radius:50%;background:' + P.primary + ';box-shadow:0 0 12px ' + P.primary + ';animation:angon-pulse 1.6s ease-in-out infinite;\"></div>'",
            "      + '</div>'",
            "      + '<div style=\"' + A.headingStyle() + 'font-size:17px;margin-bottom:6px;\">' + A.getText(\"verificationConnexion\", isFr ? \"Vérification de la connexion...\" : \"Checking connection...\") + '</div>'",
            "      + '<div style=\"font-size:13px;color:' + P.muted + ';line-height:1.45;max-width:260px;\">' + A.getText(\"rechercheServeur\", isFr ? \"Recherche du serveur Angon Cloud...\" : \"Connecting to Angon Cloud...\") + '</div>'",
            "      + '</div>';",
            "",
            "    h += '<div data-el=\"net-offline\" style=\"display:none;flex-direction:column;align-items:center;justify-content:center;padding:16px 8px 12px;width:100%;box-sizing:border-box;\">'",
            "      + '<div style=\"width:64px;height:64px;border-radius:20px;background:' + A.rgba(P.danger, 0.12) + ';border:1px solid ' + A.rgba(P.danger, 0.28) + ';display:flex;align-items:center;justify-content:center;color:' + P.danger + ';margin-bottom:16px;box-shadow:0 8px 24px ' + A.rgba(P.danger, 0.14) + ';\">'",
            "      + A.panelIcon(\"offline\")",
            "      + '</div>'",
            "      + '<div style=\"' + A.headingStyle() + 'font-size:18px;margin-bottom:8px;\">' + A.getText(\"erreurHorsLigneTitre\", isFr ? \"Aucune connexion internet\" : \"No internet connection\") + '</div>'",
            "      + '<div style=\"font-size:13px;color:' + P.muted + ';line-height:1.5;max-width:280px;margin-bottom:22px;\">' + A.getText(\"erreurHorsLigneDescription\", isFr ? \"Impossible de joindre le serveur. Veuillez vérifier votre connexion internet et réessayer.\" : \"Unable to reach the server. Please check your internet connection and try again.\") + '</div>'",
            "      + '<div style=\"display:flex;flex-direction:column;gap:8px;width:100%;\">'",
            "      + '<button type=\"button\" data-el=\"net-retry\" style=\"width:100%;min-height:43px;padding:12px;border:0;border-radius:10px;background:' + P.primary + ';color:' + P.onPrimary + ';font-family:inherit;font-size:14px;font-weight:700;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:8px;box-shadow:0 4px 14px ' + A.rgba(P.primary, 0.35) + ';transition:opacity 160ms ease, transform 160ms ease;\">'",
            "      + '<span style=\"display:inline-block;transform:translateY(-1px);\">' + A.panelIcon(\"refresh\") + '</span>'",
            "      + '<span>' + A.getText(\"boutonReessayer\", isFr ? \"Réessayer\" : \"Retry\") + '</span></button>'",
            "      + '<button type=\"button\" data-el=\"net-close\" style=\"width:100%;padding:10px;border:0;border-radius:10px;background:transparent;color:' + P.muted + ';font-family:inherit;font-size:13px;cursor:pointer;\">' + A.getText(\"boutonAnnuler\", isFr ? \"Fermer\" : \"Cancel\") + '</button>'",
            "      + '</div></div>';",
            "    h += '</div>';",
            "",
            "    // Vue formulaire avec tous les champs de saisie",
            "    h += '<div data-el=\"form-view\">';",
            "    h += '<div data-el=\"heading\" style=\"' + A.headingStyle() + 'font-size:25px;\">' + A.getText(\"enteteConnexion\", \"Sign in\") + '</div>';",
            "    h += '<div data-el=\"subtitle\" style=\"font-size:13px;line-height:1.5;color:' + P.muted + ';margin:6px 0 18px 0;\">' + A.getText(\"texteConnexion\", C.texteConnexion) + '</div>';",
            "    h += '<div style=\"display:flex;align-items:center;gap:12px;border:1px solid ' + P.border + ';border-radius:14px;padding:12px 14px;margin-bottom:18px;\">';",
            "    if (C.logoUrl) {",
            "      h += '<img data-el=\"game-logo\" src=\"' + C.logoUrl + '\" alt=\"\" style=\"width:48px;height:48px;border-radius:12px;object-fit:cover;flex:0 0 auto;\">';",
            "      h += '<div data-el=\"game-logo-fallback\" style=\"display:none;width:48px;height:48px;border-radius:12px;flex:0 0 auto;align-items:center;justify-content:center;background:' + A.rgba(P.primary, 0.15) + ';border:1px solid ' + A.rgba(P.primary, 0.3) + ';\">' + A.panelIcon(\"shield\") + '</div>';",
            "    } else {",
            "      h += '<div style=\"width:48px;height:48px;border-radius:12px;flex:0 0 auto;display:flex;align-items:center;justify-content:center;background:' + A.rgba(P.primary, 0.15) + ';border:1px solid ' + A.rgba(P.primary, 0.3) + ';\">' + A.panelIcon(\"shield\") + '</div>';",
            "    }",
            "    h += '<div style=\"min-width:0;\"><div data-el=\"game-title\" style=\"font-size:15px;font-weight:700;\">' + titre + '</div>';",
            "    if (slogan) { h += '<div data-el=\"game-slogan\" style=\"font-size:12px;color:' + P.muted + ';\">' + slogan + '</div>'; }",
            "    h += '</div></div>';",
            "    if (C.googleActif) {",
            "      h += '<div data-el=\"google-container\" style=\"margin-bottom:14px;\">';",
            "      h += '<button type=\"button\" data-el=\"google-btn\" style=\"width:100%;min-height:42px;padding:10px 14px;border-radius:10px;border:1px solid ' + A.rgba(P.text, 0.16) + ';background:' + A.rgba(P.text, 0.05) + ';color:' + P.text + ';font-family:inherit;font-size:13px;font-weight:600;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:10px;box-shadow:0 1px 4px rgba(0,0,0,0.12);transition:background 140ms ease,opacity 140ms ease;\">';",
            "      h += A.panelIcon(\"google\");",
            "      h += '<span>' + A.getText(\"continuerAvecGoogle\", isFr ? \"Continuer avec Google\" : \"Continue with Google\") + '</span>';",
            "      h += '</button>';",
            "      h += '<div style=\"display:flex;align-items:center;margin:12px 0 10px 0;\"><div style=\"flex:1;height:1px;background:' + A.rgba(P.text, 0.12) + ';\"></div><span style=\"padding:0 10px;font-size:10px;font-weight:700;color:' + P.muted + ';text-transform:uppercase;letter-spacing:0.05em;\">' + A.getText(\"separateurOu\", isFr ? \"OU\" : \"OR\") + '</span><div style=\"flex:1;height:1px;background:' + A.rgba(P.text, 0.12) + ';\"></div></div>';",
            "      h += '</div>';",
            "    }",
            "    h += '<div data-pane=\"login\" style=\"transition:opacity 150ms ease,transform 150ms ease;\">';",
            "    h += input(\"login-email\", \"email\", \"email\", isFr ? \"Adresse e-mail\" : \"Email address\", \"name@example.com\", \"mail\");",
            "    h += input(\"login-password\", \"password\", \"current-password\", isFr ? \"Mot de passe\" : \"Password\", isFr ? \"Votre mot de passe\" : \"Your password\", \"lock\");",
            "    h += '<div data-el=\"options-login\" style=\"display:flex;align-items:center;justify-content:space-between;font-size:12px;color:' + P.muted + ';margin:2px 0 4px 0;\">';",
            "    h += '<span data-el=\"remember\" style=\"display:flex;align-items:center;gap:7px;cursor:pointer;user-select:none;\"><span data-el=\"remember-box\" style=\"' + box + 'display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:800;line-height:1;color:' + P.onPrimary + ';\"></span>' + A.getText(\"seSouvenirDeMoi\", isFr ? \"Se souvenir de moi\" : \"Remember me\") + '</span>';",
            "    h += '<span data-el=\"forgot\" style=\"color:' + P.primary + ';font-weight:600;\">' + A.getText(\"motDePasseOublie\", isFr ? \"Mot de passe oublié ?\" : \"Forgot password?\") + '</span>';",
            "    h += '</div></div>';",
            "    h += '<div data-pane=\"signup\" style=\"display:none;transition:opacity 150ms ease,transform 150ms ease;\">';",
            "    h += input(\"signup-pseudo\", \"text\", \"nickname\", isFr ? \"Nom d'utilisateur\" : \"Username\", isFr ? \"Choisissez un pseudo\" : \"Choose a username\", \"user\");",
            "    h += input(\"signup-email\", \"email\", \"email\", isFr ? \"Adresse e-mail\" : \"Email address\", \"name@example.com\", \"mail\");",
            "    h += input(\"signup-password\", \"password\", \"new-password\", isFr ? \"Mot de passe\" : \"Password\", isFr ? \"8 caractères minimum\" : \"8 characters minimum\", \"lock\");",
            "    h += input(\"signup-confirm\", \"password\", \"new-password\", isFr ? \"Confirmer le mot de passe\" : \"Confirm password\", isFr ? \"Confirmez votre mot de passe\" : \"Confirm your password\", \"lock\");",
            "    h += '<div data-el=\"options-signup\" style=\"display:flex;align-items:center;gap:7px;font-size:12px;color:' + P.muted + ';margin:2px 0 4px 0;\"><span style=\"' + box + '\"></span>' + A.getText(\"conditionsUtilisation\", isFr ? \"J'accepte les conditions d'utilisation\" : \"I accept the terms of use\") + '</div>';",
            "    h += '</div>';",
            "    h += '<div data-el=\"error\" style=\"display:none;margin:6px 0 12px 0;color:' + P.danger + ';font-size:13px;line-height:1.45;\"></div>';",
            "    h += '<button type=\"button\" data-el=\"submit\" style=\"width:100%;min-height:43px;margin-top:14px;padding:12px;border:0;border-radius:10px;background:' + P.primary + ';color:' + P.onPrimary + ';font-family:inherit;font-size:15px;font-weight:700;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:9px;transition:opacity 160ms ease;\">';",
            "    h += '<span data-el=\"spinner\" style=\"display:none;width:15px;height:15px;border:2px solid currentColor;border-right-color:transparent;border-radius:50%;animation:angon-spin .7s linear infinite;\"></span><span data-el=\"submit-label\">' + A.getText(\"boutonConnexion\", \"Sign in\") + '</span></button>';",
            "    h += '<button type=\"button\" data-el=\"close\" style=\"width:100%;margin-top:8px;padding:10px;border:0;border-radius:10px;background:transparent;color:' + P.muted + ';font-family:inherit;font-size:13px;cursor:pointer;\">' + A.getText(\"boutonAnnuler\", isFr ? \"Annuler\" : \"Cancel\") + '</button>';",
            "    h += \"</div>\";",
            "    h += '<div data-el=\"oauth-view\" style=\"display:none;flex-direction:column;align-items:center;justify-content:center;padding:26px 10px 18px;text-align:center;\">';",
            "    h += '<div style=\"position:relative;width:56px;height:56px;margin-bottom:16px;display:flex;align-items:center;justify-content:center;\">';",
            "    h += '<div style=\"position:absolute;inset:0;box-sizing:border-box;border-radius:50%;border:3px solid ' + A.rgba(P.primary, 0.2) + ';border-top-color:' + P.primary + ';animation:angon-spin 0.8s cubic-bezier(0.4, 0, 0.2, 1) infinite;\"></div>';",
            "    h += A.panelIcon(\"google\");",
            "    h += '</div>';",
            "    h += '<div data-el=\"oauth-title\" style=\"' + A.headingStyle() + 'font-size:17px;margin-bottom:6px;\">' + A.getText(\"attenteOAuthTitre\", isFr ? \"Connexion avec Google...\" : \"Connecting with Google...\") + '</div>';",
            "    h += '<div data-el=\"oauth-msg\" style=\"font-size:13px;color:' + P.muted + ';line-height:1.45;max-width:280px;margin-bottom:18px;\">' + A.getText(\"attenteOAuthDescription\", isFr ? \"Veuillez finaliser la connexion dans la fenêtre ouverte dans votre navigateur.\" : \"Please complete sign-in in the browser window that just opened.\") + '</div>';",
            "    h += '<a data-el=\"oauth-link\" target=\"_blank\" style=\"display:none;color:' + P.primary + ';font-size:12px;text-decoration:underline;margin-bottom:18px;cursor:pointer;\">' + (isFr ? \"Cliquez ici si la fenêtre ne s'est pas ouverte\" : \"Click here if the window did not open\") + '</a>';",
            "    h += '<button type=\"button\" data-el=\"oauth-cancel\" style=\"width:100%;padding:10px;border:1px solid ' + P.border + ';border-radius:10px;background:transparent;color:' + P.muted + ';font-family:inherit;font-size:13px;cursor:pointer;\">' + A.getText(\"boutonAnnuler\", isFr ? \"Annuler\" : \"Cancel\") + '</button>';",
            "    h += '</div>';",
            "    h += \"</div>\";",
            "    h += \"</div>\";",
            "    return h;",
            "  };",
            "",
            "  A.bindCardEvents = function (runtimeScene) {",
            "    var root = S.panelRoot;",
            "    if (!root) { return; }",
            "    var q = function (sel) { return root.querySelector(sel); };",
            "    S.panelEls = {",
            "      switchBtn: q('[data-el=\"switch\"]'),",
            "      heading: q('[data-el=\"heading\"]'),",
            "      subtitle: q('[data-el=\"subtitle\"]'),",
            "      paneLogin: q('[data-pane=\"login\"]'),",
            "      paneSignup: q('[data-pane=\"signup\"]'),",
            "      optionsLogin: q('[data-el=\"options-login\"]'),",
            "      optionsSignup: q('[data-el=\"options-signup\"]'),",
            "      remember: q('[data-el=\"remember\"]'),",
            "      rememberBox: q('[data-el=\"remember-box\"]'),",
            "      forgot: q('[data-el=\"forgot\"]'),",
            "      inLoginEmail: q('[data-in=\"login-email\"]'),",
            "      inLoginPassword: q('[data-in=\"login-password\"]'),",
            "      inPseudo: q('[data-in=\"signup-pseudo\"]'),",
            "      inSignupEmail: q('[data-in=\"signup-email\"]'),",
            "      inSignupPassword: q('[data-in=\"signup-password\"]'),",
            "      inSignupConfirm: q('[data-in=\"signup-confirm\"]'),",
            "      error: q('[data-el=\"error\"]'),",
            "      submit: q('[data-el=\"submit\"]'),",
            "      submitLabel: q('[data-el=\"submit-label\"]'),",
            "      spinner: q('[data-el=\"spinner\"]'),",
            "      close: q('[data-el=\"close\"]'),",
            "      formView: q('[data-el=\"form-view\"]'),",
            "      googleBtn: q('[data-el=\"google-btn\"]'),",
            "      oauthView: q('[data-el=\"oauth-view\"]'),",
            "      oauthTitle: q('[data-el=\"oauth-title\"]'),",
            "      oauthMsg: q('[data-el=\"oauth-msg\"]'),",
            "      oauthLink: q('[data-el=\"oauth-link\"]'),",
            "      oauthCancel: q('[data-el=\"oauth-cancel\"]'),",
            "      netStatus: q('[data-el=\"net-status\"]'),",
            "      netChecking: q('[data-el=\"net-checking\"]'),",
            "      netOffline: q('[data-el=\"net-offline\"]'),",
            "      netRetry: q('[data-el=\"net-retry\"]'),",
            "      netClose: q('[data-el=\"net-close\"]'),",
            "      gameTitle: q('[data-el=\"game-title\"]'),",
            "      gameSlogan: q('[data-el=\"game-slogan\"]'),",
            "      gameLogo: q('[data-el=\"game-logo\"]'),",
            "      gameLogoFallback: q('[data-el=\"game-logo-fallback\"]')",
            "    };",
            "",
            "    if (S.panelEls.gameLogo && S.panelEls.gameLogoFallback) {",
            "      S.panelEls.gameLogo.addEventListener(\"error\", function () {",
            "        S.panelEls.gameLogo.style.display = \"none\";",
            "        S.panelEls.gameLogoFallback.style.display = \"flex\";",
            "      });",
            "    }",
            "",
            "    A.loadRemember();",
            "    A.renderRemember();",
            "    if (S.panelEls.remember) {",
            "      S.panelEls.remember.addEventListener(\"click\", function () {",
            "        A.setRemember(!S.remember);",
            "        A.renderRemember();",
            "      });",
            "    }",
            "    if (S.panelEls.switchBtn) {",
            "      S.panelEls.switchBtn.addEventListener(\"click\", function () {",
            "        A.setMode(S.panelMode === \"signup\" ? \"login\" : \"signup\");",
            "      });",
            "    }",
            "    if (S.panelEls.submit) {",
            "      S.panelEls.submit.addEventListener(\"click\", function () { A.submitPanel(); });",
            "    }",
            "    if (S.panelEls.close) {",
            "      S.panelEls.close.addEventListener(\"click\", function () { A.closePanel(); });",
            "    }",
            "    if (S.panelEls.googleBtn) {",
            "      S.panelEls.googleBtn.addEventListener(\"click\", function () { A.startGoogleOAuth(); });",
            "    }",
            "    if (S.panelEls.oauthCancel) {",
            "      S.panelEls.oauthCancel.addEventListener(\"click\", function () { A.cancelOAuth(); });",
            "    }",
            "    if (S.panelEls.netRetry) {",
            "      S.panelEls.netRetry.addEventListener(\"click\", function () {",
            "        A.loadAndApplyConfig(runtimeScene);",
            "      });",
            "    }",
            "    if (S.panelEls.netClose) {",
            "      S.panelEls.netClose.addEventListener(\"click\", function () { A.closePanel(); });",
            "    }",
            "  };",
            "",
            "  A.buildPanel = function (runtimeScene) {",
            "    A.PALETTE = A.theme();",
            "    A.loadFont();",
            "    var P = A.PALETTE;",
            "",
            "    var root = document.createElement(\"div\");",
            "    root.setAttribute(\"data-angon\", \"auth-panel\");",
            "    root.style.cssText = \"position:absolute;left:0;top:0;width:100%;height:100%;box-sizing:border-box;display:flex;align-items:center;justify-content:center;overflow-y:auto;overflow-x:hidden;-webkit-overflow-scrolling:touch;padding:12px;background:\" + P.veil + \";z-index:1000;pointer-events:auto;font-family:\" + A.fontStack() + \";opacity:0;transition:opacity 220ms ease;\";",
            "",
            "    root.innerHTML = A.buildCardHTML();",
            "    S.panelRoot = root;",
            "    A.bindCardEvents(runtimeScene);",
            "",
            "    S.panelKeyHandler = function (ev) {",
            "      if (!S.panelOpen) { return; }",
            "      if (ev.key === \"Enter\") {",
            "        ev.preventDefault();",
            "        var e = S.panelEls;",
            "        if (e && e.netOffline && e.netOffline.style.display !== \"none\") {",
            "          A.loadAndApplyConfig(runtimeScene);",
            "        } else if (!e || !e.netStatus || e.netStatus.style.display === \"none\") {",
            "          A.submitPanel();",
            "        }",
            "      } else if (ev.key === \"Escape\") {",
            "        ev.preventDefault();",
            "        A.closePanel();",
            "      }",
            "    };",
            "    document.addEventListener(\"keydown\", S.panelKeyHandler);",
            "",
            "    S.panelOnlineHandler = function () {",
            "      if (S.panelOpen) {",
            "        if (!S.configLoaded) {",
            "          A.loadAndApplyConfig(runtimeScene);",
            "        } else {",
            "          A.showNetOnline();",
            "        }",
            "      }",
            "    };",
            "    S.panelOfflineHandler = function () {",
            "      if (S.panelOpen) { A.showNetOffline(); }",
            "    };",
            "    window.addEventListener(\"online\", S.panelOnlineHandler);",
            "    window.addEventListener(\"offline\", S.panelOfflineHandler);",
            "",
            "    A.domContainer(runtimeScene).appendChild(root);",
            "",
            "    if (S.configLoaded) {",
            "      A.showNetOnline();",
            "    } else {",
            "      A.showNetChecking();",
            "    }",
            "",
            "    if (!document.getElementById(\"angon-panel-motion\")) {",
            "      var motionStyle = document.createElement(\"style\");",
            "      motionStyle.id = \"angon-panel-motion\";",
            "      motionStyle.textContent = \"@keyframes angon-spin{to{transform:rotate(360deg)}}@keyframes angon-pulse{0%,100%{transform:scale(0.8);opacity:0.5}50%{transform:scale(1.2);opacity:1}}[data-el=net-retry]:hover{opacity:0.92;transform:translateY(-1px)}[data-el=net-retry]:active{transform:translateY(0)}[data-el=net-close]:hover{opacity:0.8}@media(prefers-reduced-motion:reduce){[data-angon=auth-panel],[data-el=panel-card],[data-pane]{transition:none!important}[data-el=spinner]{animation-duration:1.4s!important}}[data-el=panel-scroll]::-webkit-scrollbar{width:6px}[data-el=panel-scroll]::-webkit-scrollbar-track{background:transparent}[data-el=panel-scroll]::-webkit-scrollbar-thumb{background:rgba(128,128,128,0.35);border-radius:4px}[data-el=panel-scroll]::-webkit-scrollbar-thumb:hover{background:rgba(128,128,128,0.55)}[data-el=panel-scroll]{scrollbar-width:thin;scrollbar-color:rgba(128,128,128,0.35) transparent}@media(max-height:560px){[data-el=panel-scroll]{padding:16px 20px!important}}@media(max-height:440px){[data-el=panel-scroll]{padding:12px 16px!important}}\";",
            "      document.head.appendChild(motionStyle);",
            "    }",
            "    var showCard = function () {",
            "      root.style.opacity = \"1\";",
            "    };",
            "    if (typeof requestAnimationFrame !== \"undefined\") {",
            "      requestAnimationFrame(showCard);",
            "    } else {",
            "      showCard();",
            "    }",
            "  };",
            "",
            "  gdjs.__angon = A;",
            "  if (typeof window !== \"undefined\" && window.addEventListener) {",
            "    window.addEventListener(\"online\", function () {",
            "      A.syncPendingQueue();",
            "    });",
            "  }",
            "}",
            "",
            "/* Configuration lue depuis les variables de l extension. */",
            "gdjs.__angon.syncConfig();",
            "gdjs.__angon.ensurePanelConfig();",
            "gdjs.__angon.autoRestore();",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Vrai lorsque la variable ApiKey de l extension est renseignee. La cle se saisit uniquement dans les parametres de l extension, jamais dans les evenements.",
      "fullName": "Angon Cloud est configure",
      "functionType": "Condition",
      "group": "Configuration",
      "name": "EstConfigure",
      "sentence": "Angon Cloud est configure",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = false;",
            "  return;",
            "}",
            "var S = A.state;",
            "A.syncConfig();",
            "eventsFunctionContext.returnValue = !!S.apiKey;",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Connecte un joueur avec les identifiants saisis dans ton propre menu. Verifie ensuite la condition « Un joueur est connecte ».",
      "fullName": "Connecter un joueur",
      "functionType": "Action",
      "group": "Authentification",
      "name": "Connecter",
      "sentence": "Connecter le joueur _PARAM1_ avec le mot de passe _PARAM2_",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "var task = new gdjs.ManuallyResolvableTask();",
            "eventsFunctionContext.task = task;",
            "var mail = String(eventsFunctionContext.getArgument(\"Email\") || \"\").trim();",
            "var pw = String(eventsFunctionContext.getArgument(\"MotDePasse\") || \"\");",
            "if (!mail || !pw) {",
            "  S.statusCode = 0;",
            "  S.errorCode = \"missing_credentials\";",
            "  S.errorMessage = \"Email and password are required.\";",
            "  task.resolve();",
            "  return;",
            "}",
            "A.login(mail, pw).then(function () { task.resolve(); });",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [
        {
          "description": "Adresse e-mail",
          "name": "Email",
          "type": "string"
        },
        {
          "description": "Mot de passe",
          "name": "MotDePasse",
          "type": "string"
        }
      ],
      "objectGroups": [],
      "async": true
    },
    {
      "description": "Cree un compte joueur puis le connecte immediatement. La confirmation du mot de passe est verifiee avant l envoi.",
      "fullName": "Inscrire un nouveau joueur",
      "functionType": "Action",
      "group": "Authentification",
      "name": "Inscrire",
      "sentence": "Inscrire le joueur _PARAM1_ (_PARAM2_) avec le mot de passe _PARAM3_ confirme par _PARAM4_",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "var task = new gdjs.ManuallyResolvableTask();",
            "eventsFunctionContext.task = task;",
            "var pseudo = String(eventsFunctionContext.getArgument(\"Pseudo\") || \"\").trim();",
            "var mail = String(eventsFunctionContext.getArgument(\"Email\") || \"\").trim();",
            "var pw = String(eventsFunctionContext.getArgument(\"MotDePasse\") || \"\");",
            "var confirm = String(eventsFunctionContext.getArgument(\"Confirmation\") || \"\");",
            "var fail = function (codeErreur, message) {",
            "  S.statusCode = 0;",
            "  S.errorCode = codeErreur;",
            "  S.errorMessage = message;",
            "  task.resolve();",
            "};",
            "if (pseudo.length < 2) { fail(\"invalid_pseudo\", \"Username must be at least 2 characters.\"); return; }",
            "if (!mail) { fail(\"invalid_email\", \"Missing email address.\"); return; }",
            "if (pw.length < 8) { fail(\"weak_password\", \"Password must be at least 8 characters.\"); return; }",
            "if (pw !== confirm) { fail(\"password_mismatch\", \"Passwords do not match.\"); return; }",
            "A.register(pseudo, mail, pw).then(function () { task.resolve(); });",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [
        {
          "description": "Pseudo (2 caracteres minimum)",
          "name": "Pseudo",
          "type": "string"
        },
        {
          "description": "Adresse e-mail",
          "name": "Email",
          "type": "string"
        },
        {
          "description": "Mot de passe (8 caracteres minimum)",
          "name": "MotDePasse",
          "type": "string"
        },
        {
          "description": "Confirmation du mot de passe",
          "name": "Confirmation",
          "type": "string"
        }
      ],
      "objectGroups": [],
      "async": true
    },
    {
      "description": "Demarre une connexion avec Google. Ouvre la page d autorisation dans le navigateur du joueur et attend la validation. Utilise la condition « Un joueur est connecte » pour verifier.",
      "fullName": "Connecter avec Google",
      "functionType": "Action",
      "group": "Authentification",
      "name": "ConnecterAvecGoogle",
      "sentence": "Connecter le joueur avec Google",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "var task = new gdjs.ManuallyResolvableTask();",
            "eventsFunctionContext.task = task;",
            "A.loginWithGoogle().then(function () { task.resolve(); });",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [],
      "objectGroups": [],
      "async": true
    },
    {
      "description": "Reconnecte automatiquement le joueur grace au jeton conserve sur l appareil. A appeler au demarrage avant d afficher le panneau.",
      "fullName": "Restaurer la session enregistree",
      "functionType": "Action",
      "group": "Authentification",
      "name": "RestaurerSession",
      "sentence": "Restaurer la session Angon enregistree",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "var task = new gdjs.ManuallyResolvableTask();",
            "eventsFunctionContext.task = task;",
            "A.restore().then(function () { task.resolve(); });",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [],
      "objectGroups": [],
      "async": true
    },
    {
      "description": "Recharge pseudo, niveau, score et donnees du joueur connecte depuis le serveur.",
      "fullName": "Rafraichir le profil du joueur",
      "functionType": "Action",
      "group": "Authentification",
      "name": "RafraichirProfil",
      "sentence": "Rafraichir le profil du joueur Angon",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "var task = new gdjs.ManuallyResolvableTask();",
            "eventsFunctionContext.task = task;",
            "A.loadProfile(false).then(function () { task.resolve(); });",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [],
      "objectGroups": [],
      "async": true
    },
    {
      "description": "Efface le jeton et toutes les informations du joueur sur cet appareil.",
      "fullName": "Deconnecter le joueur",
      "functionType": "Action",
      "group": "Authentification",
      "name": "Deconnecter",
      "sentence": "Deconnecter le joueur Angon",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "if (S.panelOpen) { A.closePanel(); }",
            "A.reset();",
            "A.clearError();",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Vrai lorsqu un joueur possede une session Angon valide.",
      "fullName": "Un joueur est connecte",
      "functionType": "Condition",
      "group": "Authentification",
      "name": "EstConnecte",
      "sentence": "Un joueur Angon est connecte",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = false;",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = !!S.loggedIn;",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Vrai lorsque la restauration automatique de session est terminee. Attends cette condition avant d ouvrir le panneau au demarrage.",
      "fullName": "La session enregistree a ete verifiee",
      "functionType": "Condition",
      "group": "Authentification",
      "name": "SessionPrete",
      "sentence": "La session Angon a ete verifiee",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = false;",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = !!S.restoreDone;",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Vrai lorsque la session du joueur est conservee sur cet appareil apres la fermeture du jeu.",
      "fullName": "« Se souvenir de moi » est active",
      "functionType": "Condition",
      "group": "Authentification",
      "name": "SeSouvenirActive",
      "sentence": "« Se souvenir de moi » est active pour Angon",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = false;",
            "  return;",
            "}",
            "var S = A.state;",
            "A.loadRemember();",
            "eventsFunctionContext.returnValue = !!S.remember;",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Choisit si la session reste enregistree sur cet appareil. Active par defaut : le joueur reste connecte apres la fermeture du jeu.",
      "fullName": "Activer ou desactiver « Se souvenir de moi »",
      "functionType": "Action",
      "group": "Authentification",
      "name": "DefinirSeSouvenir",
      "sentence": "Conserver la session Angon sur cet appareil : _PARAM1_",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "A.setRemember(eventsFunctionContext.getArgument(\"Actif\") === \"yes\" || eventsFunctionContext.getArgument(\"Actif\") === true);",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [
        {
          "description": "Conserver la session",
          "name": "Actif",
          "type": "yesorno"
        }
      ],
      "objectGroups": []
    },
    {
      "description": "Vrai une seule fois juste apres une connexion, une inscription ou une restauration de session reussie.",
      "fullName": "Le joueur vient de se connecter",
      "functionType": "Condition",
      "group": "Authentification",
      "name": "VientDeSeConnecter",
      "sentence": "Le joueur Angon vient de se connecter",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = false;",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = !!S.justLoggedIn;",
            "S.justLoggedIn = false;",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Affiche le panneau Angon par-dessus le jeu, onglet Connexion actif. L action attend la fermeture du panneau.",
      "fullName": "Ouvrir le panneau de connexion Angon",
      "functionType": "Action",
      "group": "Panneau Angon",
      "name": "OuvrirPanneauConnexion",
      "sentence": "Ouvrir le panneau de connexion Angon",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "var task = new gdjs.ManuallyResolvableTask();",
            "eventsFunctionContext.task = task;",
            "A.openPanel(runtimeScene, \"login\", function () { task.resolve(); });",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [],
      "objectGroups": [],
      "async": true
    },
    {
      "description": "Affiche le panneau Angon par-dessus le jeu, onglet Inscription actif (pseudo, e-mail, mot de passe et confirmation). L action attend la fermeture du panneau.",
      "fullName": "Ouvrir le panneau d inscription Angon",
      "functionType": "Action",
      "group": "Panneau Angon",
      "name": "OuvrirPanneauInscription",
      "sentence": "Ouvrir le panneau d inscription Angon",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "var task = new gdjs.ManuallyResolvableTask();",
            "eventsFunctionContext.task = task;",
            "A.openPanel(runtimeScene, \"signup\", function () { task.resolve(); });",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [],
      "objectGroups": [],
      "async": true
    },
    {
      "description": "Retire le panneau de connexion sans toucher a la session en cours.",
      "fullName": "Fermer le panneau Angon",
      "functionType": "Action",
      "group": "Panneau Angon",
      "name": "FermerPanneau",
      "sentence": "Fermer le panneau Angon",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "A.closePanel();",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Vrai tant que le panneau de connexion est affiche. Pratique pour mettre le jeu en pause.",
      "fullName": "Le panneau Angon est ouvert",
      "functionType": "Condition",
      "group": "Panneau Angon",
      "name": "PanneauOuvert",
      "sentence": "Le panneau Angon est ouvert",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = false;",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = !!S.panelOpen;",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Ouvre une boite de dialogue pour selectionner une image, la televerse vers FreeImage.host et met a jour la photo du joueur.",
      "fullName": "Changer la photo de profil (importer une image)",
      "functionType": "Action",
      "group": "Profil joueur",
      "name": "ChangerPhotoProfil",
      "sentence": "Changer la photo de profil du joueur (log: _PARAM1_)",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "var task = new gdjs.ManuallyResolvableTask();",
            "eventsFunctionContext.task = task;",
            "var logVar = eventsFunctionContext.getArgument(\"VariableLog\");",
            "",
            "if (typeof document === \"undefined\" || !document.createElement) {",
            "  S.statusCode = 0;",
            "  S.errorCode = \"unsupported_environment\";",
            "  S.errorMessage = \"File dialog is not supported in this environment.\";",
            "  if (logVar) {",
            "    A.setVariableData(logVar, { ok: false, status: 0, code: S.errorCode, message: S.errorMessage, avatar_url: \"\", raw: \"\" });",
            "  }",
            "  task.resolve();",
            "  return;",
            "}",
            "",
            "var input = document.createElement(\"input\");",
            "input.type = \"file\";",
            "input.accept = \"image/*\";",
            "input.style.position = \"fixed\";",
            "input.style.bottom = \"-1000px\";",
            "input.style.left = \"-1000px\";",
            "input.style.opacity = \"0\";",
            "document.body.appendChild(input);",
            "",
            "var cleanedUp = false;",
            "function cleanup() {",
            "  if (cleanedUp) return;",
            "  cleanedUp = true;",
            "  try { if (input.parentNode) input.parentNode.removeChild(input); } catch (e) {}",
            "}",
            "",
            "input.onchange = function () {",
            "  var file = input.files && input.files[0];",
            "  if (!file) {",
            "    cleanup();",
            "    task.resolve();",
            "    return;",
            "  }",
            "  var reader = new FileReader();",
            "  reader.onload = function () {",
            "    cleanup();",
            "    var base64 = String(reader.result || \"\");",
            "    A.uploadAvatar(base64).then(function (res) {",
            "      if (logVar && res && res.log) {",
            "        A.setVariableData(logVar, res.log);",
            "      }",
            "      task.resolve();",
            "    });",
            "  };",
            "  reader.onerror = function () {",
            "    cleanup();",
            "    S.statusCode = 0;",
            "    S.errorCode = \"file_read_error\";",
            "    S.errorMessage = \"Failed to read the selected file.\";",
            "    if (logVar) {",
            "      A.setVariableData(logVar, { ok: false, status: 0, code: S.errorCode, message: S.errorMessage, avatar_url: \"\", raw: \"\" });",
            "    }",
            "    task.resolve();",
            "  };",
            "  reader.readAsDataURL(file);",
            "};",
            "",
            "input.oncancel = function () {",
            "  cleanup();",
            "  task.resolve();",
            "};",
            "",
            "try {",
            "  input.click();",
            "} catch (e) {",
            "  cleanup();",
            "  task.resolve();",
            "}",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [
        {
          "description": "Variable facultative pour recevoir le log de la requete (ok, status, avatar_url, etc.)",
          "name": "VariableLog",
          "type": "scenevar",
          "optional": true
        }
      ],
      "objectGroups": [],
      "async": true
    },
    {
      "description": "Pseudo du joueur connecte.",
      "fullName": "Pseudo du joueur",
      "functionType": "StringExpression",
      "group": "Profil joueur",
      "name": "Pseudo",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = \"\";",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = S.pseudo || \"\";",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "string"
      },
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Adresse e-mail du compte connecte.",
      "fullName": "E-mail du joueur",
      "functionType": "StringExpression",
      "group": "Profil joueur",
      "name": "Email",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = \"\";",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = S.email || \"\";",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "string"
      },
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "URL de la photo de profil hebergee du joueur.",
      "fullName": "Photo de profil (URL)",
      "functionType": "StringExpression",
      "group": "Profil joueur",
      "name": "PhotoProfil",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = \"\";",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = S.avatarUrl || \"\";",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "string"
      },
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Pays enregistre sur le compte.",
      "fullName": "Pays du joueur",
      "functionType": "StringExpression",
      "group": "Profil joueur",
      "name": "Pays",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = \"\";",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = S.pays || \"\";",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "string"
      },
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Statut sur ce jeu : en_ligne, hors_ligne ou suspendu.",
      "fullName": "Statut du joueur",
      "functionType": "StringExpression",
      "group": "Profil joueur",
      "name": "Statut",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = \"\";",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = S.statut || \"\";",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "string"
      },
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Jeton de session du joueur (x-player-token). A ne pas afficher.",
      "fullName": "Jeton de session",
      "functionType": "StringExpression",
      "group": "Profil joueur",
      "name": "Jeton",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = \"\";",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = S.token || \"\";",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "string"
      },
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Identifiant global du compte joueur, tous jeux confondus.",
      "fullName": "Identifiant du compte",
      "functionType": "StringExpression",
      "group": "Profil joueur",
      "name": "IdCompte",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = \"\";",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = S.accountId || \"\";",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "string"
      },
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Identifiant du profil du joueur sur ce jeu.",
      "fullName": "Identifiant du profil",
      "functionType": "StringExpression",
      "group": "Profil joueur",
      "name": "IdProfil",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = \"\";",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = S.profileId || \"\";",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "string"
      },
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Donnees libres du joueur, au format JSON.",
      "fullName": "Donnees libres du joueur",
      "functionType": "StringExpression",
      "group": "Profil joueur",
      "name": "Donnees",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = \"\";",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = S.donnees || \"\";",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "string"
      },
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Niveau du joueur connecte sur ce jeu.",
      "fullName": "Niveau du joueur",
      "functionType": "Expression",
      "group": "Profil joueur",
      "name": "Niveau",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = 1;",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = Number(S.niveau) || 0;",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "number"
      },
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Score enregistre du joueur connecte sur ce jeu.",
      "fullName": "Score du joueur",
      "functionType": "Expression",
      "group": "Profil joueur",
      "name": "Score",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = 0;",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = Number(S.score) || 0;",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "number"
      },
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Sauvegarde une chaine de texte, des donnees ou du JSON dans l espace de stockage du joueur sur Angon Cloud.",
      "fullName": "Sauvegarder un fichier texte / JSON sur Angon Cloud",
      "functionType": "Action",
      "group": "Stockage Angon Cloud",
      "name": "TeleverserFichierTexte",
      "sentence": "Sauvegarder le fichier _PARAM1_ avec le contenu _PARAM2_ sur Angon Cloud (ecraser: _PARAM3_, log: _PARAM4_)",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "var task = new gdjs.ManuallyResolvableTask();",
            "eventsFunctionContext.task = task;",
            "var nom = eventsFunctionContext.getArgument(\"NomFichier\");",
            "var contenu = eventsFunctionContext.getArgument(\"Contenu\");",
            "var ecraser = eventsFunctionContext.getArgument(\"Ecraser\");",
            "if (ecraser === undefined || ecraser === null || ecraser === \"\") ecraser = true;",
            "var logVar = eventsFunctionContext.getArgument(\"VariableLog\");",
            "",
            "A.uploadFile(nom, contenu, \"application/json\", ecraser).then(function (res) {",
            "  if (logVar) { A.setVariableData(logVar, res.log); }",
            "  task.resolve();",
            "});",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [
        {
          "description": "Nom du fichier a sauvegarder (ex: sauvegarde.json)",
          "name": "NomFichier",
          "type": "string"
        },
        {
          "description": "Contenu texte ou JSON du fichier",
          "name": "Contenu",
          "type": "string"
        },
        {
          "description": "Ecraser si existant deja (defaut: vrai)",
          "name": "Ecraser",
          "type": "boolean",
          "optional": true
        },
        {
          "description": "Variable facultative pour recevoir le log de la requete",
          "name": "VariableLog",
          "type": "scenevar",
          "optional": true
        }
      ],
      "objectGroups": [],
      "async": true
    },
    {
      "description": "Ouvre une boite de dialogue pour selectionner un fichier depuis l appareil et le televerser directement sur Angon Cloud dans l espace du joueur.",
      "fullName": "Televerser un fichier depuis l appareil (photo, video, donnees)",
      "functionType": "Action",
      "group": "Stockage Angon Cloud",
      "name": "TeleverserFichierDialogue",
      "sentence": "Ouvrir la boite de dialogue pour televerser un fichier sur Angon Cloud (nom: _PARAM1_, types: _PARAM2_, log: _PARAM3_)",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "var task = new gdjs.ManuallyResolvableTask();",
            "eventsFunctionContext.task = task;",
            "var customName = eventsFunctionContext.getArgument(\"NomFichier\");",
            "var acceptTypes = eventsFunctionContext.getArgument(\"TypesAcceptes\") || \"*/*\";",
            "var logVar = eventsFunctionContext.getArgument(\"VariableLog\");",
            "",
            "if (typeof document === \"undefined\" || !document.createElement) {",
            "  S.statusCode = 0;",
            "  S.errorCode = \"unsupported_environment\";",
            "  S.errorMessage = \"File dialog is not supported in this environment.\";",
            "  S.lastUploadSuccess = false;",
            "  if (logVar) {",
            "    A.setVariableData(logVar, { ok: false, status: 0, code: S.errorCode, message: S.errorMessage, raw: \"\" });",
            "  }",
            "  task.resolve();",
            "  return;",
            "}",
            "",
            "var input = document.createElement(\"input\");",
            "input.type = \"file\";",
            "input.accept = acceptTypes;",
            "input.style.position = \"fixed\";",
            "input.style.bottom = \"-1000px\";",
            "input.style.left = \"-1000px\";",
            "input.style.opacity = \"0\";",
            "document.body.appendChild(input);",
            "",
            "var cleanedUp = false;",
            "function cleanup() {",
            "  if (cleanedUp) return;",
            "  cleanedUp = true;",
            "  try { if (input.parentNode) input.parentNode.removeChild(input); } catch (e) {}",
            "}",
            "",
            "input.onchange = function () {",
            "  var file = input.files && input.files[0];",
            "  if (!file) {",
            "    cleanup();",
            "    task.resolve();",
            "    return;",
            "  }",
            "  var targetName = (customName && String(customName).trim()) ? String(customName).trim() : file.name;",
            "  var reader = new FileReader();",
            "  reader.onload = function () {",
            "    cleanup();",
            "    var base64 = reader.result;",
            "    A.uploadFile(targetName, base64, file.type, true).then(function (res) {",
            "      if (logVar) { A.setVariableData(logVar, res.log); }",
            "      task.resolve();",
            "    });",
            "  };",
            "  reader.onerror = function () {",
            "    cleanup();",
            "    S.statusCode = 0;",
            "    S.errorCode = \"file_read_error\";",
            "    S.errorMessage = \"Failed to read the selected file.\";",
            "    S.lastUploadSuccess = false;",
            "    if (logVar) {",
            "      A.setVariableData(logVar, { ok: false, status: 0, code: S.errorCode, message: S.errorMessage, raw: \"\" });",
            "    }",
            "    task.resolve();",
            "  };",
            "  reader.readAsDataURL(file);",
            "};",
            "",
            "input.oncancel = function () {",
            "  cleanup();",
            "  task.resolve();",
            "};",
            "",
            "try {",
            "  input.click();",
            "} catch (e) {",
            "  cleanup();",
            "  task.resolve();",
            "}",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [
        {
          "description": "Nom du fichier personnalise (optionnel, laisse vide pour garder le nom d origine)",
          "name": "NomFichier",
          "type": "string",
          "optional": true
        },
        {
          "description": "Types MIME acceptes (ex: image/*, video/*, .json, */*)",
          "name": "TypesAcceptes",
          "type": "string",
          "optional": true
        },
        {
          "description": "Variable facultative pour recevoir le log de la requete",
          "name": "VariableLog",
          "type": "scenevar",
          "optional": true
        }
      ],
      "objectGroups": [],
      "async": true
    },
    {
      "description": "Supprime definitivement un fichier sur Angon Cloud par son nom ou son identifiant.",
      "fullName": "Supprimer un fichier du joueur sur Angon Cloud",
      "functionType": "Action",
      "group": "Stockage Angon Cloud",
      "name": "SupprimerFichierJoueur",
      "sentence": "Supprimer le fichier _PARAM1_ du joueur sur Angon Cloud (log: _PARAM2_)",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "var task = new gdjs.ManuallyResolvableTask();",
            "eventsFunctionContext.task = task;",
            "var nom = eventsFunctionContext.getArgument(\"NomFichier\");",
            "var logVar = eventsFunctionContext.getArgument(\"VariableLog\");",
            "",
            "A.deleteFile(nom).then(function (res) {",
            "  if (logVar) { A.setVariableData(logVar, res.log); }",
            "  task.resolve();",
            "});",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [
        {
          "description": "Nom du fichier (ex: sauvegarde.json) ou ID unique du fichier",
          "name": "NomFichier",
          "type": "string"
        },
        {
          "description": "Variable facultative pour recevoir le log de la requete",
          "name": "VariableLog",
          "type": "scenevar",
          "optional": true
        }
      ],
      "objectGroups": [],
      "async": true
    },
    {
      "description": "Recupere tous les fichiers du joueur enregistres sur Angon Cloud et remplit une variable tableau/structure.",
      "fullName": "Lister les fichiers du joueur sur Angon Cloud",
      "functionType": "Action",
      "group": "Stockage Angon Cloud",
      "name": "ListerFichiersJoueur",
      "sentence": "Lister les fichiers Angon Cloud du joueur dans la variable _PARAM1_ (log: _PARAM2_)",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "var task = new gdjs.ManuallyResolvableTask();",
            "eventsFunctionContext.task = task;",
            "var targetVar = eventsFunctionContext.getArgument(\"VariableCible\");",
            "var logVar = eventsFunctionContext.getArgument(\"VariableLog\");",
            "",
            "A.listFiles().then(function (res) {",
            "  if (targetVar) { A.setVariableData(targetVar, res.fichiers); }",
            "  if (logVar) { A.setVariableData(logVar, res.log); }",
            "  task.resolve();",
            "});",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [
        {
          "description": "Variable de scene pour recevoir la liste des fichiers",
          "name": "VariableCible",
          "type": "scenevar"
        },
        {
          "description": "Variable facultative pour recevoir le log de la requete",
          "name": "VariableLog",
          "type": "scenevar",
          "optional": true
        }
      ],
      "objectGroups": [],
      "async": true
    },
    {
      "description": "Vrai si le dernier appel a TeleverserFichier s est termine avec succes sur Angon Cloud.",
      "fullName": "Le dernier televersement de fichier a reussi",
      "functionType": "Condition",
      "group": "Stockage Angon Cloud",
      "name": "DernierTeleversementReussi",
      "sentence": "Le dernier televersement de fichier a reussi",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = false;",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = !!S.lastUploadSuccess;",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Vrai si le dernier appel a TeleverserFichier a renvoye une erreur.",
      "fullName": "Le dernier televersement de fichier a echoue",
      "functionType": "Condition",
      "group": "Stockage Angon Cloud",
      "name": "DernierTeleversementEchoue",
      "sentence": "Le dernier televersement de fichier a echoue",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = false;",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = !S.lastUploadSuccess;",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Lien web direct du dernier fichier televerse avec succes sur Angon Cloud.",
      "fullName": "URL du dernier fichier televerse",
      "functionType": "StringExpression",
      "group": "Stockage Angon Cloud",
      "name": "DernierFichierUrl",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = \"\";",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = S.lastFileUrl || \"\";",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "string"
      },
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Identifiant unique du dernier fichier televerse avec succes.",
      "fullName": "Identifiant du dernier fichier televerse",
      "functionType": "StringExpression",
      "group": "Stockage Angon Cloud",
      "name": "DernierFichierId",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = \"\";",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = S.lastFileId || \"\";",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "string"
      },
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Nom de fichier du dernier fichier televerse.",
      "fullName": "Nom du dernier fichier televerse",
      "functionType": "StringExpression",
      "group": "Stockage Angon Cloud",
      "name": "DernierFichierNom",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = \"\";",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = S.lastFileName || \"\";",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "string"
      },
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Taille en octets du dernier fichier televerse.",
      "fullName": "Taille du dernier fichier (octets)",
      "functionType": "Expression",
      "group": "Stockage Angon Cloud",
      "name": "DernierFichierTaille",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = 0;",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = Number(S.lastFileSize) || 0;",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "number"
      },
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Tableau JSON brut de tous les fichiers du joueur recuperes par ListerFichiersJoueur.",
      "fullName": "Liste des fichiers du joueur (JSON)",
      "functionType": "StringExpression",
      "group": "Stockage Angon Cloud",
      "name": "FichiersJoueurJson",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = \"[]\";",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = S.playerFilesJson || \"[]\";",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "string"
      },
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Vrai pendant qu un appel au serveur Angon n a pas encore repondu.",
      "fullName": "Une requete Angon est en cours",
      "functionType": "Condition",
      "group": "Etat des requetes",
      "name": "RequeteEnCours",
      "sentence": "Une requete Angon est en cours",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = false;",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = !!S.pending;",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Vrai si le dernier appel Angon a renvoye une erreur. Lis le message avec l expression MessageErreur.",
      "fullName": "La derniere requete a echoue",
      "functionType": "Condition",
      "group": "Etat des requetes",
      "name": "DerniereRequeteAEchoue",
      "sentence": "La derniere requete Angon a echoue",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = false;",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = !!S.errorCode;",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Code technique de la derniere erreur : invalid_credentials, email_taken, network_error...",
      "fullName": "Code de la derniere erreur",
      "functionType": "StringExpression",
      "group": "Etat des requetes",
      "name": "CodeErreur",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = \"\";",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = S.errorCode || \"\";",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "string"
      },
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Message lisible de la derniere erreur, affichable tel quel au joueur.",
      "fullName": "Message de la derniere erreur",
      "functionType": "StringExpression",
      "group": "Etat des requetes",
      "name": "MessageErreur",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = \"\";",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = S.errorMessage || \"\";",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "string"
      },
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Code HTTP de la derniere reponse du serveur Angon (0 si la requete n a pas abouti).",
      "fullName": "Code HTTP de la derniere reponse",
      "functionType": "Expression",
      "group": "Etat des requetes",
      "name": "CodeStatut",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = 0;",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = Number(S.statusCode) || 0;",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "number"
      },
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Charge toutes les lignes d une table de donnees Angon et stocke le tableau d objets dans une variable de scene.",
      "fullName": "Lister les lignes d une table",
      "functionType": "Action",
      "group": "Tables : Lecture et Recherche",
      "name": "ListerLignes",
      "sentence": "Charger toutes les lignes de la table _PARAM1_ dans la variable _PARAM2_",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "var task = new gdjs.ManuallyResolvableTask();",
            "eventsFunctionContext.task = task;",
            "var tbl = String(eventsFunctionContext.getArgument(\"Table\") || \"\").trim();",
            "var targetVar = eventsFunctionContext.getArgument(\"VariableCible\");",
            "A.listRows(tbl, { limit: 100 }).then(function (r) {",
            "  if (r.ok && targetVar) {",
            "    A.setVariableData(targetVar, r.data);",
            "  }",
            "  task.resolve();",
            "});",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [
        {
          "description": "Nom de la table (ex: inventaire, quetes)",
          "name": "Table",
          "type": "string"
        },
        {
          "description": "Variable de scene pour recevoir la liste des lignes",
          "name": "VariableCible",
          "type": "scenevar"
        }
      ],
      "objectGroups": [],
      "async": true
    },
    {
      "description": "Charge les lignes d une table ou la colonne specifiee correspond a la valeur recherchee.",
      "fullName": "Lister les lignes filtrees d une table",
      "functionType": "Action",
      "group": "Tables : Lecture et Recherche",
      "name": "ListerLignesFiltrees",
      "sentence": "Charger les lignes de la table _PARAM1_ ou _PARAM2_ = _PARAM3_ dans la variable _PARAM4_",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "var task = new gdjs.ManuallyResolvableTask();",
            "eventsFunctionContext.task = task;",
            "var tbl = String(eventsFunctionContext.getArgument(\"Table\") || \"\").trim();",
            "var col = String(eventsFunctionContext.getArgument(\"Champ\") || \"\").trim();",
            "var val = String(eventsFunctionContext.getArgument(\"Valeur\") || \"\");",
            "var targetVar = eventsFunctionContext.getArgument(\"VariableCible\");",
            "var q = { limit: 100 };",
            "if (col) { q[col] = val; }",
            "A.listRows(tbl, q).then(function (r) {",
            "  if (r.ok && targetVar) {",
            "    A.setVariableData(targetVar, r.data);",
            "  }",
            "  task.resolve();",
            "});",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [
        {
          "description": "Nom de la table",
          "name": "Table",
          "type": "string"
        },
        {
          "description": "Nom de la colonne de filtre",
          "name": "Champ",
          "type": "string"
        },
        {
          "description": "Valeur recherchee",
          "name": "Valeur",
          "type": "string"
        },
        {
          "description": "Variable de scene pour recevoir les lignes",
          "name": "VariableCible",
          "type": "scenevar"
        }
      ],
      "objectGroups": [],
      "async": true
    },
    {
      "description": "Charge les lignes d une table triees selon un champ (croissant ou decroissant) avec une limite personnalisee.",
      "fullName": "Lister les lignes triees d une table",
      "functionType": "Action",
      "group": "Tables : Lecture et Recherche",
      "name": "ListerLignesTriees",
      "sentence": "Charger les lignes de la table _PARAM1_ triees par _PARAM2_ (croissant: _PARAM3_, max _PARAM4_) dans la variable _PARAM5_",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "var task = new gdjs.ManuallyResolvableTask();",
            "eventsFunctionContext.task = task;",
            "var tbl = String(eventsFunctionContext.getArgument(\"Table\") || \"\").trim();",
            "var col = String(eventsFunctionContext.getArgument(\"Champ\") || \"\").trim();",
            "var asc = !!eventsFunctionContext.getArgument(\"Croissant\");",
            "var lim = Number(eventsFunctionContext.getArgument(\"Limite\")) || 50;",
            "var targetVar = eventsFunctionContext.getArgument(\"VariableCible\");",
            "var q = { limit: Math.min(Math.max(lim, 1), 200) };",
            "if (col) { q.order = col + (asc ? \".asc\" : \".desc\"); }",
            "A.listRows(tbl, q).then(function (r) {",
            "  if (r.ok && targetVar) {",
            "    A.setVariableData(targetVar, r.data);",
            "  }",
            "  task.resolve();",
            "});",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [
        {
          "description": "Nom de la table",
          "name": "Table",
          "type": "string"
        },
        {
          "description": "Nom de la colonne de tri",
          "name": "Champ",
          "type": "string"
        },
        {
          "description": "Ordre croissant (Oui) ou decroissant (Non)",
          "name": "Croissant",
          "type": "yesorno"
        },
        {
          "description": "Nombre maximum de lignes (0 ou defaut 50)",
          "name": "Limite",
          "type": "expression"
        },
        {
          "description": "Variable de scene pour recevoir les lignes",
          "name": "VariableCible",
          "type": "scenevar"
        }
      ],
      "objectGroups": [],
      "async": true
    },
    {
      "description": "Charge une ligne unique d une table a partir de son identifiant ID et stocke la structure dans une variable.",
      "fullName": "Obtenir une ligne par son identifiant",
      "functionType": "Action",
      "group": "Tables : Lecture et Recherche",
      "name": "ObtenirLigne",
      "sentence": "Recuperer la ligne _PARAM2_ de la table _PARAM1_ dans la variable _PARAM3_",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "var task = new gdjs.ManuallyResolvableTask();",
            "eventsFunctionContext.task = task;",
            "var tbl = String(eventsFunctionContext.getArgument(\"Table\") || \"\").trim();",
            "var id = String(eventsFunctionContext.getArgument(\"IdLigne\") || \"\").trim();",
            "var targetVar = eventsFunctionContext.getArgument(\"VariableCible\");",
            "A.getRow(tbl, id).then(function (r) {",
            "  if (r.ok && targetVar && r.data) {",
            "    A.setVariableData(targetVar, r.data);",
            "  }",
            "  task.resolve();",
            "});",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [
        {
          "description": "Nom de la table",
          "name": "Table",
          "type": "string"
        },
        {
          "description": "Identifiant de la ligne (ID)",
          "name": "IdLigne",
          "type": "string"
        },
        {
          "description": "Variable de scene pour recevoir la ligne",
          "name": "VariableCible",
          "type": "scenevar"
        }
      ],
      "objectGroups": [],
      "async": true
    },
    {
      "description": "Recherche la premiere ligne d une table ayant la valeur demandee dans une colonne specifiee.",
      "fullName": "Obtenir la premiere ligne correspondante",
      "functionType": "Action",
      "group": "Tables : Lecture et Recherche",
      "name": "ObtenirPremiereLigne",
      "sentence": "Recuperer la premiere ligne de _PARAM1_ ou _PARAM2_ = _PARAM3_ dans la variable _PARAM4_",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "var task = new gdjs.ManuallyResolvableTask();",
            "eventsFunctionContext.task = task;",
            "var tbl = String(eventsFunctionContext.getArgument(\"Table\") || \"\").trim();",
            "var col = String(eventsFunctionContext.getArgument(\"Champ\") || \"\").trim();",
            "var val = String(eventsFunctionContext.getArgument(\"Valeur\") || \"\");",
            "var targetVar = eventsFunctionContext.getArgument(\"VariableCible\");",
            "var q = { limit: 1 };",
            "if (col) { q[col] = val; }",
            "A.listRows(tbl, q).then(function (r) {",
            "  if (r.ok && r.data && r.data.length > 0) {",
            "    S.lastTableRow = r.data[0];",
            "    S.lastCreatedId = String(r.data[0].id || \"\");",
            "    if (targetVar) {",
            "      A.setVariableData(targetVar, r.data[0]);",
            "    }",
            "  } else {",
            "    S.lastTableRow = null;",
            "  }",
            "  task.resolve();",
            "});",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [
        {
          "description": "Nom de la table",
          "name": "Table",
          "type": "string"
        },
        {
          "description": "Nom de la colonne",
          "name": "Champ",
          "type": "string"
        },
        {
          "description": "Valeur recherchee",
          "name": "Valeur",
          "type": "string"
        },
        {
          "description": "Variable de scene pour recevoir la ligne",
          "name": "VariableCible",
          "type": "scenevar"
        }
      ],
      "objectGroups": [],
      "async": true
    },
    {
      "description": "Insere une nouvelle ligne dans une table Angon a partir des champs d une variable structure GDevelop.",
      "fullName": "Creer une ligne depuis une variable structure",
      "functionType": "Action",
      "group": "Tables : Ecriture et Insertion",
      "name": "CreerLigneDepuisVariable",
      "sentence": "Inserer une ligne dans la table _PARAM1_ avec les donnees de la variable _PARAM2_ (log: _PARAM3_)",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "var task = new gdjs.ManuallyResolvableTask();",
            "eventsFunctionContext.task = task;",
            "var tbl = String(eventsFunctionContext.getArgument(\"Table\") || \"\").trim();",
            "var sourceVar = eventsFunctionContext.getArgument(\"Donnees\");",
            "var logVar = eventsFunctionContext.getArgument(\"VariableLog\");",
            "var obj = A.getVariableData(sourceVar);",
            "A.createRow(tbl, obj).then(function (r) {",
            "  if (logVar && r && r.log) { A.setVariableData(logVar, r.log); }",
            "  task.resolve();",
            "});",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [
        {
          "description": "Nom de la table",
          "name": "Table",
          "type": "string"
        },
        {
          "description": "Variable structure contenant les colonnes et valeurs",
          "name": "Donnees",
          "type": "scenevar"
        },
        {
          "description": "Variable facultative pour recevoir le log de la requete",
          "name": "VariableLog",
          "type": "scenevar",
          "optional": true
        }
      ],
      "objectGroups": [],
      "async": true
    },
    {
      "description": "Insere une nouvelle ligne dans une table Angon a partir d un objet JSON sous forme de texte.",
      "fullName": "Creer une ligne depuis un texte JSON",
      "functionType": "Action",
      "group": "Tables : Ecriture et Insertion",
      "name": "CreerLigneJson",
      "sentence": "Inserer une ligne dans la table _PARAM1_ avec le JSON _PARAM2_ (log: _PARAM3_)",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "var task = new gdjs.ManuallyResolvableTask();",
            "eventsFunctionContext.task = task;",
            "var tbl = String(eventsFunctionContext.getArgument(\"Table\") || \"\").trim();",
            "var raw = String(eventsFunctionContext.getArgument(\"DonneesJson\") || \"\").trim();",
            "var logVar = eventsFunctionContext.getArgument(\"VariableLog\");",
            "var obj = {};",
            "try { obj = raw ? JSON.parse(raw) : {}; } catch (e) {",
            "  S.statusCode = 0;",
            "  S.errorCode = \"invalid_json\";",
            "  S.errorMessage = \"The provided JSON string is invalid.\";",
            "  S.tableSuccess = false;",
            "  if (logVar) {",
            "    A.setVariableData(logVar, { ok: false, status: 0, code: \"invalid_json\", message: S.errorMessage, data: null, raw: raw });",
            "  }",
            "  task.resolve();",
            "  return;",
            "}",
            "A.createRow(tbl, obj).then(function (r) {",
            "  if (logVar && r && r.log) { A.setVariableData(logVar, r.log); }",
            "  task.resolve();",
            "});",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [
        {
          "description": "Nom de la table",
          "name": "Table",
          "type": "string"
        },
        {
          "description": "Objet JSON (ex: {\"nom\": \"Epee\", \"degats\": 15})",
          "name": "DonneesJson",
          "type": "string"
        },
        {
          "description": "Variable facultative pour recevoir le log de la requete",
          "name": "VariableLog",
          "type": "scenevar",
          "optional": true
        }
      ],
      "objectGroups": [],
      "async": true
    },
    {
      "description": "Insere une nouvelle ligne dans une table Angon avec une seule colonne definie.",
      "fullName": "Creer une ligne avec un champ",
      "functionType": "Action",
      "group": "Tables : Ecriture et Insertion",
      "name": "CreerLigneSimple",
      "sentence": "Inserer une ligne dans la table _PARAM1_ avec _PARAM2_ = _PARAM3_ (log: _PARAM4_)",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "var task = new gdjs.ManuallyResolvableTask();",
            "eventsFunctionContext.task = task;",
            "var tbl = String(eventsFunctionContext.getArgument(\"Table\") || \"\").trim();",
            "var col = String(eventsFunctionContext.getArgument(\"Champ\") || \"\").trim();",
            "var val = String(eventsFunctionContext.getArgument(\"Valeur\") || \"\");",
            "var logVar = eventsFunctionContext.getArgument(\"VariableLog\");",
            "var obj = {};",
            "if (col) { obj[col] = val; }",
            "A.createRow(tbl, obj).then(function (r) {",
            "  if (logVar && r && r.log) { A.setVariableData(logVar, r.log); }",
            "  task.resolve();",
            "});",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [
        {
          "description": "Nom de la table",
          "name": "Table",
          "type": "string"
        },
        {
          "description": "Nom de la colonne",
          "name": "Champ",
          "type": "string"
        },
        {
          "description": "Valeur",
          "name": "Valeur",
          "type": "string"
        },
        {
          "description": "Variable facultative pour recevoir le log de la requete",
          "name": "VariableLog",
          "type": "scenevar",
          "optional": true
        }
      ],
      "objectGroups": [],
      "async": true
    },
    {
      "description": "Insere une nouvelle ligne dans une table Angon avec deux colonnes definies.",
      "fullName": "Creer une ligne avec deux champs",
      "functionType": "Action",
      "group": "Tables : Ecriture et Insertion",
      "name": "CreerLigneDouble",
      "sentence": "Inserer une ligne dans la table _PARAM1_ avec _PARAM2_ = _PARAM3_ et _PARAM4_ = _PARAM5_ (log: _PARAM6_)",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "var task = new gdjs.ManuallyResolvableTask();",
            "eventsFunctionContext.task = task;",
            "var tbl = String(eventsFunctionContext.getArgument(\"Table\") || \"\").trim();",
            "var col1 = String(eventsFunctionContext.getArgument(\"Champ1\") || \"\").trim();",
            "var val1 = String(eventsFunctionContext.getArgument(\"Valeur1\") || \"\");",
            "var col2 = String(eventsFunctionContext.getArgument(\"Champ2\") || \"\").trim();",
            "var val2 = String(eventsFunctionContext.getArgument(\"Valeur2\") || \"\");",
            "var logVar = eventsFunctionContext.getArgument(\"VariableLog\");",
            "var obj = {};",
            "if (col1) { obj[col1] = val1; }",
            "if (col2) { obj[col2] = val2; }",
            "A.createRow(tbl, obj).then(function (r) {",
            "  if (logVar && r && r.log) { A.setVariableData(logVar, r.log); }",
            "  task.resolve();",
            "});",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [
        {
          "description": "Nom de la table",
          "name": "Table",
          "type": "string"
        },
        {
          "description": "Colonne 1",
          "name": "Champ1",
          "type": "string"
        },
        {
          "description": "Valeur 1",
          "name": "Valeur1",
          "type": "string"
        },
        {
          "description": "Colonne 2",
          "name": "Champ2",
          "type": "string"
        },
        {
          "description": "Valeur 2",
          "name": "Valeur2",
          "type": "string"
        },
        {
          "description": "Variable facultative pour recevoir le log de la requete",
          "name": "VariableLog",
          "type": "scenevar",
          "optional": true
        }
      ],
      "objectGroups": [],
      "async": true
    },
    {
      "description": "Met a jour les colonnes d une ligne existante a partir des valeurs d une variable structure.",
      "fullName": "Modifier une ligne depuis une variable structure",
      "functionType": "Action",
      "group": "Tables : Modification",
      "name": "ModifierLigneDepuisVariable",
      "sentence": "Mettre a jour la ligne _PARAM2_ de la table _PARAM1_ avec la variable _PARAM3_ (log: _PARAM4_)",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "var task = new gdjs.ManuallyResolvableTask();",
            "eventsFunctionContext.task = task;",
            "var tbl = String(eventsFunctionContext.getArgument(\"Table\") || \"\").trim();",
            "var id = String(eventsFunctionContext.getArgument(\"IdLigne\") || \"\").trim();",
            "var sourceVar = eventsFunctionContext.getArgument(\"Donnees\");",
            "var logVar = eventsFunctionContext.getArgument(\"VariableLog\");",
            "var obj = A.getVariableData(sourceVar);",
            "A.updateRow(tbl, id, obj).then(function (r) {",
            "  if (logVar && r && r.log) { A.setVariableData(logVar, r.log); }",
            "  task.resolve();",
            "});",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [
        {
          "description": "Nom de la table",
          "name": "Table",
          "type": "string"
        },
        {
          "description": "Identifiant de la ligne (ID)",
          "name": "IdLigne",
          "type": "string"
        },
        {
          "description": "Variable structure contenant les modifications",
          "name": "Donnees",
          "type": "scenevar"
        },
        {
          "description": "Variable facultative pour recevoir le log de la requete",
          "name": "VariableLog",
          "type": "scenevar",
          "optional": true
        }
      ],
      "objectGroups": [],
      "async": true
    },
    {
      "description": "Met a jour les colonnes d une ligne existante a partir d un objet JSON texte.",
      "fullName": "Modifier une ligne depuis un texte JSON",
      "functionType": "Action",
      "group": "Tables : Modification",
      "name": "ModifierLigneJson",
      "sentence": "Mettre a jour la ligne _PARAM2_ de la table _PARAM1_ avec le JSON _PARAM3_ (log: _PARAM4_)",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "var task = new gdjs.ManuallyResolvableTask();",
            "eventsFunctionContext.task = task;",
            "var tbl = String(eventsFunctionContext.getArgument(\"Table\") || \"\").trim();",
            "var id = String(eventsFunctionContext.getArgument(\"IdLigne\") || \"\").trim();",
            "var raw = String(eventsFunctionContext.getArgument(\"DonneesJson\") || \"\").trim();",
            "var logVar = eventsFunctionContext.getArgument(\"VariableLog\");",
            "var obj = {};",
            "try { obj = raw ? JSON.parse(raw) : {}; } catch (e) {",
            "  S.statusCode = 0;",
            "  S.errorCode = \"invalid_json\";",
            "  S.errorMessage = \"The provided JSON string is invalid.\";",
            "  S.tableSuccess = false;",
            "  if (logVar) {",
            "    A.setVariableData(logVar, { ok: false, status: 0, code: \"invalid_json\", message: S.errorMessage, data: null, raw: raw });",
            "  }",
            "  task.resolve();",
            "  return;",
            "}",
            "A.updateRow(tbl, id, obj).then(function (r) {",
            "  if (logVar && r && r.log) { A.setVariableData(logVar, r.log); }",
            "  task.resolve();",
            "});",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [
        {
          "description": "Nom de la table",
          "name": "Table",
          "type": "string"
        },
        {
          "description": "Identifiant de la ligne (ID)",
          "name": "IdLigne",
          "type": "string"
        },
        {
          "description": "JSON des colonnes a modifier (ex: {\"score\": 100})",
          "name": "DonneesJson",
          "type": "string"
        },
        {
          "description": "Variable facultative pour recevoir le log de la requete",
          "name": "VariableLog",
          "type": "scenevar",
          "optional": true
        }
      ],
      "objectGroups": [],
      "async": true
    },
    {
      "description": "Met a jour la valeur texte d une seule colonne pour une ligne donnee.",
      "fullName": "Modifier un champ texte d une ligne",
      "functionType": "Action",
      "group": "Tables : Modification",
      "name": "ModifierChampLigne",
      "sentence": "Mettre a jour dans _PARAM1_ la ligne _PARAM2_ : definir _PARAM3_ = _PARAM4_ (log: _PARAM5_)",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "var task = new gdjs.ManuallyResolvableTask();",
            "eventsFunctionContext.task = task;",
            "var tbl = String(eventsFunctionContext.getArgument(\"Table\") || \"\").trim();",
            "var id = String(eventsFunctionContext.getArgument(\"IdLigne\") || \"\").trim();",
            "var col = String(eventsFunctionContext.getArgument(\"Champ\") || \"\").trim();",
            "var val = String(eventsFunctionContext.getArgument(\"Valeur\") || \"\");",
            "var logVar = eventsFunctionContext.getArgument(\"VariableLog\");",
            "var obj = {};",
            "if (col) { obj[col] = val; }",
            "A.updateRow(tbl, id, obj).then(function (r) {",
            "  if (logVar && r && r.log) { A.setVariableData(logVar, r.log); }",
            "  task.resolve();",
            "});",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [
        {
          "description": "Nom de la table",
          "name": "Table",
          "type": "string"
        },
        {
          "description": "Identifiant de la ligne (ID)",
          "name": "IdLigne",
          "type": "string"
        },
        {
          "description": "Nom de la colonne",
          "name": "Champ",
          "type": "string"
        },
        {
          "description": "Nouvelle valeur texte",
          "name": "Valeur",
          "type": "string"
        },
        {
          "description": "Variable facultative pour recevoir le log de la requete",
          "name": "VariableLog",
          "type": "scenevar",
          "optional": true
        }
      ],
      "objectGroups": [],
      "async": true
    },
    {
      "description": "Met a jour la valeur numerique d une seule colonne pour une ligne donnee.",
      "fullName": "Modifier un champ numerique d une ligne",
      "functionType": "Action",
      "group": "Tables : Modification",
      "name": "ModifierNombreLigne",
      "sentence": "Mettre a jour dans _PARAM1_ la ligne _PARAM2_ : definir _PARAM3_ = _PARAM4_ (log: _PARAM5_)",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "var task = new gdjs.ManuallyResolvableTask();",
            "eventsFunctionContext.task = task;",
            "var tbl = String(eventsFunctionContext.getArgument(\"Table\") || \"\").trim();",
            "var id = String(eventsFunctionContext.getArgument(\"IdLigne\") || \"\").trim();",
            "var col = String(eventsFunctionContext.getArgument(\"Champ\") || \"\").trim();",
            "var val = Number(eventsFunctionContext.getArgument(\"Valeur\")) || 0;",
            "var logVar = eventsFunctionContext.getArgument(\"VariableLog\");",
            "var obj = {};",
            "if (col) { obj[col] = val; }",
            "A.updateRow(tbl, id, obj).then(function (r) {",
            "  if (logVar && r && r.log) { A.setVariableData(logVar, r.log); }",
            "  task.resolve();",
            "});",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [
        {
          "description": "Nom de la table",
          "name": "Table",
          "type": "string"
        },
        {
          "description": "Identifiant de la ligne (ID)",
          "name": "IdLigne",
          "type": "string"
        },
        {
          "description": "Nom de la colonne",
          "name": "Champ",
          "type": "string"
        },
        {
          "description": "Nouvelle valeur numerique",
          "name": "Valeur",
          "type": "expression"
        },
        {
          "description": "Variable facultative pour recevoir le log de la requete",
          "name": "VariableLog",
          "type": "scenevar",
          "optional": true
        }
      ],
      "objectGroups": [],
      "async": true
    },
    {
      "description": "Supprime definitivement une ligne d une table Angon.",
      "fullName": "Supprimer une ligne par son identifiant",
      "functionType": "Action",
      "group": "Tables : Suppression",
      "name": "SupprimerLigne",
      "sentence": "Supprimer la ligne _PARAM2_ de la table _PARAM1_ (log: _PARAM3_)",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "var task = new gdjs.ManuallyResolvableTask();",
            "eventsFunctionContext.task = task;",
            "var tbl = String(eventsFunctionContext.getArgument(\"Table\") || \"\").trim();",
            "var id = String(eventsFunctionContext.getArgument(\"IdLigne\") || \"\").trim();",
            "var logVar = eventsFunctionContext.getArgument(\"VariableLog\");",
            "A.deleteRow(tbl, id).then(function (r) {",
            "  if (logVar && r && r.log) { A.setVariableData(logVar, r.log); }",
            "  task.resolve();",
            "});",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [
        {
          "description": "Nom de la table",
          "name": "Table",
          "type": "string"
        },
        {
          "description": "Identifiant de la ligne (ID)",
          "name": "IdLigne",
          "type": "string"
        },
        {
          "description": "Variable facultative pour recevoir le log de la requete",
          "name": "VariableLog",
          "type": "scenevar",
          "optional": true
        }
      ],
      "objectGroups": [],
      "async": true
    },
    {
      "description": "Recherche toutes les lignes d une table correspondant a un critere puis les supprime une par une.",
      "fullName": "Supprimer les lignes correspondant a un filtre",
      "functionType": "Action",
      "group": "Tables : Suppression",
      "name": "SupprimerLignesFiltrees",
      "sentence": "Supprimer les lignes de la table _PARAM1_ ou _PARAM2_ = _PARAM3_ (log: _PARAM4_)",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "var task = new gdjs.ManuallyResolvableTask();",
            "eventsFunctionContext.task = task;",
            "var tbl = String(eventsFunctionContext.getArgument(\"Table\") || \"\").trim();",
            "var col = String(eventsFunctionContext.getArgument(\"Champ\") || \"\").trim();",
            "var val = String(eventsFunctionContext.getArgument(\"Valeur\") || \"\");",
            "var logVar = eventsFunctionContext.getArgument(\"VariableLog\");",
            "var q = { limit: 200 };",
            "if (col) { q[col] = val; }",
            "A.listRows(tbl, q).then(function (r) {",
            "  if (!r.ok || !r.data || r.data.length === 0) {",
            "    if (logVar) {",
            "      A.setVariableData(logVar, { ok: r.ok, count: 0, status: S.statusCode, code: S.errorCode || \"\", message: S.errorMessage || \"No rows found to delete\", raw: S.lastTableResponse || \"\" });",
            "    }",
            "    task.resolve();",
            "    return;",
            "  }",
            "  var promises = [];",
            "  for (var i = 0; i < r.data.length; i++) {",
            "    if (r.data[i] && r.data[i].id) {",
            "      promises.push(A.deleteRow(tbl, r.data[i].id));",
            "    }",
            "  }",
            "  Promise.all(promises).then(function (results) {",
            "    if (logVar) {",
            "      var allOk = results.every(function (res) { return res && res.ok; });",
            "      A.setVariableData(logVar, { ok: allOk, count: promises.length, status: S.statusCode, code: S.errorCode || \"\", message: allOk ? \"Rows deleted successfully\" : \"Some rows could not be deleted\", raw: S.lastTableResponse || \"\" });",
            "    }",
            "    task.resolve();",
            "  }, function () {",
            "    if (logVar) {",
            "      A.setVariableData(logVar, { ok: false, count: 0, status: S.statusCode, code: S.errorCode || \"\", message: S.errorMessage || \"Delete failed\", raw: S.lastTableResponse || \"\" });",
            "    }",
            "    task.resolve();",
            "  });",
            "});",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [
        {
          "description": "Nom de la table",
          "name": "Table",
          "type": "string"
        },
        {
          "description": "Nom de la colonne",
          "name": "Champ",
          "type": "string"
        },
        {
          "description": "Valeur a matcher",
          "name": "Valeur",
          "type": "string"
        },
        {
          "description": "Variable facultative pour recevoir le log de la requete",
          "name": "VariableLog",
          "type": "scenevar",
          "optional": true
        }
      ],
      "objectGroups": [],
      "async": true
    },
    {
      "description": "Vrai si la derniere action sur une table Angon (lecture, insertion, modification, suppression) s est terminee avec succes.",
      "fullName": "La derniere operation de table a reussi",
      "functionType": "Condition",
      "group": "Tables : Donnees et Expressions",
      "name": "TableDerniereOperationReussie",
      "sentence": "La derniere operation sur une table Angon a reussi",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = false;",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = !!S.tableSuccess;",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Vrai si la derniere requete de lecture a retourne au moins une ligne.",
      "fullName": "Le dernier resultat de table contient des lignes",
      "functionType": "Condition",
      "group": "Tables : Donnees et Expressions",
      "name": "TableContientLignes",
      "sentence": "Le dernier resultat de table contient des lignes",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = false;",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = (Number(S.lastTableCount) || 0) > 0;",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Retourne l identifiant (ID) de la derniere ligne creee, modifiee ou chargee dans une table.",
      "fullName": "Identifiant de la derniere ligne traitee",
      "functionType": "StringExpression",
      "group": "Tables : Donnees et Expressions",
      "name": "DernierIdLigne",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = \"\";",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = S.lastCreatedId || \"\";",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "string"
      },
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Retourne le nombre de lignes obtenues lors de la derniere requete de lecture de table.",
      "fullName": "Nombre de lignes du dernier resultat",
      "functionType": "Expression",
      "group": "Tables : Donnees et Expressions",
      "name": "NombreLignesTable",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = 0;",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = Number(S.lastTableCount) || 0;",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "number"
      },
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Retourne l integralite de la derniere liste de lignes chargees sous forme de chaine JSON.",
      "fullName": "Dernieres donnees de table au format JSON",
      "functionType": "StringExpression",
      "group": "Tables : Donnees et Expressions",
      "name": "DonneesTableJson",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = \"\";",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = S.lastTableData ? JSON.stringify(S.lastTableData) : \"[]\";",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "string"
      },
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Retourne la valeur texte d un champ pour une ligne specifiee par son index dans le dernier tableau charge.",
      "fullName": "Valeur texte d un champ par index de ligne",
      "functionType": "StringExpression",
      "group": "Tables : Donnees et Expressions",
      "name": "ValeurChampLigne",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = \"\";",
            "  return;",
            "}",
            "var S = A.state;",
            "var idx = Number(eventsFunctionContext.getArgument(\"Index\")) || 0;",
            "var col = String(eventsFunctionContext.getArgument(\"Champ\") || \"\").trim();",
            "if (!S.lastTableData || !S.lastTableData[idx] || !col) {",
            "  eventsFunctionContext.returnValue = \"\";",
            "  return;",
            "}",
            "var val = S.lastTableData[idx][col];",
            "if (val === undefined || val === null) {",
            "  eventsFunctionContext.returnValue = \"\";",
            "} else if (typeof val === \"object\") {",
            "  eventsFunctionContext.returnValue = JSON.stringify(val);",
            "} else {",
            "  eventsFunctionContext.returnValue = String(val);",
            "}",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "string"
      },
      "parameters": [
        {
          "description": "Index de la ligne dans la liste (0 pour la premiere)",
          "name": "Index",
          "type": "expression"
        },
        {
          "description": "Nom du champ / de la colonne",
          "name": "Champ",
          "type": "string"
        }
      ],
      "objectGroups": []
    },
    {
      "description": "Retourne la valeur numerique d un champ pour une ligne specifiee par son index dans le dernier tableau charge.",
      "fullName": "Valeur numerique d un champ par index de ligne",
      "functionType": "Expression",
      "group": "Tables : Donnees et Expressions",
      "name": "ValeurNombreChampLigne",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = 0;",
            "  return;",
            "}",
            "var S = A.state;",
            "var idx = Number(eventsFunctionContext.getArgument(\"Index\")) || 0;",
            "var col = String(eventsFunctionContext.getArgument(\"Champ\") || \"\").trim();",
            "if (!S.lastTableData || !S.lastTableData[idx] || !col) {",
            "  eventsFunctionContext.returnValue = 0;",
            "  return;",
            "}",
            "var val = Number(S.lastTableData[idx][col]);",
            "eventsFunctionContext.returnValue = isNaN(val) ? 0 : val;",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "number"
      },
      "parameters": [
        {
          "description": "Index de la ligne dans la liste (0 pour la premiere)",
          "name": "Index",
          "type": "expression"
        },
        {
          "description": "Nom du champ / de la colonne",
          "name": "Champ",
          "type": "string"
        }
      ],
      "objectGroups": []
    },
    {
      "description": "Retourne la valeur texte d un champ pour la derniere ligne unique obtenue ou creee.",
      "fullName": "Valeur texte d un champ de la derniere ligne unique",
      "functionType": "StringExpression",
      "group": "Tables : Donnees et Expressions",
      "name": "LigneChampTexte",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = \"\";",
            "  return;",
            "}",
            "var S = A.state;",
            "var col = String(eventsFunctionContext.getArgument(\"Champ\") || \"\").trim();",
            "if (!S.lastTableRow || !col) {",
            "  eventsFunctionContext.returnValue = \"\";",
            "  return;",
            "}",
            "var val = S.lastTableRow[col];",
            "if (val === undefined || val === null) {",
            "  eventsFunctionContext.returnValue = \"\";",
            "} else if (typeof val === \"object\") {",
            "  eventsFunctionContext.returnValue = JSON.stringify(val);",
            "} else {",
            "  eventsFunctionContext.returnValue = String(val);",
            "}",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "string"
      },
      "parameters": [
        {
          "description": "Nom du champ / de la colonne",
          "name": "Champ",
          "type": "string"
        }
      ],
      "objectGroups": []
    },
    {
      "description": "Retourne la valeur numerique d un champ pour la derniere ligne unique obtenue ou creee.",
      "fullName": "Valeur numerique d un champ de la derniere ligne unique",
      "functionType": "Expression",
      "group": "Tables : Donnees et Expressions",
      "name": "LigneChampNombre",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = 0;",
            "  return;",
            "}",
            "var S = A.state;",
            "var col = String(eventsFunctionContext.getArgument(\"Champ\") || \"\").trim();",
            "if (!S.lastTableRow || !col) {",
            "  eventsFunctionContext.returnValue = 0;",
            "  return;",
            "}",
            "var val = Number(S.lastTableRow[col]);",
            "eventsFunctionContext.returnValue = isNaN(val) ? 0 : val;",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "number"
      },
      "parameters": [
        {
          "description": "Nom du champ / de la colonne",
          "name": "Champ",
          "type": "string"
        }
      ],
      "objectGroups": []
    },
    {
      "description": "Vrai si l appareil du joueur dispose d une connexion internet active.",
      "fullName": "L appareil est connecte a internet",
      "functionType": "Condition",
      "group": "Mode Hors-Ligne et Synchronisation",
      "name": "EstEnLigne",
      "sentence": "L appareil est connecte a internet",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "eventsFunctionContext.returnValue = (typeof navigator === \"undefined\" || navigator.onLine !== false);",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Vrai si l appareil du joueur est deconnecte d internet (mode avion, coupure reseau).",
      "fullName": "L appareil est hors ligne",
      "functionType": "Condition",
      "group": "Mode Hors-Ligne et Synchronisation",
      "name": "EstHorsLigne",
      "sentence": "L appareil est hors ligne",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "eventsFunctionContext.returnValue = (typeof navigator !== \"undefined\" && navigator.onLine === false);",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Vrai si des sauvegardes ou modifications de tables ont ete effectuees hors-ligne et doivent encore etre envoyees a Angon Cloud.",
      "fullName": "Des donnees locales sont en attente de synchronisation",
      "functionType": "Condition",
      "group": "Mode Hors-Ligne et Synchronisation",
      "name": "DonneesEnAttenteDeSynchronisation",
      "sentence": "Des donnees locales sont en attente de synchronisation",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = false;",
            "  return;",
            "}",
            "var S = A.state;",
            "var q = A.getSyncQueue();",
            "eventsFunctionContext.returnValue = (Array.isArray(q) && q.length > 0);",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Vrai pendant que la file d attente locale est en cours d envoi vers Angon Cloud.",
      "fullName": "Une synchronisation vers le cloud est en cours",
      "functionType": "Condition",
      "group": "Mode Hors-Ligne et Synchronisation",
      "name": "SynchronisationEnCours",
      "sentence": "Une synchronisation vers le cloud est en cours",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = false;",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = !!S.syncing;",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Nombre de modifications locales en attente d envoi vers le serveur Angon.",
      "fullName": "Nombre d operations en attente de synchronisation",
      "functionType": "Expression",
      "group": "Mode Hors-Ligne et Synchronisation",
      "name": "NombreOperationsEnAttente",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = 0;",
            "  return;",
            "}",
            "var S = A.state;",
            "var q = A.getSyncQueue();",
            "eventsFunctionContext.returnValue = Array.isArray(q) ? q.length : 0;",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "number"
      },
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Declenche l envoi immediat des donnees locales en attente vers le serveur Angon. L action attend la fin de l envoi.",
      "fullName": "Synchroniser les donnees locales vers Angon Cloud",
      "functionType": "Action",
      "group": "Mode Hors-Ligne et Synchronisation",
      "name": "ForcerSynchronisation",
      "sentence": "Synchroniser les donnees locales vers Angon Cloud",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "var task = new gdjs.ManuallyResolvableTask();",
            "eventsFunctionContext.task = task;",
            "A.syncPendingQueue().then(function () {",
            "  task.resolve();",
            "}, function () {",
            "  task.resolve();",
            "});",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [],
      "objectGroups": [],
      "async": true
    },
    {
      "description": "Definit si une table autorise le mode hors-ligne : 'autorise' (complet), 'lecture_seule' (consultable mais ecritures bloquees), ou 'desactive' (strictement en ligne, ideal pour la boutique).",
      "fullName": "Definir la politique hors-ligne d une table",
      "functionType": "Action",
      "group": "Mode Hors-Ligne et Synchronisation",
      "name": "DefinirPolitiqueHorsLigneTable",
      "sentence": "Definir la politique hors-ligne de la table _PARAM1_ a _PARAM2_",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "var tbl = String(eventsFunctionContext.getArgument(\"Table\") || \"\").trim();",
            "var pol = String(eventsFunctionContext.getArgument(\"Politique\") || \"\").trim();",
            "A.setTablePolicy(tbl, pol);",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [
        {
          "description": "Nom de la table (ex: boutique, inventaire)",
          "name": "Table",
          "type": "string"
        },
        {
          "description": "Politique hors-ligne ('autorise', 'lecture_seule', 'desactive')",
          "name": "Politique",
          "type": "string"
        }
      ],
      "objectGroups": []
    },
    {
      "description": "Telecharge les dernieres politiques d acces hors-ligne configurees dans Angon Cloud pour chaque table.",
      "fullName": "Actualiser les politiques hors-ligne depuis le serveur",
      "functionType": "Action",
      "group": "Mode Hors-Ligne et Synchronisation",
      "name": "ActualiserPolitiquesTables",
      "sentence": "Actualiser les politiques hors-ligne des tables depuis Angon Cloud",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "var task = new gdjs.ManuallyResolvableTask();",
            "eventsFunctionContext.task = task;",
            "A.fetchTablePolicies().then(function () {",
            "  task.resolve();",
            "}, function () {",
            "  task.resolve();",
            "});",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [],
      "objectGroups": [],
      "async": true
    },
    {
      "description": "Vrai si la table autorise l acces en mode hors-ligne (politique differente de 'desactive').",
      "fullName": "La table autorise le mode hors-ligne",
      "functionType": "Condition",
      "group": "Mode Hors-Ligne et Synchronisation",
      "name": "TableAutoriseHorsLigne",
      "sentence": "La table _PARAM1_ autorise le mode hors-ligne",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = false;",
            "  return;",
            "}",
            "var S = A.state;",
            "var tbl = String(eventsFunctionContext.getArgument(\"Table\") || \"\").trim();",
            "eventsFunctionContext.returnValue = (A.getTablePolicy(tbl) !== \"desactive\");",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [
        {
          "description": "Nom de la table",
          "name": "Table",
          "type": "string"
        }
      ],
      "objectGroups": []
    },
    {
      "description": "Vrai si la table autorise la lecture en cache mais refuse les creations et modifications hors-ligne.",
      "fullName": "La table est en lecture seule hors-ligne",
      "functionType": "Condition",
      "group": "Mode Hors-Ligne et Synchronisation",
      "name": "TableEnLectureSeuleHorsLigne",
      "sentence": "La table _PARAM1_ est en lecture seule hors-ligne",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = false;",
            "  return;",
            "}",
            "var S = A.state;",
            "var tbl = String(eventsFunctionContext.getArgument(\"Table\") || \"\").trim();",
            "eventsFunctionContext.returnValue = (A.getTablePolicy(tbl) === \"lecture_seule\");",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [
        {
          "description": "Nom de la table",
          "name": "Table",
          "type": "string"
        }
      ],
      "objectGroups": []
    },
    {
      "description": "Renvoie la politique hors-ligne actuelle de la table ('autorise', 'lecture_seule', ou 'desactive').",
      "fullName": "Politique hors-ligne de la table",
      "functionType": "StringExpression",
      "group": "Mode Hors-Ligne et Synchronisation",
      "name": "PolitiqueHorsLigneTable",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = \"\";",
            "  return;",
            "}",
            "var S = A.state;",
            "var tbl = String(eventsFunctionContext.getArgument(\"Table\") || \"\").trim();",
            "eventsFunctionContext.returnValue = A.getTablePolicy(tbl);",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "string"
      },
      "parameters": [
        {
          "description": "Nom de la table",
          "name": "Table",
          "type": "string"
        }
      ],
      "objectGroups": []
    },
    {
      "description": "Telecharge tous les articles actifs de la boutique et stocke la liste dans une variable de scene.",
      "fullName": "Charger les articles de la boutique",
      "functionType": "Action",
      "group": "Boutique Angon Cloud",
      "name": "ListerArticlesBoutique",
      "sentence": "Charger les articles de la boutique dans la variable _PARAM1_",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "var task = new gdjs.ManuallyResolvableTask();",
            "eventsFunctionContext.task = task;",
            "var targetVar = eventsFunctionContext.getArgument(\"VariableCible\");",
            "A.listShopArticles().then(function (r) {",
            "  if (r.ok && targetVar) {",
            "    A.setVariableData(targetVar, r.data);",
            "  }",
            "  task.resolve();",
            "}, function () {",
            "  task.resolve();",
            "});",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [
        {
          "description": "Variable de scene pour recevoir la liste des articles",
          "name": "VariableCible",
          "type": "scenevar"
        }
      ],
      "objectGroups": [],
      "async": true
    },
    {
      "description": "Vrai si au moins un article actif a ete charge depuis la boutique.",
      "fullName": "La boutique contient des articles",
      "functionType": "Condition",
      "group": "Boutique Angon Cloud",
      "name": "BoutiqueContientArticles",
      "sentence": "La boutique contient des articles",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = false;",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = (Array.isArray(S.shopArticles) && S.shopArticles.length > 0);",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Renvoie le nombre d articles actifs actuellement charges dans la boutique.",
      "fullName": "Nombre d articles dans la boutique",
      "functionType": "Expression",
      "group": "Boutique Angon Cloud",
      "name": "NombreArticlesBoutique",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = 0;",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = Array.isArray(S.shopArticles) ? S.shopArticles.length : 0;",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "number"
      },
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Renvoie le code unique de l article a l index donne (0 pour le premier).",
      "fullName": "Code identifiant d un article de la boutique",
      "functionType": "StringExpression",
      "group": "Boutique Angon Cloud",
      "name": "ArticleBoutiqueCode",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = \"\";",
            "  return;",
            "}",
            "var S = A.state;",
            "var idx = Number(eventsFunctionContext.getArgument(\"Index\")) || 0;",
            "var art = (Array.isArray(S.shopArticles) && idx >= 0 && idx < S.shopArticles.length) ? S.shopArticles[idx] : null;",
            "eventsFunctionContext.returnValue = (art && art.code) ? String(art.code) : \"\";",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "string"
      },
      "parameters": [
        {
          "description": "Index de l article (0 pour le premier)",
          "name": "Index",
          "type": "number"
        }
      ],
      "objectGroups": []
    },
    {
      "description": "Renvoie le nom de l article a l index donne (0 pour le premier).",
      "fullName": "Nom d un article de la boutique",
      "functionType": "StringExpression",
      "group": "Boutique Angon Cloud",
      "name": "ArticleBoutiqueNom",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = \"\";",
            "  return;",
            "}",
            "var S = A.state;",
            "var idx = Number(eventsFunctionContext.getArgument(\"Index\")) || 0;",
            "var art = (Array.isArray(S.shopArticles) && idx >= 0 && idx < S.shopArticles.length) ? S.shopArticles[idx] : null;",
            "eventsFunctionContext.returnValue = (art && art.nom) ? String(art.nom) : \"\";",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "string"
      },
      "parameters": [
        {
          "description": "Index de l article (0 pour le premier)",
          "name": "Index",
          "type": "number"
        }
      ],
      "objectGroups": []
    },
    {
      "description": "Renvoie la description de l article a l index donne.",
      "fullName": "Description d un article de la boutique",
      "functionType": "StringExpression",
      "group": "Boutique Angon Cloud",
      "name": "ArticleBoutiqueDescription",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = \"\";",
            "  return;",
            "}",
            "var S = A.state;",
            "var idx = Number(eventsFunctionContext.getArgument(\"Index\")) || 0;",
            "var art = (Array.isArray(S.shopArticles) && idx >= 0 && idx < S.shopArticles.length) ? S.shopArticles[idx] : null;",
            "eventsFunctionContext.returnValue = (art && art.description) ? String(art.description) : \"\";",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "string"
      },
      "parameters": [
        {
          "description": "Index de l article (0 pour le premier)",
          "name": "Index",
          "type": "number"
        }
      ],
      "objectGroups": []
    },
    {
      "description": "Renvoie le prix de l article a l index donne.",
      "fullName": "Prix d un article de la boutique",
      "functionType": "Expression",
      "group": "Boutique Angon Cloud",
      "name": "ArticleBoutiquePrix",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = 0;",
            "  return;",
            "}",
            "var S = A.state;",
            "var idx = Number(eventsFunctionContext.getArgument(\"Index\")) || 0;",
            "var art = (Array.isArray(S.shopArticles) && idx >= 0 && idx < S.shopArticles.length) ? S.shopArticles[idx] : null;",
            "var p = art ? Number(art.prix) : 0;",
            "eventsFunctionContext.returnValue = isNaN(p) ? 0 : p;",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "number"
      },
      "parameters": [
        {
          "description": "Index de l article (0 pour le premier)",
          "name": "Index",
          "type": "number"
        }
      ],
      "objectGroups": []
    },
    {
      "description": "Renvoie la devise de vente de l article (pieces, gemmes, or, fcfa, etc.).",
      "fullName": "Devise d un article de la boutique",
      "functionType": "StringExpression",
      "group": "Boutique Angon Cloud",
      "name": "ArticleBoutiqueDevise",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = \"\";",
            "  return;",
            "}",
            "var S = A.state;",
            "var idx = Number(eventsFunctionContext.getArgument(\"Index\")) || 0;",
            "var art = (Array.isArray(S.shopArticles) && idx >= 0 && idx < S.shopArticles.length) ? S.shopArticles[idx] : null;",
            "eventsFunctionContext.returnValue = (art && art.devise) ? String(art.devise) : \"pieces\";",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "string"
      },
      "parameters": [
        {
          "description": "Index de l article (0 pour le premier)",
          "name": "Index",
          "type": "number"
        }
      ],
      "objectGroups": []
    },
    {
      "description": "Renvoie l URL de l image ou icone du produit.",
      "fullName": "URL de l icone d un article de la boutique",
      "functionType": "StringExpression",
      "group": "Boutique Angon Cloud",
      "name": "ArticleBoutiqueIcone",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = \"\";",
            "  return;",
            "}",
            "var S = A.state;",
            "var idx = Number(eventsFunctionContext.getArgument(\"Index\")) || 0;",
            "var art = (Array.isArray(S.shopArticles) && idx >= 0 && idx < S.shopArticles.length) ? S.shopArticles[idx] : null;",
            "eventsFunctionContext.returnValue = (art && art.icone_url) ? String(art.icone_url) : \"\";",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "string"
      },
      "parameters": [
        {
          "description": "Index de l article (0 pour le premier)",
          "name": "Index",
          "type": "number"
        }
      ],
      "objectGroups": []
    },
    {
      "description": "Renvoie 'consommable' ou 'permanent'.",
      "fullName": "Type d un article de la boutique",
      "functionType": "StringExpression",
      "group": "Boutique Angon Cloud",
      "name": "ArticleBoutiqueType",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = \"\";",
            "  return;",
            "}",
            "var S = A.state;",
            "var idx = Number(eventsFunctionContext.getArgument(\"Index\")) || 0;",
            "var art = (Array.isArray(S.shopArticles) && idx >= 0 && idx < S.shopArticles.length) ? S.shopArticles[idx] : null;",
            "eventsFunctionContext.returnValue = (art && art.type) ? String(art.type) : \"consommable\";",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "string"
      },
      "parameters": [
        {
          "description": "Index de l article (0 pour le premier)",
          "name": "Index",
          "type": "number"
        }
      ],
      "objectGroups": []
    },
    {
      "description": "Vrai si l article specifie beneficie d une promotion active.",
      "fullName": "L article de la boutique est en promotion",
      "functionType": "Condition",
      "group": "Boutique Angon Cloud",
      "name": "ArticleBoutiqueEstEnPromo",
      "sentence": "L article _PARAM1_ de la boutique est en promotion",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = false;",
            "  return;",
            "}",
            "var S = A.state;",
            "var idx = Number(eventsFunctionContext.getArgument(\"Index\")) || 0;",
            "var art = (Array.isArray(S.shopArticles) && idx >= 0 && idx < S.shopArticles.length) ? S.shopArticles[idx] : null;",
            "eventsFunctionContext.returnValue = !!(art && art.en_promotion);",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [
        {
          "description": "Index de l article (0 pour le premier)",
          "name": "Index",
          "type": "number"
        }
      ],
      "objectGroups": []
    },
    {
      "description": "Renvoie le prix promotionnel de l article si en promotion (ou le prix normal sinon).",
      "fullName": "Prix promotionnel d un article de la boutique",
      "functionType": "Expression",
      "group": "Boutique Angon Cloud",
      "name": "ArticleBoutiquePrixPromo",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = 0;",
            "  return;",
            "}",
            "var S = A.state;",
            "var idx = Number(eventsFunctionContext.getArgument(\"Index\")) || 0;",
            "var art = (Array.isArray(S.shopArticles) && idx >= 0 && idx < S.shopArticles.length) ? S.shopArticles[idx] : null;",
            "if (!art) { eventsFunctionContext.returnValue = 0; return; }",
            "var promoVal = (art.en_promotion && art.prix_promo !== null && art.prix_promo !== undefined) ? Number(art.prix_promo) : Number(art.prix);",
            "eventsFunctionContext.returnValue = isNaN(promoVal) ? 0 : promoVal;",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "number"
      },
      "parameters": [
        {
          "description": "Index de l article (0 pour le premier)",
          "name": "Index",
          "type": "number"
        }
      ],
      "objectGroups": []
    },
    {
      "description": "Envoie un score au classement specifie par son code ou son identifiant unique (ID). Le serveur applique la regle de score choisie sur Angon Cloud (meilleur score, ecraser, cumulatif ou brut).",
      "fullName": "Envoyer un score au classement",
      "functionType": "Action",
      "group": "Classements",
      "name": "SoumettreScore",
      "sentence": "Envoyer le score _PARAM1_ au classement _PARAM0_ (resultat dans _PARAM2_)",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "var task = new gdjs.ManuallyResolvableTask();",
            "eventsFunctionContext.task = task;",
            "var cl = String(eventsFunctionContext.getArgument(\"Classement\") || \"\").trim();",
            "var val = Number(eventsFunctionContext.getArgument(\"Score\"));",
            "var logVar = eventsFunctionContext.getArgument(\"VariableLog\");",
            "",
            "A.submitScore(cl, val).then(function (res) {",
            "  if (logVar && res && res.log) {",
            "    A.setVariableData(logVar, res.log);",
            "  }",
            "  task.resolve();",
            "}, function () {",
            "  task.resolve();",
            "});",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [
        {
          "description": "Code ou Identifiant (ID) du classement",
          "name": "Classement",
          "type": "string"
        },
        {
          "description": "Valeur numerique du score (points, chrono, etc.)",
          "name": "Score",
          "type": "number"
        },
        {
          "description": "Variable facultative pour recevoir le resultat { ok, rang, meilleur }",
          "name": "VariableLog",
          "type": "scenevar",
          "optional": true
        }
      ],
      "objectGroups": [],
      "async": true
    },
    {
      "description": "Telecharge les meilleurs scores d un classement par son code ou son identifiant unique et les stocke dans une variable de scene.",
      "fullName": "Recuperer le top du classement",
      "functionType": "Action",
      "group": "Classements",
      "name": "ChargerClassement",
      "sentence": "Telecharger le top _PARAM1_ du classement _PARAM0_ dans la variable _PARAM2_",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "var task = new gdjs.ManuallyResolvableTask();",
            "eventsFunctionContext.task = task;",
            "var cl = String(eventsFunctionContext.getArgument(\"Classement\") || \"\").trim();",
            "var lim = Number(eventsFunctionContext.getArgument(\"Limite\")) || 50;",
            "var targetVar = eventsFunctionContext.getArgument(\"VariableCible\");",
            "",
            "A.fetchLeaderboard(cl, lim).then(function (res) {",
            "  if (targetVar && res && res.data) {",
            "    A.setVariableData(targetVar, res.data);",
            "  }",
            "  task.resolve();",
            "}, function () {",
            "  task.resolve();",
            "});",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [
        {
          "description": "Code ou Identifiant (ID) du classement",
          "name": "Classement",
          "type": "string"
        },
        {
          "description": "Nombre maximum de scores (ex: 20, 50)",
          "name": "Limite",
          "type": "number"
        },
        {
          "description": "Variable de scene pour recevoir la liste des entrees",
          "name": "VariableCible",
          "type": "scenevar"
        }
      ],
      "objectGroups": [],
      "async": true
    },
    {
      "description": "Vrai si le dernier score a ete accepte par le classement sur Angon Cloud.",
      "fullName": "Le score a ete enregistre avec succes",
      "functionType": "Condition",
      "group": "Classements",
      "name": "DernierScoreEnvoye",
      "sentence": "Le dernier score a ete enregistre avec succes",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = false;",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = !!S.leaderboardSuccess;",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Renvoie la position / rang (1er, 2e, ...) obtenue par le joueur lors du dernier envoi de score.",
      "fullName": "Dernier rang obtenu au classement",
      "functionType": "Expression",
      "group": "Classements",
      "name": "DernierRangClassement",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = 0;",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = Number(S.lastLeaderboardRank) || 0;",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "number"
      },
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Renvoie le score actuellement retenu pour le joueur sur le classement (apres regle serveur).",
      "fullName": "Dernier score valide au classement",
      "functionType": "Expression",
      "group": "Classements",
      "name": "DernierScoreClassement",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = 0;",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = Number(S.lastLeaderboardScore) || 0;",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "number"
      },
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Renvoie le code du classement utilise lors du dernier envoi.",
      "fullName": "Dernier code de classement utilise",
      "functionType": "StringExpression",
      "group": "Classements",
      "name": "DernierCodeClassement",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = \"\";",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = S.lastLeaderboardCode || \"\";",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "string"
      },
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Renvoie le nombre de scores recuperes lors du dernier appel de 'Recuperer le top du classement'.",
      "fullName": "Nombre de scores charges dans le classement",
      "functionType": "Expression",
      "group": "Classements",
      "name": "NombreEntreesClassement",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = 0;",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = Array.isArray(S.leaderboardEntries) ? S.leaderboardEntries.length : 0;",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "number"
      },
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Renvoie le pseudo du joueur classe a l index specifie (0 pour le 1er).",
      "fullName": "Pseudo du joueur classe a l index",
      "functionType": "StringExpression",
      "group": "Classements",
      "name": "EntreeClassementPseudo",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = \"\";",
            "  return;",
            "}",
            "var S = A.state;",
            "var idx = Number(eventsFunctionContext.getArgument(\"Index\")) || 0;",
            "var e = (Array.isArray(S.leaderboardEntries) && idx >= 0 && idx < S.leaderboardEntries.length) ? S.leaderboardEntries[idx] : null;",
            "eventsFunctionContext.returnValue = (e && e.pseudo) ? String(e.pseudo) : \"\";",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "string"
      },
      "parameters": [
        {
          "description": "Index dans la liste (0 pour le 1er)",
          "name": "Index",
          "type": "number"
        }
      ],
      "objectGroups": []
    },
    {
      "description": "Renvoie la valeur du score du joueur classe a l index specifie (0 pour le 1er).",
      "fullName": "Score du joueur classe a l index",
      "functionType": "Expression",
      "group": "Classements",
      "name": "EntreeClassementScore",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = 0;",
            "  return;",
            "}",
            "var S = A.state;",
            "var idx = Number(eventsFunctionContext.getArgument(\"Index\")) || 0;",
            "var e = (Array.isArray(S.leaderboardEntries) && idx >= 0 && idx < S.leaderboardEntries.length) ? S.leaderboardEntries[idx] : null;",
            "var val = e ? Number(e.valeur) : 0;",
            "eventsFunctionContext.returnValue = isNaN(val) ? 0 : val;",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "number"
      },
      "parameters": [
        {
          "description": "Index dans la liste (0 pour le 1er)",
          "name": "Index",
          "type": "number"
        }
      ],
      "objectGroups": []
    },
    {
      "description": "Renvoie le rang officiel (1, 2, 3...) du joueur classe a l index donne.",
      "fullName": "Rang du joueur classe a l index",
      "functionType": "Expression",
      "group": "Classements",
      "name": "EntreeClassementRang",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = 0;",
            "  return;",
            "}",
            "var S = A.state;",
            "var idx = Number(eventsFunctionContext.getArgument(\"Index\")) || 0;",
            "var e = (Array.isArray(S.leaderboardEntries) && idx >= 0 && idx < S.leaderboardEntries.length) ? S.leaderboardEntries[idx] : null;",
            "var r = e ? Number(e.rang) : (idx + 1);",
            "eventsFunctionContext.returnValue = isNaN(r) ? (idx + 1) : r;",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "number"
      },
      "parameters": [
        {
          "description": "Index dans la liste (0 pour le 1er)",
          "name": "Index",
          "type": "number"
        }
      ],
      "objectGroups": []
    },
    {
      "description": "Renvoie le code pays ou nom du pays du joueur classe a l index donne.",
      "fullName": "Pays du joueur classe a l index",
      "functionType": "StringExpression",
      "group": "Classements",
      "name": "EntreeClassementPays",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = \"\";",
            "  return;",
            "}",
            "var S = A.state;",
            "var idx = Number(eventsFunctionContext.getArgument(\"Index\")) || 0;",
            "var e = (Array.isArray(S.leaderboardEntries) && idx >= 0 && idx < S.leaderboardEntries.length) ? S.leaderboardEntries[idx] : null;",
            "eventsFunctionContext.returnValue = (e && e.pays) ? String(e.pays) : \"\";",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "string"
      },
      "parameters": [
        {
          "description": "Index dans la liste (0 pour le 1er)",
          "name": "Index",
          "type": "number"
        }
      ],
      "objectGroups": []
    },
    {
      "description": "Inscrit le joueur connecte dans la file d attente du mode choisi (1v1, 2v2...). Le serveur regroupe automatiquement les joueurs et cree le salon des que le mode est complet. Resultat : status waiting|matched, position, partie_id, code_salon, equipe.",
      "fullName": "Rejoindre la file d attente multijoueur",
      "functionType": "Action",
      "group": "Multijoueur",
      "name": "RejoindreFileAttente",
      "sentence": "Rejoindre la file d attente du mode _PARAM0_ (resultat dans _PARAM1_)",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "var task = new gdjs.ManuallyResolvableTask();",
            "eventsFunctionContext.task = task;",
            "var mode = String(eventsFunctionContext.getArgument(\"Mode\") || \"1v1\").trim();",
            "var logVar = eventsFunctionContext.getArgument(\"VariableLog\");",
            "",
            "A.matchmakingJoin(mode).then(function (res) {",
            "  if (logVar && res && res.log) {",
            "    A.setVariableData(logVar, res.log);",
            "  }",
            "  task.resolve();",
            "}, function () {",
            "  task.resolve();",
            "});",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [
        {
          "description": "Code du mode de jeu (ex : 1v1, 2v2)",
          "name": "Mode",
          "type": "string"
        },
        {
          "description": "Variable facultative pour recevoir le resultat",
          "name": "VariableLog",
          "type": "scenevar",
          "optional": true
        }
      ],
      "objectGroups": [],
      "async": true
    },
    {
      "description": "Retire le joueur de toutes les files d attente en cours.",
      "fullName": "Quitter la file d attente multijoueur",
      "functionType": "Action",
      "group": "Multijoueur",
      "name": "QuitterFileAttente",
      "sentence": "Quitter la file d attente multijoueur",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "var task = new gdjs.ManuallyResolvableTask();",
            "eventsFunctionContext.task = task;",
            "A.matchmakingLeave().then(function () {",
            "  task.resolve();",
            "}, function () {",
            "  task.resolve();",
            "});",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [],
      "objectGroups": [],
      "async": true
    },
    {
      "description": "Interroge le serveur pour savoir si un salon a ete trouve (a appeler regulierement, ex : toutes les 2 secondes, pendant que 'En file d attente' est vrai). Met a jour le statut et remplit l id de partie, le code du salon et l equipe quand le match est trouve.",
      "fullName": "Verifier le statut du matchmaking",
      "functionType": "Action",
      "group": "Multijoueur",
      "name": "VerifierFileAttente",
      "sentence": "Verifier le statut du matchmaking (resultat dans _PARAM0_)",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "var task = new gdjs.ManuallyResolvableTask();",
            "eventsFunctionContext.task = task;",
            "var logVar = eventsFunctionContext.getArgument(\"VariableLog\");",
            "",
            "A.matchmakingStatus().then(function (res) {",
            "  if (logVar && res && res.log) {",
            "    A.setVariableData(logVar, res.log);",
            "  }",
            "  task.resolve();",
            "}, function () {",
            "  task.resolve();",
            "});",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [
        {
          "description": "Variable facultative pour recevoir le resultat",
          "name": "VariableLog",
          "type": "scenevar",
          "optional": true
        }
      ],
      "objectGroups": [],
      "async": true
    },
    {
      "description": "Vrai tant que le joueur attend que le serveur trouve un match.",
      "fullName": "Le joueur est en file d attente",
      "functionType": "Condition",
      "group": "Multijoueur",
      "name": "EnFileDAttente",
      "sentence": "Le joueur est en file d attente",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = false;",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = S.mmStatus === \"waiting\";",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Vrai quand le matchmaking a forme un salon. Lisez IdPartie, CodeSalon et MonEquipe, puis rejoignez le salon temps-reel.",
      "fullName": "Un salon multijoueur a ete trouve",
      "functionType": "Condition",
      "group": "Multijoueur",
      "name": "PartieTrouvee",
      "sentence": "Un salon multijoueur a ete trouve",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = false;",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = S.mmStatus === \"matched\";",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Renvoie la position du joueur dans la file (1 = prochain a etre apparie).",
      "fullName": "Position dans la file d attente",
      "functionType": "Expression",
      "group": "Multijoueur",
      "name": "PositionFileAttente",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = 0;",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = Number(S.mmPosition) || 0;",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "number"
      },
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Renvoie l identifiant unique de la partie trouvee (a stocker pour lire/ecrire la ligne de la table parties_multijoueur).",
      "fullName": "Identifiant de la partie multijoueur",
      "functionType": "StringExpression",
      "group": "Multijoueur",
      "name": "IdPartieMultijoueur",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = \"\";",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = S.mmPartieId || \"\";",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "string"
      },
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Renvoie le code lisible du salon (ex : SALON_AB123).",
      "fullName": "Code du salon multijoueur",
      "functionType": "StringExpression",
      "group": "Multijoueur",
      "name": "CodeSalonMultijoueur",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = \"\";",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = S.mmCodeSalon || \"\";",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "string"
      },
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Renvoie le numero d equipe attribue au joueur par le matchmaking (1, 2, ...).",
      "fullName": "Equipe du joueur dans le salon",
      "functionType": "Expression",
      "group": "Multijoueur",
      "name": "MonEquipe",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = 0;",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = Number(S.mmEquipe) || 0;",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "number"
      },
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Renvoie la liste JSON des participants du salon [{joueur_id, pseudo, avatar, equipe, statut}]. Convertissez-la avec ToJSON vers une variable.",
      "fullName": "Participants du salon (JSON)",
      "functionType": "StringExpression",
      "group": "Multijoueur",
      "name": "ParticipantsSalonJson",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = \"[]\";",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = S.mmParticipantsJson || \"[]\";",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "string"
      },
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Ouvre la connexion temps-reel du salon trouve par le matchmaking (notifications quasi instantanees des coups des autres joueurs). A appeler une fois que 'Un salon a ete trouve' est vrai.",
      "fullName": "Rejoindre le canal temps-reel du salon",
      "functionType": "Action",
      "group": "Multijoueur",
      "name": "RejoindreSalonTempsReel",
      "sentence": "Rejoindre le canal temps-reel du salon (resultat dans _PARAM0_)",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "var task = new gdjs.ManuallyResolvableTask();",
            "eventsFunctionContext.task = task;",
            "var logVar = eventsFunctionContext.getArgument(\"VariableLog\");",
            "A.roomJoin(S.mmPartieId).then(function (ok) {",
            "  if (logVar) { A.setVariableData(logVar, { ok: !!ok }); }",
            "  task.resolve();",
            "}, function () {",
            "  task.resolve();",
            "});",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [
        {
          "description": "Variable facultative pour recevoir { ok }",
          "name": "VariableLog",
          "type": "scenevar",
          "optional": true
        }
      ],
      "objectGroups": [],
      "async": true
    },
    {
      "description": "Ferme la connexion temps-reel du salon en cours.",
      "fullName": "Quitter le canal temps-reel du salon",
      "functionType": "Action",
      "group": "Multijoueur",
      "name": "QuitterSalonTempsReel",
      "sentence": "Quitter le canal temps-reel du salon",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "A.roomLeave();",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Vrai quand la connexion temps-reel du salon est ouverte et prete.",
      "fullName": "Le canal temps-reel du salon est connecte",
      "functionType": "Condition",
      "group": "Multijoueur",
      "name": "SalonTempsReelConnecte",
      "sentence": "Le canal temps-reel du salon est connecte",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = false;",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = !!S.rtJoined;",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Diffuse instantanement une variable (coup joue, etat, etc.) aux autres joueurs connectes au canal du salon. Pensez aussi a persister l etat dans la table parties_multijoueur pour les joueurs qui se reconnecteraient.",
      "fullName": "Envoyer une action aux autres joueurs du salon",
      "functionType": "Action",
      "group": "Multijoueur",
      "name": "EnvoyerActionSalon",
      "sentence": "Envoyer la variable _PARAM0_ aux autres joueurs du salon",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "var v = eventsFunctionContext.getArgument(\"Donnees\");",
            "A.roomSend(v);",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [
        {
          "description": "Variable contenant les donnees a diffuser (ex : le coup joue)",
          "name": "Donnees",
          "type": "scenevar"
        }
      ],
      "objectGroups": []
    },
    {
      "description": "Vrai une fois par nouvelle action recue sur le canal du salon. Lisez DerniereActionSalon pour recuperer les donnees (JSON).",
      "fullName": "Une action d un autre joueur a ete recue",
      "functionType": "Condition",
      "group": "Multijoueur",
      "name": "ActionSalonRecue",
      "sentence": "Une action d un autre joueur a ete recue",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = false;",
            "  return;",
            "}",
            "var S = A.state;",
            "var unread = S.rtMsgCount - S.rtMsgRead;",
            "if (unread <= 0) { eventsFunctionContext.returnValue = false; return; }",
            "S.rtMsgRead = S.rtMsgCount;",
            "eventsFunctionContext.returnValue = true;",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Renvoie le JSON de la derniere action diffusee par un autre joueur du salon. Convertissez-la avec ToJSON vers une variable.",
      "fullName": "Derniere action recue du salon (JSON)",
      "functionType": "StringExpression",
      "group": "Multijoueur",
      "name": "DerniereActionSalon",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = \"\";",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = S.rtLastMsg || \"\";",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "string"
      },
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Envoie une action de match au serveur : les regles definies dans l onglet Regles de la console (evenement 'action_partie') la valident et peuvent mettre a jour l etat officiel de la partie, qui est ensuite rediffuse a tout le salon. Utilisez-la pour les coups qui doivent etre verifies (anti-triche) ; gardez 'Envoyer la variable aux autres joueurs' pour les donnees cosmetiques.",
      "fullName": "Envoyer une action de partie au serveur (regles appliquees)",
      "functionType": "Action",
      "group": "Multijoueur",
      "name": "EnvoyerActionPartie",
      "sentence": "Envoyer l'action de partie _PARAM0_ avec les donnees _PARAM1_ (resultat dans _PARAM2_)",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "var task = new gdjs.ManuallyResolvableTask();",
            "eventsFunctionContext.task = task;",
            "var nom = String(eventsFunctionContext.getArgument(\"Action\") || \"\").trim();",
            "var don = A.getVariableData(eventsFunctionContext.getArgument(\"Donnees\"));",
            "var logVar = eventsFunctionContext.getArgument(\"VariableLog\");",
            "A.matchAction(\"\", nom, don).then(function (r) {",
            "  if (logVar) { A.setVariableData(logVar, r); }",
            "  task.resolve();",
            "}, function () {",
            "  if (logVar) { A.setVariableData(logVar, { ok: false, reason: \"network_error\" }); }",
            "  task.resolve();",
            "});",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [
        {
          "description": "Nom de l'action (ex : coup, tir, pret)",
          "name": "Action",
          "type": "string"
        },
        {
          "description": "Variable contenant les donnees de l'action",
          "name": "Donnees",
          "type": "scenevar"
        },
        {
          "description": "Variable facultative pour recevoir { ok, partie, reason }",
          "name": "VariableLog",
          "type": "scenevar",
          "optional": true
        }
      ],
      "objectGroups": [],
      "async": true
    },
    {
      "description": "Recupere l etat autoritaire de la partie en cours depuis le serveur (participants, plateau, tour, statut). Utile en reconnexion ou pour verifier la coherence.",
      "fullName": "Charger l'etat officiel de la partie",
      "functionType": "Action",
      "group": "Multijoueur",
      "name": "ChargerEtatPartie",
      "sentence": "Charger l'etat officiel de la partie (resultat dans _PARAM0_)",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "var task = new gdjs.ManuallyResolvableTask();",
            "eventsFunctionContext.task = task;",
            "var logVar = eventsFunctionContext.getArgument(\"VariableLog\");",
            "A.matchState(\"\").then(function (r) {",
            "  if (logVar) { A.setVariableData(logVar, r); }",
            "  task.resolve();",
            "}, function () {",
            "  task.resolve();",
            "});",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [
        {
          "description": "Variable facultative pour recevoir { ok, partie }",
          "name": "VariableLog",
          "type": "scenevar",
          "optional": true
        }
      ],
      "objectGroups": [],
      "async": true
    },
    {
      "description": "Renvoie le JSON de l etat officiel de la partie (statut, tour_joueur, numero_tour, etat_plateau, participants...), mis a jour apres EnvoyerActionPartie / ChargerEtatPartie. Convertissez-le avec ToJSON vers une variable.",
      "fullName": "Etat officiel de la partie (JSON)",
      "functionType": "StringExpression",
      "group": "Multijoueur",
      "name": "EtatPartieJson",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = \"{}\";",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = S.partieJson || \"{}\";",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "string"
      },
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Vrai lorsque le tour de la partie appartient au joueur connecte (d apres le dernier etat officiel recu).",
      "fullName": "C'est mon tour",
      "functionType": "Condition",
      "group": "Multijoueur",
      "name": "EstMonTour",
      "sentence": "C'est le tour du joueur connecte",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = false;",
            "  return;",
            "}",
            "var S = A.state;",
            "var P = {}; try { P = JSON.parse(S.partieJson || \"{}\") || {}; } catch (e) { P = {}; } var V = P.etat_plateau; if (typeof V === \"string\") { try { V = JSON.parse(V); } catch (e) { V = {}; } } V = V || {};",
            "eventsFunctionContext.returnValue = !!P.tour_joueur && String(P.tour_joueur) === String(S.accountId || \"\");",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Vrai lorsque le tour appartient au joueur dont l identifiant ou le pseudo est donne.",
      "fullName": "Le tour appartient a un joueur",
      "functionType": "Condition",
      "group": "Multijoueur",
      "name": "TourAppartientA",
      "sentence": "Le tour appartient a _PARAM0_",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = false;",
            "  return;",
            "}",
            "var S = A.state;",
            "var P = {}; try { P = JSON.parse(S.partieJson || \"{}\") || {}; } catch (e) { P = {}; } var V = P.etat_plateau; if (typeof V === \"string\") { try { V = JSON.parse(V); } catch (e) { V = {}; } } V = V || {};",
            "var j = String(eventsFunctionContext.getArgument(\"Joueur\") || \"\");",
            "var t = String(P.tour_joueur || \"\");",
            "var ok = !!t && t === j;",
            "if (!ok && t) { (P.participants || []).forEach(function (m) { if (String(m.joueur_id) === t && String(m.pseudo) === j) { ok = true; } }); }",
            "eventsFunctionContext.returnValue = ok;",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [
        {
          "description": "Identifiant ou pseudo du joueur",
          "name": "Joueur",
          "type": "string"
        }
      ],
      "objectGroups": []
    },
    {
      "description": "Vrai lorsque le minuteur demarre par la feuille d evenements des matchs est arrive a zero.",
      "fullName": "Le minuteur du salon est ecoule",
      "functionType": "Condition",
      "group": "Multijoueur",
      "name": "MinuteurSalonEcoule",
      "sentence": "Le minuteur _PARAM0_ du salon est ecoule",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = false;",
            "  return;",
            "}",
            "var S = A.state;",
            "var P = {}; try { P = JSON.parse(S.partieJson || \"{}\") || {}; } catch (e) { P = {}; } var V = P.etat_plateau; if (typeof V === \"string\") { try { V = JSON.parse(V); } catch (e) { V = {}; } } V = V || {};",
            "var fin = Number(V[String(eventsFunctionContext.getArgument(\"Nom\") || \"\")]);",
            "eventsFunctionContext.returnValue = !isNaN(fin) && fin > 0 && Date.now() >= fin;",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [
        {
          "description": "Nom du minuteur (ex : chrono)",
          "name": "Nom",
          "type": "string"
        }
      ],
      "objectGroups": []
    },
    {
      "description": "Identifiant du joueur qui doit jouer.",
      "fullName": "Joueur dont c est le tour",
      "functionType": "StringExpression",
      "group": "Multijoueur",
      "name": "TourJoueur",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = \"\";",
            "  return;",
            "}",
            "var S = A.state;",
            "var P = {}; try { P = JSON.parse(S.partieJson || \"{}\") || {}; } catch (e) { P = {}; } var V = P.etat_plateau; if (typeof V === \"string\") { try { V = JSON.parse(V); } catch (e) { V = {}; } } V = V || {};",
            "eventsFunctionContext.returnValue = String(P.tour_joueur || \"\");",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "string"
      },
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Pseudo du joueur qui doit jouer.",
      "fullName": "Pseudo du joueur dont c est le tour",
      "functionType": "StringExpression",
      "group": "Multijoueur",
      "name": "TourJoueurPseudo",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = \"\";",
            "  return;",
            "}",
            "var S = A.state;",
            "var P = {}; try { P = JSON.parse(S.partieJson || \"{}\") || {}; } catch (e) { P = {}; } var V = P.etat_plateau; if (typeof V === \"string\") { try { V = JSON.parse(V); } catch (e) { V = {}; } } V = V || {};",
            "var r = \"\";",
            "(P.participants || []).forEach(function (m) { if (String(m.joueur_id) === String(P.tour_joueur)) { r = String(m.pseudo || \"\"); } });",
            "eventsFunctionContext.returnValue = r;",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "string"
      },
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Numero du tour en cours (1 au debut de la partie).",
      "fullName": "Numero du tour",
      "functionType": "Expression",
      "group": "Multijoueur",
      "name": "NumeroTour",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = 0;",
            "  return;",
            "}",
            "var S = A.state;",
            "var P = {}; try { P = JSON.parse(S.partieJson || \"{}\") || {}; } catch (e) { P = {}; } var V = P.etat_plateau; if (typeof V === \"string\") { try { V = JSON.parse(V); } catch (e) { V = {}; } } V = V || {};",
            "eventsFunctionContext.returnValue = Number(P.numero_tour) || 0;",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "number"
      },
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Valeur texte d une variable du salon modifiee par la feuille d evenements.",
      "fullName": "Variable du salon (texte)",
      "functionType": "StringExpression",
      "group": "Multijoueur",
      "name": "VariableSalonTexte",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = \"\";",
            "  return;",
            "}",
            "var S = A.state;",
            "var P = {}; try { P = JSON.parse(S.partieJson || \"{}\") || {}; } catch (e) { P = {}; } var V = P.etat_plateau; if (typeof V === \"string\") { try { V = JSON.parse(V); } catch (e) { V = {}; } } V = V || {};",
            "var v = V[String(eventsFunctionContext.getArgument(\"Nom\") || \"\")];",
            "eventsFunctionContext.returnValue = v == null ? \"\" : (typeof v === \"object\" ? JSON.stringify(v) : String(v));",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "string"
      },
      "parameters": [
        {
          "description": "Nom de la variable",
          "name": "Nom",
          "type": "string"
        }
      ],
      "objectGroups": []
    },
    {
      "description": "Valeur numerique d une variable du salon modifiee par la feuille d evenements.",
      "fullName": "Variable du salon (nombre)",
      "functionType": "Expression",
      "group": "Multijoueur",
      "name": "VariableSalonNombre",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = 0;",
            "  return;",
            "}",
            "var S = A.state;",
            "var P = {}; try { P = JSON.parse(S.partieJson || \"{}\") || {}; } catch (e) { P = {}; } var V = P.etat_plateau; if (typeof V === \"string\") { try { V = JSON.parse(V); } catch (e) { V = {}; } } V = V || {};",
            "eventsFunctionContext.returnValue = Number(V[String(eventsFunctionContext.getArgument(\"Nom\") || \"\")]) || 0;",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "number"
      },
      "parameters": [
        {
          "description": "Nom de la variable",
          "name": "Nom",
          "type": "string"
        }
      ],
      "objectGroups": []
    },
    {
      "description": "Secondes restantes avant la fin du minuteur (0 si ecoule ou absent).",
      "fullName": "Temps restant d un minuteur du salon",
      "functionType": "Expression",
      "group": "Multijoueur",
      "name": "TempsRestantMinuteur",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = 0;",
            "  return;",
            "}",
            "var S = A.state;",
            "var P = {}; try { P = JSON.parse(S.partieJson || \"{}\") || {}; } catch (e) { P = {}; } var V = P.etat_plateau; if (typeof V === \"string\") { try { V = JSON.parse(V); } catch (e) { V = {}; } } V = V || {};",
            "var fin = Number(V[String(eventsFunctionContext.getArgument(\"Nom\") || \"\")]) || 0;",
            "eventsFunctionContext.returnValue = Math.max(0, (fin - Date.now()) / 1000);",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "expressionType": {
        "type": "number"
      },
      "parameters": [
        {
          "description": "Nom du minuteur (ex : chrono)",
          "name": "Nom",
          "type": "string"
        }
      ],
      "objectGroups": []
    },
    {
      "description": "Affiche le panneau de matchmaking integre (meme habillage que le panneau de connexion) : le joueur voit son avatar pendant la recherche, puis la composition des equipes quand le salon est trouve. Le panneau rejoint la file automatiquement, affiche 'en attente du lancement', puis se ferme quand le jeu appelle 'Rejoindre le canal temps-reel du salon'. Si le joueur n'est pas connecte, le panneau de connexion s'ouvre d'abord.",
      "fullName": "Ouvrir le panneau de matchmaking",
      "functionType": "Action",
      "group": "Multijoueur",
      "name": "OuvrirPanneauMatchmaking",
      "sentence": "Ouvrir le panneau de matchmaking pour le mode _PARAM0_",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "var task = new gdjs.ManuallyResolvableTask();",
            "eventsFunctionContext.task = task;",
            "var mode = String(eventsFunctionContext.getArgument(\"Mode\") || \"1v1\").trim() || \"1v1\";",
            "A.openMatchmakingPanel(runtimeScene, mode).then(function () {",
            "  task.resolve();",
            "}, function () {",
            "  task.resolve();",
            "});",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [
        {
          "description": "Code du mode de jeu (ex : 1v1, 2v2)",
          "name": "Mode",
          "type": "string"
        }
      ],
      "objectGroups": [],
      "async": true
    },
    {
      "description": "Ferme le panneau de matchmaking. Si une recherche est encore en cours, le joueur est retire de la file d attente.",
      "fullName": "Fermer le panneau de matchmaking",
      "functionType": "Action",
      "group": "Multijoueur",
      "name": "FermerPanneauMatchmaking",
      "sentence": "Fermer le panneau de matchmaking",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  console.error(\"[AngonCloud] Extension not initialized. Make sure the scene is running.\");",
            "  return;",
            "}",
            "var S = A.state;",
            "A.closeMatchmakingPanel(true);",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Vrai tant que le panneau de matchmaking est affiche a l ecran.",
      "fullName": "Le panneau de matchmaking est ouvert",
      "functionType": "Condition",
      "group": "Multijoueur",
      "name": "PanneauMatchmakingOuvert",
      "sentence": "Le panneau de matchmaking est ouvert",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "var A = gdjs.__angon;",
            "if (!A) {",
            "  eventsFunctionContext.returnValue = false;",
            "  return;",
            "}",
            "var S = A.state;",
            "eventsFunctionContext.returnValue = !!S.mmPanelOpen;",
            ""
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": true
        }
      ],
      "parameters": [],
      "objectGroups": []
    }
  ],
  "eventsFunctionsFolderStructure": {
    "folderName": "__ROOT",
    "children": [
      {
        "folderName": "Configuration",
        "children": [
          {
            "functionName": "onFirstSceneLoaded"
          },
          {
            "functionName": "EstConfigure"
          }
        ]
      },
      {
        "folderName": "Authentification",
        "children": [
          {
            "functionName": "Connecter"
          },
          {
            "functionName": "Inscrire"
          },
          {
            "functionName": "RestaurerSession"
          },
          {
            "functionName": "RafraichirProfil"
          },
          {
            "functionName": "Deconnecter"
          },
          {
            "functionName": "EstConnecte"
          },
          {
            "functionName": "VientDeSeConnecter"
          },
          {
            "functionName": "SessionPrete"
          },
          {
            "functionName": "SeSouvenirActive"
          },
          {
            "functionName": "DefinirSeSouvenir"
          },
          {
            "functionName": "ConnecterAvecGoogle"
          }
        ]
      },
      {
        "folderName": "Panneau Angon",
        "children": [
          {
            "functionName": "OuvrirPanneauConnexion"
          },
          {
            "functionName": "OuvrirPanneauInscription"
          },
          {
            "functionName": "FermerPanneau"
          },
          {
            "functionName": "PanneauOuvert"
          }
        ]
      },
      {
        "folderName": "Profil joueur",
        "children": [
          {
            "functionName": "Pseudo"
          },
          {
            "functionName": "Email"
          },
          {
            "functionName": "Pays"
          },
          {
            "functionName": "Statut"
          },
          {
            "functionName": "Jeton"
          },
          {
            "functionName": "IdCompte"
          },
          {
            "functionName": "IdProfil"
          },
          {
            "functionName": "Donnees"
          },
          {
            "functionName": "Niveau"
          },
          {
            "functionName": "Score"
          },
          {
            "functionName": "ChangerPhotoProfil"
          },
          {
            "functionName": "PhotoProfil"
          }
        ]
      },
      {
        "folderName": "Etat des requetes",
        "children": [
          {
            "functionName": "RequeteEnCours"
          },
          {
            "functionName": "DerniereRequeteAEchoue"
          },
          {
            "functionName": "CodeErreur"
          },
          {
            "functionName": "MessageErreur"
          },
          {
            "functionName": "CodeStatut"
          }
        ]
      },
      {
        "folderName": "Tables : Lecture et Recherche",
        "children": [
          {
            "functionName": "ListerLignes"
          },
          {
            "functionName": "ListerLignesFiltrees"
          },
          {
            "functionName": "ListerLignesTriees"
          },
          {
            "functionName": "ObtenirLigne"
          },
          {
            "functionName": "ObtenirPremiereLigne"
          }
        ]
      },
      {
        "folderName": "Tables : Ecriture et Insertion",
        "children": [
          {
            "functionName": "CreerLigneDepuisVariable"
          },
          {
            "functionName": "CreerLigneJson"
          },
          {
            "functionName": "CreerLigneSimple"
          },
          {
            "functionName": "CreerLigneDouble"
          }
        ]
      },
      {
        "folderName": "Tables : Modification",
        "children": [
          {
            "functionName": "ModifierLigneDepuisVariable"
          },
          {
            "functionName": "ModifierLigneJson"
          },
          {
            "functionName": "ModifierChampLigne"
          },
          {
            "functionName": "ModifierNombreLigne"
          }
        ]
      },
      {
        "folderName": "Tables : Suppression",
        "children": [
          {
            "functionName": "SupprimerLigne"
          },
          {
            "functionName": "SupprimerLignesFiltrees"
          }
        ]
      },
      {
        "folderName": "Tables : Donnees et Expressions",
        "children": [
          {
            "functionName": "TableDerniereOperationReussie"
          },
          {
            "functionName": "TableContientLignes"
          },
          {
            "functionName": "DernierIdLigne"
          },
          {
            "functionName": "NombreLignesTable"
          },
          {
            "functionName": "DonneesTableJson"
          },
          {
            "functionName": "ValeurChampLigne"
          },
          {
            "functionName": "ValeurNombreChampLigne"
          },
          {
            "functionName": "LigneChampTexte"
          },
          {
            "functionName": "LigneChampNombre"
          }
        ]
      },
      {
        "folderName": "Stockage Angon Cloud",
        "children": [
          {
            "functionName": "TeleverserFichierTexte"
          },
          {
            "functionName": "TeleverserFichierDialogue"
          },
          {
            "functionName": "SupprimerFichierJoueur"
          },
          {
            "functionName": "ListerFichiersJoueur"
          },
          {
            "functionName": "DernierTeleversementReussi"
          },
          {
            "functionName": "DernierTeleversementEchoue"
          },
          {
            "functionName": "DernierFichierUrl"
          },
          {
            "functionName": "DernierFichierId"
          },
          {
            "functionName": "DernierFichierNom"
          },
          {
            "functionName": "DernierFichierTaille"
          },
          {
            "functionName": "FichiersJoueurJson"
          }
        ]
      },
      {
        "folderName": "Mode Hors-Ligne et Synchronisation",
        "children": [
          {
            "functionName": "EstEnLigne"
          },
          {
            "functionName": "EstHorsLigne"
          },
          {
            "functionName": "DonneesEnAttenteDeSynchronisation"
          },
          {
            "functionName": "SynchronisationEnCours"
          },
          {
            "functionName": "NombreOperationsEnAttente"
          },
          {
            "functionName": "ForcerSynchronisation"
          },
          {
            "functionName": "DefinirPolitiqueHorsLigneTable"
          },
          {
            "functionName": "ActualiserPolitiquesTables"
          },
          {
            "functionName": "TableAutoriseHorsLigne"
          },
          {
            "functionName": "TableEnLectureSeuleHorsLigne"
          },
          {
            "functionName": "PolitiqueHorsLigneTable"
          }
        ]
      },
      {
        "folderName": "Boutique Angon Cloud",
        "children": [
          {
            "functionName": "ListerArticlesBoutique"
          },
          {
            "functionName": "BoutiqueContientArticles"
          },
          {
            "functionName": "NombreArticlesBoutique"
          },
          {
            "functionName": "ArticleBoutiqueCode"
          },
          {
            "functionName": "ArticleBoutiqueNom"
          },
          {
            "functionName": "ArticleBoutiqueDescription"
          },
          {
            "functionName": "ArticleBoutiquePrix"
          },
          {
            "functionName": "ArticleBoutiqueDevise"
          },
          {
            "functionName": "ArticleBoutiqueIcone"
          },
          {
            "functionName": "ArticleBoutiqueType"
          },
          {
            "functionName": "ArticleBoutiqueEstEnPromo"
          },
          {
            "functionName": "ArticleBoutiquePrixPromo"
          }
        ]
      },
      {
        "folderName": "Classements",
        "children": [
          {
            "functionName": "SoumettreScore"
          },
          {
            "functionName": "ChargerClassement"
          },
          {
            "functionName": "DernierScoreEnvoye"
          },
          {
            "functionName": "DernierRangClassement"
          },
          {
            "functionName": "DernierScoreClassement"
          },
          {
            "functionName": "DernierCodeClassement"
          },
          {
            "functionName": "NombreEntreesClassement"
          },
          {
            "functionName": "EntreeClassementPseudo"
          },
          {
            "functionName": "EntreeClassementScore"
          },
          {
            "functionName": "EntreeClassementRang"
          },
          {
            "functionName": "EntreeClassementPays"
          }
        ]
      },
      {
        "folderName": "Multijoueur",
        "children": [
          {
            "functionName": "RejoindreFileAttente"
          },
          {
            "functionName": "QuitterFileAttente"
          },
          {
            "functionName": "VerifierFileAttente"
          },
          {
            "functionName": "EnFileDAttente"
          },
          {
            "functionName": "PartieTrouvee"
          },
          {
            "functionName": "PositionFileAttente"
          },
          {
            "functionName": "IdPartieMultijoueur"
          },
          {
            "functionName": "CodeSalonMultijoueur"
          },
          {
            "functionName": "MonEquipe"
          },
          {
            "functionName": "ParticipantsSalonJson"
          },
          {
            "functionName": "RejoindreSalonTempsReel"
          },
          {
            "functionName": "QuitterSalonTempsReel"
          },
          {
            "functionName": "SalonTempsReelConnecte"
          },
          {
            "functionName": "EnvoyerActionSalon"
          },
          {
            "functionName": "ActionSalonRecue"
          },
          {
            "functionName": "DerniereActionSalon"
          },
          {
            "functionName": "OuvrirPanneauMatchmaking"
          },
          {
            "functionName": "FermerPanneauMatchmaking"
          },
          {
            "functionName": "PanneauMatchmakingOuvert"
          }
        ]
      }
    ]
  },
  "eventsBasedBehaviors": [],
  "eventsBasedObjects": []
}
